• curveIt’s not often used in development, but learning how to draw curves can make your software much bettercreativeandEndless charm.

Other apis are the basis for drawing, and I think curves are the soul of painting. Take off with it. In this lesson we are going to learn about curves and the application of curves and so on.

I. Curve recognition and understanding

Curve common API 1. First-order curve 2. Second-order curve 3Copy the code

We in junior high school learning to learn a variety of straight lines, circles, ellipses, is xuan… Coordinate system equations for curves and so on, and then let’s review our equations for lines and curves and so on.

  • So the first step we’re going to do is we’re going to define a class and we’re going to create a coordinate system, and we’re going to rotate the screen in landscape
package com.example.android_draw.view

import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.View

/ * * * * ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ * │ ┌ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┐ │ x │ │ Esc │! 1 2 │ │ @ # % 3 $4 │ │ │ 5 6 7 │ │ & * ^ 8 │ │ │ (9) 0 _ + = │ │ - | \ │ ` ~ │ │ * │ ├ ─ ─ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ─ ─ ┤ │ x │ │ Tab │ │ │ │ │ Q W E R T I │ │ │ Y U │ │ │ P O {[│}] │ BS │ │ x │ ├ ─ ─ ─ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ─ ─ ─ ─ ─ ┤ │ x │ │ Ctrl │ │ │ │ │ │ F G D S A H │ │ │ K J L │ :; │ │ "' Enter │ │ X │ ├ ─ ─ ─ ─ ─ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ─ ─ ─ ┬ ─ ─ ─ ┤ │ X │ │ Shift │ │ X │ │ Z C V │ │ │ N M B , │ │ < >. │? / │ Shift │ Fn │ │ x │ └ ─ ─ ─ ─ ─ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ─ ─ ┬ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ┬ ┴ ─ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ┘ │ x │ │ Fn Alt │ │ Space Alt │ │ Win │ HHKB │ x │ └ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ┘ │ * └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ Copyright (c) 2015 Bohai Xinneng All Rights Reserved@authorFeiWang * Version: 1.5 * Created date: 2/8/21 * Description: Android_Draw * E-mail: 1276998208@qq.com
 * CSDN:https://blog.csdn.net/m0_37667770/article
 * GitHub:https://github.com/luhenchang
 */
class LHC_Cubic_View @JvmOverloads constructor( context: Context? , attrs: AttributeSet? =null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    init{}override fun onDraw(canvas: Canvas?). {
        super.onDraw(canvas)
    }

}

Copy the code

In order to facilitate observation and rendering, grid and coordinate axes are drawn. I believe that I have mastered the transformation operation of canvas in the last article. I will not explain the code of grid axis. Look at the picture.

1. The equation is mapped to the coordinate system

Remember we learned in junior high that Y of x is equal to ax plus b. So let’s look at this equation mapped to the coordinate system. I’m going to define a function y equals 2x minus 80 and I’m going to get a set of points, and for the sake of effect we’re going to draw x even, and then we’re going to draw points. The code is as follows:

    private var number=0.420.
    // The equation of the line is y=2x-80
    private fun drawzxLine(canvas: Canvas) {
        pointList= ArrayList()
        // Draw y=10x+20
        val gPaint = getPaint()
        number.forEach { t ->
            val point=PointF()
            if (t%2= =0) {// Draw even points on the X-axis
                point.x = t.toFloat()
                point.y = 2f * t - 80
                pointList.add(point)
                canvas.drawPoint(point.x, point.y, gPaint)
            }
        }
    }

Copy the code

1. The equations of the same circle and ellipse can be mapped to the coordinate system in this way.

2.The curve represented is a circle with center O(a, b) and radius R.

3. For example: (x – 10)2+(y-10)2= 1602Let’s map it to the coordinate system.

  • I should be able to solve the equation. Let’s convert to the familiar equation:

(x-10)2+(y-10)2=1602 1. (y-10)2=1602-(x-10)2 2 The equation is as follows: There are positive and negative values after the square root

  1. Y = SQRT (160.0 pow (2.0). The toFloat () – ((point. X – 10). ToDouble ()). The pow (2.0)). ToFloat () + 10
  2. Y = – SQRT (160.0 pow (2.0). The toFloat () – ((pointDown. X – 10). ToDouble ()). The pow (2.0)). ToFloat () + 10
 // Draw the circle
        number.forEach { t ->
            val point = PointF()
            val pointDown = PointF()

            //(x-10)2+ (y-10)2 =1602
            point.x = t.toFloat()
            pointDown.x = t.toFloat()
            // I don't need to tell you.
            point.y =
                sqrt(160.0.pow(2.0).toFloat() - ((point.x - 10).toDouble()).pow(2.0)).toFloat() + 10
            pointDown.y = -sqrt(
                160.0.pow(2.0).toFloat() - ((pointDown.x - 10).toDouble()).pow(2.0)
            ).toFloat() + 10
            canvas.drawPoint(point.x, point.y, gPaint)
            canvas.drawPoint(pointDown.x, pointDown.y, gPaint)

        }
Copy the code

2. Bezier curve

From the above we can see that any function can be mapped to coordinate system drawing one by one, and of course there are equations for Bezier curves. There are the following:

Linear Bezier curve

  • Given points P0, P1, the linear Bezier curve is just a straight line between two points. The line is given by the following formula:

     

Quadratic Bezier curve

  • The path of the quadratic Bezier curve is traced by B (t) of the given points P0, P1, and P2:

     

Bessel curve to the third power

The points P0, P1, P2, and P3 define a cubic Bezier curve in the plane or in three dimensions. It starts at P0 going to P1, and it goes from P2 to P3. It usually doesn’t go through P1 or P2; The formula is as follows:

Of course, the Native layer of Android terminal has encapsulated methods, quadratic Bezier curve and cubic Bezier curve, and known functions can be encapsulated.

Second and third order bezier curves are provided on the Android side:public void quadTo(float x1, float y1, float x2, float y2)
    publicVoid rQuadTo(float dx1, float dy1, float dx2, float dy2)public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)
    public void rCubicTo(float x1, float y1, float x2, float y2,float x3, float y3) 

Copy the code

Next, we draw a second-order curve, and the control point can move the screen with the gesture and press down. It is very clear in the previous section of the broken line about the mapping between the gesture coordinate system and the screen coordinate system, which will not be explained here.

  • quadTo(float x1, float y1, float x2, float y2)
    // Record the moving canvas coordinates, not gesture coordinates, which are converted to canvas coordinates for refreshing
    private var moveX: Float = 160f
    private var moveY: Float = 160f
   private fun drawQuz(canvas: Canvas) {
        controllRect = Rect(
            (moveX - 30f).toInt(),
            (moveY + 30f).toInt(),
            (moveX + 30).toInt(),
            (moveY - 30f).toInt()
        )
        val quePath = Path()
        canvas.drawCircle(0f.0f.10f, getPaintCir(Paint.Style.FILL))
        canvas.drawCircle(320f.0f.10f, getPaintCir(Paint.Style.FILL))
        // The first point and control point are connected to the last point chain. For the sake of observation
        val lineLeft = Path()
        lineLeft.moveTo(0f.0f)
        lineLeft.lineTo(moveX, moveY)
        lineLeft.lineTo(320f.0f)
        canvas.drawPath(lineLeft, getPaint(Paint.Style.STROKE))
        // Draw a circle at p0. I'm going to draw a control point circle at the second p1, and then I'm going to draw a control point circle at the end.
        canvas.drawCircle(moveX, moveY, 10f, getPaintCir(Paint.Style.FILL))
        quePath.quadTo(moveX, moveY, 320f.0f)
        canvas.drawPath(quePath, getPaint(Paint.Style.STROKE))
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            ACTION_DOWN,
            ACTION_MOVE -> {
                // Within the range near the control point, move
                Log.e("x="."onTouchEvent: (x,y)"+(event.x - width / 2).toInt()+":"+(-(event.y - height / 2)).toInt())
                // Convert gesture coordinates to screen coordinates
                moveX = event.x - width / 2
                moveY = -(event.y - height / 2)
                invalidate()
            }
        }
        return true
    }

Copy the code

In the figure above, you can drag the control point, and the curve between the beginning and the end deforms with the control point. The protrusion near the radian of the control point is inclined to the other side. It is only necessary to have a preliminary understanding of this rule, and the control point is constantly adjusted in practice to meet our needs. But in the figure above we see that the radians are not circular enough, and we can adjust the radians well in the third order function. Now let’s look at third-order functions

The third order curve

  • public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)

And again we’re going to plot the third order curve in the coordinate system. In order to get a good view of the effect we are going to do fine control this time, we can drag any control point we want and look at our third order curve. In the previous section, the contains method of Rect with the gesture can be used for local clicking, of course, dragging is also no problem. As shown in the figure below: We only need to draw the distance shape near the control point to wrap the control point, and the control point and corresponding distance shape can be refreshed by gesture sliding.

  private fun drawCubic(canvas: Canvas) {
       val cubicPath=Path()
       cubicPath.moveTo(0f.0f)
       cubicLeftRect= Rect(
           (moveCubiX - 30f).toInt(),
           (moveCubiY - 30f).toInt(),
           (moveCubiX + 30).toInt(),
           (moveCubiY + 30f).toInt()
       )
       cubicRightRect=Rect(
           (moveCubiXX - 30f).toInt(),
           (moveCubiYY - 30f).toInt(),
           (moveCubiXX + 30).toInt(),
           (moveCubiYY + 30f).toInt()
       )
        val lineLeft = Path()
        lineLeft.moveTo(0f.0f)
        lineLeft.lineTo(moveCubiX, moveCubiY)
        lineLeft.lineTo(moveCubiXX, moveCubiYY)
        lineLeft.lineTo(320f.0f)
        canvas.drawPath(lineLeft, getPaint(Paint.Style.STROKE,Color.GRAY))

        //canvas.drawRect(cubicLeftRect, getPaint(Paint.Style.FILL,Color.RED))
        //canvas.drawRect(cubicRightRect, getPaint(Paint.Style.FILL,Color.RED))
        canvas.drawCircle(moveCubiX, moveCubiY, 10f, getPaintCir(Paint.Style.FILL))
        canvas.drawCircle(moveCubiXX, moveCubiYY, 10f, getPaintCir(Paint.Style.FILL))

        cubicPath.cubicTo(moveCubiX,moveCubiY,moveCubiXX,moveCubiYY,320f.0f)
        canvas.drawPath(cubicPath, getPaint(Paint.Style.STROKE,Color.RED))
    }
    
   override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            ACTION_DOWN,
            ACTION_MOVE -> {
                // Within the range near the control point, move
                Log.e(
                    "x="."onTouchEvent: (x,y)" + (event.x - width / 2).toInt() + ":" + (-(event.y - height / 2)).toInt()
                )
                // Second order curve
                if (controllRect.contains((event.x - width / 2).toInt(),(-(event.y - height / 2)).toInt())) {
                    Log.e("Click here"."To" )
                    moveX = event.x - width / 2
                    moveY = -(event.y - height / 2)
                    invalidate()
                // Control point 1 of third order curve
                }else if(cubicLeftRect.contains((event.x - width / 2).toInt(),(-(event.y - height / 2)).toInt())){
                    moveCubiX= event.x - width / 2
                    moveCubiY= -(event.y - height / 2)
                    invalidate()
                 // Third order curve control point 2
                }else if(cubicRightRect.contains((event.x - width / 2).toInt(),(-(event.y - height / 2)).toInt())){
                    moveCubiXX= event.x - width / 2
                    moveCubiYY= -(event.y - height / 2)
                    invalidate()

                }
            }
        }
        return true
    }

Copy the code

Now I think we have a pretty good idea of what second and third order curves are controlling in the general direction of radians. You think this is the end of it. Now let’s begin the formal application of the entry curve.

Curve – Simple application

In qq group no one asked how to do the effect. I think this should be pretty easy for you. We are going to use the above learning curve to write.

First prepare 1. A picture 2. Draw a rectangular box 3. Drag curve 5. Change hue saturation brightness, etc

1. Understanding of image hue saturation, etc

Android does not provide an API for manipulating the Bitmap directly to change hue saturation, so we need to draw the Bitmap onto the canvas. So let’s start writing code. First create class LHC_Image_View and add attributes to attrs.

attribute

<declare-styleable name="LHC_Image_View">
  <attr name="defaultImag" format="reference" />
</declare-styleable>

Copy the code

Get the property image and draw it to the canvas, where we need to know a little bit about the color API. ColorMatrix image is composed of countless pixels, each pixel with RGBA four values to describe, specifically by a 4*5 matrix to control the display of each pixel, point RGBA and together constitute the appearance of the bitmap expression form, so change the control pixel RGBA value, you can change the effect of bitmap display

The 'ColorMatrix', represented by a 4x5 matrix, is used to convert the color and alpha components of the bitmap. [a, b, c, d, e, f, g, h, I, j, k, l, m, n, o, p, q, r, s, t] calculation is as follows: r = a * r g + c + b * * * a + b + d e; G = f*R + g*G + h*B + i*A + j; B = k*R + l*G + m*B + n*A + o; A = p*R + q*G + r*B + s*A + t; Common default standard matrix [1 0 0 0 0 R=225 R1=225 + 0+ 0+ 0+ 0+ offset =225 0 1 0 0 x G=225 = G1=225 + 0+ 0+ 0+ offset =225 = RGBA=[225,225,225,225] 0 0 1 0 0 B=225 B1=225 + 0+ 0+ 0+ offset =225 0 0 0 1 0] A=225 A1=225 + 0+ 0+ 0+ offset =225 I want to lower the green and blue, so we can change the matrix 2 rows, 2 columns, 1 to 0.5,3 rows,3 columns, 1 to 0.5 and so on. Public ColorMatrix() {reset(); } /** * Create a new colormatrix initialized with the specified array of values. */ public ColorMatrix(float[] src) { System.arraycopy(src, 0, mArray, 0, 20); } /** * Create a new colormatrix initialized with the specified colormatrix. */ public ColorMatrix(ColorMatrix src) { System.arraycopy(src.mArray, 0, mArray, 0, 20); }Copy the code

Common use method in ColorMatrix :setRotate(int axis, PI / 180d here in order to control the matrix array developers easy to write to use the positive remainder function, -1 to 1 to 0-360 degrees, of course, this is my guess, after all, -1 to 1 decimal is not easy to grasp. For your convenience, please attach a positive image:

From the graph we can see that 0 degrees and 90 degrees are 0 and 1 respectively, so next we draw a picture set to int=0 for red.. Degrees =90 degrees

[ 1 0 0 0 0 R 0 1 0 0 0 x G 0 0 1 0 0 B =R G B A 0 0 0 1 0 ] A [ 1 0 0 0 0 R 0 0 1 0 0 x G =R B -G A 0 -1 0 0 0 B 0 0 0 So the red stays the same, the green becomes blue, the blue becomes negative, and we know that red and blue become yellow. So let's verify our results.Copy the code
    public void setRotate(int axis, float degrees) {
        reset();
        double radians = degrees * Math.PI / 180d;
        float cosine = (float) Math.cos(radians);
        float sine = (float) Math.sin(radians);
        switch (axis) {
        // Rotation around the red color
        case 0:
            mArray[6] = mArray[12] = cosine;
            mArray[7] = sine;
            mArray[11] = -sine;
            break;
        // Rotation around the green color
        case 1:
            mArray[0] = mArray[12] = cosine;
            mArray[2] = -sine;
            mArray[10] = sine;
            break;
        // Rotation around the blue color
        case 2:
            mArray[0] = mArray[6] = cosine;
            mArray[1] = sine;
            mArray[5] = -sine;
            break;
        default:
            thrownew RuntimeException(); }}Copy the code

Verification results:

class LHC_Image_View @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    private val MIDDLE_VALUE=127
    private var mdrawable: Drawable?
    lateinit var bitmap:Bitmap
    init {
        val array: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.LHC_Image_View)
        mdrawable = array.getDrawable(R.styleable.LHC_Image_View_defaultImag)

    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if(mdrawable! =null){ bitmap =mdrawable!! .toBitmap(width,height, Bitmap.Config.ARGB_8888) }else{
          return
        }
        // instantiate a brush
        val mPaint = Paint()
        mPaint.strokeWidth=10f
        // Instantiate the color matrix that handles the hue
        val colorMatrix =  ColorMatrix()
        // Get the hue calculation formula
        val gress=90f
        // 0 is red
        colorMatrix.setRotate(0, gress)

        // Set the color to the brush
        mPaint.colorFilter = ColorMatrixColorFilter(colorMatrix)
        // Then we use the adjusted color brush to paint the original image BMP onto the new bitmap
        canvas.drawBitmap(bitmap, 0f.0f, mPaint)
    }

}

Copy the code

Verification succeeded. So let’s go ahead and plot our curves, and get a little off topic.

2. Draw a rectangle

This one I think is super easy.

   private fun drawGrid(canvas: Canvas) {
        //1. The lower left corner is the dot on the screen
        canvas.translate(0f, height.toFloat())
        canvas.scale(1f, -1f)
        canvas.save()

        val xpath=Path()
        xpath.moveTo(0f.0f)
        xpath.lineTo(width.toFloat(), 0f)

        val paint=Paint()
        paint.color=Color.GRAY
        paint.strokeWidth=2f
        paint.style= Paint.Style.STROKE
        for (index in 0 until 6){
            canvas.translate(0f.160f)
            canvas.drawPath(xpath,paint)
        }
        canvas.restore()

        val ypath=Path()
        ypath.moveTo(0f.0f)
        ypath.lineTo(0f, width.toFloat()-120)

        val painty=Paint()
        painty.color=Color.GRAY
        painty.strokeWidth=2f
        painty.style= Paint.Style.STROKE
        canvas.save()

        for (index in 0 until 6){
            canvas.translate(160f.0f)
            canvas.drawPath(ypath,paint)
        }
        canvas.restore()



    }
Copy the code

3. Lines and curves with a gesture

Draw a line and a curve and a circle with a gesture. Also talk about a pepper, the above all learned, RIGHT? I think the little friends who work harder can only think of can not do it. If you haven’t — and I believe you haven’t read my article carefully — here’s the code:

      private fun drawLineAndCubit(canvas: Canvas) {
        val paint=Paint()
        paint.color=Color.GRAY
        paint.strokeWidth=2f
        paint.style= Paint.Style.STROKE
        / / slash
        val xpath=Path()
        xpath.moveTo(10f.10f)
        xpath.lineTo(width.toFloat(), width.toFloat()-120)
        canvas.drawPath(xpath,paint)
        // Start and finish circles
        paint.style= Paint.Style.FILL
        paint.color=Color.RED
        canvas.drawCircle(15f.15f.15f,paint)
        canvas.drawCircle(width.toFloat()-15, width.toFloat()-120-15.15f,paint)
        val cubicPath=Path()
        cubicPath.moveTo(0f.0f)
        paint.style= Paint.Style.STROKE
        paint.strokeWidth=5f
        cubicPath.quadTo(moveX, moveY,width.toFloat()-15, width.toFloat()-120-15)

        // Draw a curve
        canvas.drawPath(cubicPath,paint)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN,
            MotionEvent.ACTION_MOVE -> {
                // Within the range near the control point, move
                moveX= event.x 
                moveY= -(event.y - height)
                invalidate()
            }
        }
        return true
    }

Copy the code

Three, the use of curve, texture, cutting

Many floating spheres such as energy or progress spheres have bezier curves. Let’s do a simple application, write a small animation of fish swimming. This process we need is simple math calculation, canvas cropping, drawing pictures to canvas and other basic common operations. The following small case is analyzed step by step with the combination of curve animation and related API.

  • Draw the analysis
1. Wave drawing -> Curve drawing 2. Moving wave -> Canvas translation 3. Always moving animation 4. fish swimming with waves -> Drawing of pictures on canvasCopy the code

1. Wave drawing

In the image above we can see the curved waves in constant motion. So how are waves painted? The Bezier curve would have done the following.

Code:

// Draw waves
    private fun drawWave(canvas: Canvas) {
        val wavePath=Path()
        wavePath.moveTo(0f, -waveWidth * 6)
        wavePath.lineTo(0f.0f)
        wavePath.quadTo(waveWidth, waveHeight, waveWidth * 2.0f)
        wavePath.quadTo(waveWidth * 3, -waveHeight, waveWidth * 4.0f)
        wavePath.quadTo(waveWidth * 5, waveHeight, waveWidth * 6.0f)
        wavePath.quadTo(waveWidth * 7, -waveHeight, waveWidth * 8.0f)
        wavePath.lineTo(waveWidth * 8, -waveWidth * 6)
        canvas.drawPath(wavePath, getPaintBefor(Paint.Style.FILL))
Copy the code

2. Moving waves

The drawing of waves is only filled with colors after the curve is drawn. The movement of the curve can be accomplished simply by moving the canvas.

// The waves move with the pan of the canvas.
canvas.translate(animal.animatedValue as Float.0f) 

Copy the code

The effect of an ocean wave moving in a round or rectangular or a container of various shapes. All we have to do is cut the canvas to the desired shape and let the waves watch over the cut area.

Code:

    private fun clipCanvas(canvas: Canvas,type:Int) {
        if (type==0) {
            val rect = Rect(
                (waveWidth * 4).toInt(),
                160,
                (waveWidth * 8).toInt(),
                (-waveWidth * 4).toInt()
            )
            canvas.clipRect(rect)
        }else  if (type==1) {
            val circlePath = Path()
            circlePath.addCircle(480f.0f.160f, Path.Direction.CCW)
            canvas.drawCircle(480f.0f.160f, getPaint(Paint.Style.STROKE))
            canvas.clipPath(circlePath)
        }else  if (type==2) {val rundRect = Path()
            rundRect.addRoundRect(waveWidth * 4,waveWidth*3,waveWidth* 8,-waveWidth*3.60f.60f,Path.Direction.CCW)
            //canvas.drawPath(rundRect,getPaint(Paint.Style.STROKE))
            canvas.clipPath(rundRect)

        }
    }

Copy the code

But setting the value of the animation is a simple math calculation.

The value of animation only needs to be shifted by a whole wavelength = waveWidth * 4

 init {
        animal=ObjectAnimator.ofFloat(0f, waveWidth * 4)
        animal.duration = 2000
        animal.repeatCount = ValueAnimator.INFINITE;
        animal.interpolator = LinearInterpolator()
        animal.addUpdateListener {
            invalidate()
        }
    }
Copy the code

3. Fish swimming with the waves

Canvas pan. If you place a position map on the canvas, the picture will move as the canvas moves.

// You can read the related API to draw an image to the canvas coordinate system (left,top) position.
drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint)
Copy the code

Material can be downloaded in alibaba picture material library.

  val arrList= arrayListOf( R.drawable.yu, R.drawable.ziyuan, R.drawable.jingyu)
    private fun drawFish(canvas: Canvas) {

        val bmp = BitmapFactory.decodeResource(resources, arrList[0])
        canvas.drawBitmap(bmp, waveWidth * 3, -waveHeight * 2.2 f, getPaint(Paint.Style.FILL))


        val bmpzy = BitmapFactory.decodeResource(resources,arrList[1])
        canvas.drawBitmap(bmpzy, waveWidth, -waveHeight *3f, getPaint(Paint.Style.FILL))

        val bmpjy= BitmapFactory.decodeResource(resources,arrList[2])
        canvas.drawBitmap(bmpjy, waveWidth* 6, -waveHeight * 3.5 f, getPaint(Paint.Style.FILL))

    }
Copy the code

End result:

                                       Code:

package com.example.android_draw.view

import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_DOWN
import android.view.MotionEvent.ACTION_MOVE
import android.view.View
import android.view.animation.LinearInterpolator
import com.example.android_draw.R
import kotlin.math.max
import kotlin.random.Random

/ * * * * ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ * │ ┌ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┐ │ x │ │ Esc │! 1 2 │ │ @ # % 3 $4 │ │ │ 5 6 7 │ │ & * ^ 8 │ │ │ (9) 0 _ + = │ │ - | \ │ ` ~ │ │ * │ ├ ─ ─ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ─ ─ ┤ │ x │ │ Tab │ │ │ │ │ Q W E R T I │ │ │ Y U │ │ │ P O {[│}] │ BS │ │ x │ ├ ─ ─ ─ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ─ ─ ─ ─ ─ ┤ │ x │ │ Ctrl │ │ │ │ │ │ F G D S A H │ │ │ K J L │ :; │ │ "' Enter │ │ X │ ├ ─ ─ ─ ─ ─ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ─ ─ ─ ┬ ─ ─ ─ ┤ │ X │ │ Shift │ │ X │ │ Z C V │ │ │ N M B , │ │ < >. │? / │ Shift │ Fn │ │ x │ └ ─ ─ ─ ─ ─ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ─ ─ ┬ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ┬ ┴ ─ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ┘ │ x │ │ Fn Alt │ │ Space Alt │ │ Win │ HHKB │ x │ └ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ┘ │ * └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ Copyright (c) 2015 Bohai Xinneng All Rights Reserved@authorFeiWang * Version: 1.5 * Created date: 2/8/21 * Description: Android_Draw * E-mail: 1276998208@qq.com
 * CSDN:https://blog.csdn.net/m0_37667770/article
 * GitHub:https://github.com/luhenchang
 */
@Suppress("DEPRECATION")
class LHC_wave_View @JvmOverloads constructor( context: Context? , attrs: AttributeSet? =null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    private var moveX: Float = 0f
    private var moveY: Float = 0f
    private var hCount: Int = 0
    private var wCount: Int = 0
    private lateinit var pointList: ArrayList<PointF>



    // Width of grid
    var gridWidth = 80
    / / half wavelength
    val waveWidth=80f
    val waveHeight=30f
    lateinit var animal:ValueAnimator
    init {
        animal=ObjectAnimator.ofFloat(0f, waveWidth * 4)
        animal.duration = 2000
        animal.repeatCount = ValueAnimator.INFINITE;
        animal.interpolator = LinearInterpolator()
        animal.addUpdateListener {
            invalidate()
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        // Draw grid lines
        drawGridLine(canvas)
        // Draw text on the x and y axes
        drawTextXAndY(canvas)
        // Crop the canvas
        clipCanvas(canvas,2)
        // Draw waves
        drawWave(canvas)
        / / to draw
        drawFish(canvas)



    }
    val arrList= arrayListOf( R.drawable.yu, R.drawable.ziyuan, R.drawable.jingyu)
    private fun drawFish(canvas: Canvas) {

        val bmp = BitmapFactory.decodeResource(resources, arrList[0])
        canvas.drawBitmap(bmp, waveWidth * 3, -waveHeight * 2.2 f, getPaint(Paint.Style.FILL))


        val bmpzy = BitmapFactory.decodeResource(resources,arrList[1])
        canvas.drawBitmap(bmpzy, waveWidth, -waveHeight *3f, getPaint(Paint.Style.FILL))

        val bmpjy= BitmapFactory.decodeResource(resources,arrList[2])
        canvas.drawBitmap(bmpjy, waveWidth* 6, -waveHeight * 3.5 f, getPaint(Paint.Style.FILL))

    }

    private fun clipCanvas(canvas: Canvas,type:Int) {
        if (type==0) {
            val rect = Rect(
                (waveWidth * 4).toInt(),
                160,
                (waveWidth * 8).toInt(),
                (-waveWidth * 4).toInt()
            )
            canvas.clipRect(rect)
        }else  if (type==1) {
            val circlePath = Path()
            circlePath.addCircle(480f.0f.160f, Path.Direction.CCW)
            canvas.drawCircle(480f.0f.160f, getPaint(Paint.Style.STROKE))
            canvas.clipPath(circlePath)
        }else  if (type==2) {val rundRect = Path()
            rundRect.addRoundRect(waveWidth * 4,waveWidth*3,waveWidth* 8,-waveWidth*3.60f.60f,Path.Direction.CCW)
            //canvas.drawPath(rundRect,getPaint(Paint.Style.STROKE))
            canvas.clipPath(rundRect)

        }
    }


    // Draw waves
    private fun drawWave(canvas: Canvas) {
        canvas.translate(animal.animatedValue as Float.0f) // Inner wave
        val wavePath=Path()
        wavePath.moveTo(0f, -waveWidth * 6)
        wavePath.lineTo(0f.0f)
        wavePath.quadTo(waveWidth, waveHeight, waveWidth * 2.0f)
        wavePath.quadTo(waveWidth * 3, -waveHeight, waveWidth * 4.0f)
        wavePath.quadTo(waveWidth * 5, waveHeight, waveWidth * 6.0f)
        wavePath.quadTo(waveWidth * 7, -waveHeight, waveWidth * 8.0f)
        wavePath.lineTo(waveWidth * 8, -waveWidth * 6)
        canvas.drawPath(wavePath, getPaintBefor(Paint.Style.FILL))

        canvas.translate(animal.animatedValue as Float.0f) // Inner wave
        val wavePath_out=Path()
        wavePath_out.moveTo(-waveWidth * 7, -waveWidth * 6)
        wavePath_out.lineTo(-waveWidth * 7.0f)
        wavePath_out.quadTo(-waveWidth * 7, waveHeight, -waveWidth * 6.0f)
        wavePath_out.quadTo(-waveWidth * 5, -waveHeight, -waveWidth * 4.0f)
        wavePath_out.quadTo(-waveWidth * 3, waveHeight, -waveWidth * 2.0f)
        wavePath_out.quadTo(-waveWidth, -waveHeight, 0f.0f)
        wavePath_out.quadTo(waveWidth, waveHeight, waveWidth * 2.0f)
        wavePath_out.quadTo(waveWidth * 3, -waveHeight, waveWidth * 4.0f)
        wavePath_out.quadTo(waveWidth * 5, waveHeight, waveWidth * 6.0f)
        wavePath_out.quadTo(waveWidth * 7, -waveHeight, waveWidth * 8.0f)
        wavePath_out.lineTo(waveWidth * 8, -waveWidth * 6)
        canvas.drawPath(wavePath_out, getPaint(Paint.Style.FILL))


    }

    private fun getPaintIn(style: Paint.Style): Paint {
        val gPaint = Paint()
        gPaint.color = Color.BLUE
        gPaint.strokeWidth = 2f
        gPaint.isAntiAlias = true
        gPaint.style = style
        gPaint.textSize = 26f
        gPaint.color = Color.argb(255.75.151.79)
        var linearGradient = LinearGradient(
            waveWidth * 4, -waveWidth * 8,
            waveWidth * 4.80f,
            Color.argb(255.47.26.253),
            Color.argb(255.24.220.253),

            Shader.TileMode.CLAMP
        )
        gPaint.shader=linearGradient
        return gPaint
    }


    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            ACTION_DOWN,
            ACTION_MOVE -> {
                animal.start()
                // Within the range near the control point, move
                Log.e(
                    "x="."onTouchEvent: (x,y)" + (event.x - width / 2).toInt() + ":" + (-(event.y - height / 2)).toInt()
                )
                moveX = event.x - width / 2
                moveY = -(event.y - height / 2)
                invalidate()

            }
        }
        return true
    }




    private fun drawTextXAndY(canvas: Canvas) {

        val gPaint = Paint()
        gPaint.color = Color.BLUE
        gPaint.strokeWidth = 2f
        gPaint.isAntiAlias = true
        gPaint.style = Paint.Style.STROKE
        gPaint.textSize = 26f
        gPaint.color = Color.argb(255.75.151.79)
        canvas.scale(-1f.1f)


        canvas.save()

        canvas.scale(1f, -1f)

        // X-axis square text
        for (index in 1 until wCount / 2) {
            val rectText = Rect()
            canvas.translate(160f.0f)
            gPaint.getTextBounds(
                (80 * index * 2).toString(),
                0,
                (80 * index * 2).toString().length,
                rectText
            )
            canvas.drawText(
                (80 * index * 2).toString(),
                -(rectText.width() / 2).toFloat(), rectText.height().toFloat() * 2f, gPaint
            )
        }
        canvas.restore()
        canvas.save()
        // Draw text in the negative direction of the x axis
        canvas.scale(1f, -1f)
        for (index in 1 until wCount / 2) {
            val rectText = Rect()
            canvas.translate(-160f.0f)
            gPaint.getTextBounds(
                "-The ${(80 * index * 2)}".0,
                (80 * index * 2).toString().length,
                rectText
            )
            canvas.drawText(
                "-The ${(80 * index * 2)}",
                -(rectText.width() / 2).toFloat(), rectText.height().toFloat() * 2f, gPaint
            )
        }
        canvas.restore()

        canvas.save()
        // Draw text in the negative direction of the x axis
        canvas.scale(1f, -1f)
        canvas.translate(20f.0f)
        // Negative y direction
        for (index in 1 until hCount / 2) {
            val rectText = Rect()
            canvas.translate(0f.160f)
            gPaint.getTextBounds(
                "-The ${(80 * index * 2)}".0,
                (80 * index * 2).toString().length,
                rectText
            )
            canvas.drawText(
                "-The ${(80 * index * 2)}".0f, rectText.height().toFloat(), gPaint
            )
        }
        canvas.restore()

        canvas.save()
        canvas.scale(1f.1f)
        canvas.translate(20f.0f)
        // Positive y direction
        for (index in 1 until hCount / 2) {
            val rectText = Rect()
            canvas.translate(0f.160f)
            canvas.save()
            canvas.scale(1f, -1f)
            gPaint.getTextBounds(
                "The ${(80 * index * 2)}".0,
                (80 * index * 2).toString().length,
                rectText
            )
            canvas.drawText(
                "The ${(80 * index * 2)}".0f, rectText.height().toFloat(), gPaint
            )
            canvas.restore()
        }

        canvas.restore()


    }

    private fun drawGridLine(canvas: Canvas) {
        // Initialize a brush
        val gPaint = Paint()
        gPaint.color = Color.BLUE
        gPaint.strokeWidth = 2f
        gPaint.isAntiAlias = true
        gPaint.style = Paint.Style.FILL
        gPaint.shader = RadialGradient(
            0f.0f,
            max(width, height) / 2f,
            Color.BLUE,
            Color.YELLOW,
            Shader.TileMode.CLAMP
        )
        // The screen width and height are known in onDraw
        val screenWidth = width
        val screenHeight = height
        // The number of wide cells
        wCount = screenWidth / gridWidth
        // The number of high cells
        hCount = screenHeight / gridWidth

        //1. Move the coordinate point to the midpoint of the screen
        canvas.translate((screenWidth / 2).toFloat(), (screenHeight / 2).toFloat())
        // The direction of the overall coordinate system changes clockwise
        //2. Change the positive direction above the y axis.
        canvas.scale(1f, -1f)
        // Draw the x and y axes
        canvas.drawLine(-screenWidth / 2f.0f, screenWidth / 2f.0f, gPaint)
        canvas.drawLine(0f, -screenHeight / 2f.0f, screenHeight / 2f, gPaint)
        gPaint.color = Color.argb(61.111.111.111)
        drawGridCode(canvas, screenWidth, gPaint, hCount, screenHeight, wCount)
        //2. Change the positive direction below the y axis.
        canvas.scale(1f, -1f)
        drawGridCode(canvas, screenWidth, gPaint, hCount, screenHeight, wCount)
        //3. Change the positive left direction of the X axis.
        canvas.scale(-1f.1f)
        drawGridCode(canvas, screenWidth, gPaint, hCount, screenHeight, wCount)
        //4. Change x to positive
        canvas.scale(1f, -1f)
        drawGridCode(canvas, screenWidth, gPaint, hCount, screenHeight, wCount)


    }

    private fun drawGridCode(
        canvas: Canvas,
        screenWidth: Int,
        gPaint: Paint,
        hCount: Int,
        screenHeight: Int,
        wCount: Int
    ) {
        // Save the snapshot of the center of the screen into the stack. Convenient for later operation.
        canvas.save()
        // draw a horizontal line starting at the point (0,0)
        //canvas.drawLine(0f, 0f, (screenWidth / 2).toFloat(), 0f, gPaint)

        //3. Draw a line parallel to the X-axis that completes the first quadrant
        for (index in 0 until hCount) {
            // The coordinate system dot is continuously shifted upwards to the height of gridWidth
            canvas.translate(0f, gridWidth.toFloat())
            // Draw a straight line at the translated dot
            canvas.drawLine(0f.0f, (screenWidth / 2).toFloat(), 0f, gPaint)
        }
        // Restore to the snapshot state. The dot is in the center
        canvas.restore()
        canvas.save()
        //4. Draw parallel to y axis
        //canvas.drawLine(0f, 0f, 0f, screenHeight / 2f, gPaint)
        for (index in 0 until wCount) {
            // The coordinate system dot is continuously shifted upwards to the height of gridWidth
            canvas.translate(gridWidth.toFloat(), 0f)
            // Draw a straight line at the translated dot
            canvas.drawLine(0f.0f.0f, screenHeight / 2f, gPaint)
        }
        // Restore to the snapshot state. The dot is in the center
        canvas.restore()
    }
    private fun getPaintBefor(style: Paint.Style): Paint {
        val gPaint = Paint()
        gPaint.color = Color.BLUE
        gPaint.strokeWidth = 2f
        gPaint.isAntiAlias = true
        gPaint.style = style
        gPaint.textSize = 26f
        gPaint.color = Color.argb(255.75.151.79)
        var linearGradient = LinearGradient(
            waveWidth * 4, -waveWidth * 8,
            waveWidth * 4.80f,
            Color.argb(155.27.134.244),
            Color.argb(195.24.220.253),
            Shader.TileMode.CLAMP
        )
        gPaint.shader=linearGradient
        return gPaint
    }
    private fun getPaint(style: Paint.Style): Paint {
        val gPaint = Paint()
        gPaint.color = Color.BLUE
        gPaint.strokeWidth = 2f
        gPaint.isAntiAlias = true
        gPaint.style = style
        gPaint.textSize = 26f
        gPaint.color = Color.argb(255.75.151.79)
        var linearGradient = LinearGradient(
            waveWidth * 4, -waveWidth * 2,
            waveWidth * 4.80f,
            Color.argb(255.27.134.244),
            Color.argb(255.24.220.253),
            Shader.TileMode.CLAMP
        )
        gPaint.shader=linearGradient
        return gPaint
    }

}



Copy the code

4. Curve charts

Charts are the most common and simplest,No more than draw API,Simple math,gesturesCan. Next, we will draw a common curve chart in the market.

1. Transform the coordinates

First we transform the left side to our usual coordinate system: leave the left and bottom margin of the text out. I’m going to do the following transformation and if you don’t understand it just look at the coordinate system transformation that we showed you in the last video on polylines.

Scale (1f, -1f) // Coordinate shift leftWidth to the right, flatten down height-bootomheight, where upward is positive. So translation down is negative canvas. Translate (leftWidth, -(bottom-height))Copy the code
// Create a new class LHC_curve_View
class LHC_curve_View @JvmOverloads constructor( context: Context? , attrs: AttributeSet? =null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    // The distance to the left
    val leftWidth = 180f
    // The height from the bottom
    val bottomHeight = 240f
    // Width of x axis
    var x_scaleWidth=0f
    // The width and height of the square
    private var grid_width=0f
    init{}override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //2
        translateCanvas(canvas)
    }

    // Pan the canvas
    private fun translateCanvas(canvas: Canvas) {
        // The y direction is negative
        canvas.scale(1f, -1f)
        // Frame shift leftWidth to the right, flatten down height-bootomheight, where up is positive. So the shift down is negative
        canvas.translate(leftWidth, -(height - bottomHeight))
     }
    
}

Copy the code

2. Draw lines parallel to the X-axis

So let’s draw lines parallel to the X-axis, we’ve switched coordinates up here and now let’s draw lines parallel to the X-axis

// Draw lines parallel to the x axis
    private fun drawLine(canvas: Canvas) {
        //canvas.drawColor(Color.argb(22, 255, 255, 111))
        val line_paint = Paint()
        line_paint.strokeWidth = 2f
        line_paint.style = Paint.Style.STROKE
        line_paint.color = Color.argb(100.188.188.188)

        x_scaleWidth = (width - leftWidth - 80f)
        grid_width = x_scaleWidth / 6
        // Draw the bottom line
        val x_path = Path()
        x_path.moveTo(0f.0f)
        x_path.lineTo(x_scaleWidth, 0f)
        canvas.drawPath(x_path, line_paint)

        canvas.save()
        // Draw the remaining parallel x axes by translating the canvas
        (0 until 3).forEach { index ->
            canvas.translate(0f, grid_width-40f)
            canvas.drawPath(x_path, line_paint)
        }
        canvas.restore()

    }
Copy the code

The effect is as follows:

3. Draw text

  • The text below the X axis is nothing more than the measurement of the text and the transformation of the canvas.
1.Paint's getTextBounds are measured in conjunction with Rect 2. Use the canvas save and restore to transform the canvas at willCopy the code
 // The text below the horizontal x axis
    private fun drawDownOfXLineText(canvas: Canvas) {
        val text_paint = Paint()
        text_paint.strokeWidth = 2f
        text_paint.style = Paint.Style.STROKE
        text_paint.color = Color.argb(100.111.111.111)
        text_paint.textSize=24f

        val rectText=Rect()
        canvas.save()
        // Rotate the text so that the coordinate system y downward is positive
        canvas.scale(1f, -1f)
        (0 until 7).forEach { index ->
            if(index>0) {
                canvas.translate(grid_width, 0f)}val strTx= "11.The ${11+index}"
            text_paint.getTextBounds(strTx,0,strTx.length,rectText)
            canvas.drawText(strTx, -rectText.width().toFloat()/2, rectText.height().toFloat()*2.5 f,text_paint)
        }
        canvas.restore()

    }
    
    
     private fun drawLeftOfYLineText(canvas: Canvas) {
        val text_paint = Paint()
        text_paint.strokeWidth = 2f
        text_paint.style = Paint.Style.STROKE
        text_paint.color = Color.argb(100.111.111.111)
        text_paint.textSize=24f

        val rectText=Rect()
        canvas.save()
        // Rotate the text so that the coordinate system y downward is positive
        (0 until 4).forEach { index ->
            if(index>0) {
                canvas.translate(0f, grid_width-40f)}var strTx=""
            if(index==0){
                strTx="${index}"
            }else if(index==1){
                strTx="The ${500}"
            }else if(index==2){
                strTx="1k"
            }else{
                strTx="1.5 k"
            }

            canvas.save()
            canvas.scale(1f, -1f)
            text_paint.getTextBounds(strTx,0,strTx.length,rectText)
            canvas.drawText(strTx, -rectText.width().toFloat()-42f, rectText.height().toFloat()/2,text_paint)
            canvas.restore()
        }
        canvas.restore()

    }

Copy the code

The effect

3. Curve drawing

  • Second order curve drawing, we will find the lack of control points. Now let’s calculate the control points

The control points controX=(x1x_1X1 + X2X_2X2)/2 and controY=(y1y_1Y1 + Y2y_2y2)/2. Now let’s plot the curve. X-axis grid_width=1 day time. It’s not hard to figure out x for the intermediate control point. Y = grid_width – 40 500 f.

The code is as follows:

    private fun drawCaves(canvas: Canvas) {
        val text_paint = Paint()
        text_paint.strokeWidth = 2f
        text_paint.style = Paint.Style.STROKE
        text_paint.color = Color.argb(100.111.111.111)

       val caves_path=Path()
        //500=grid_width-40 Each unit of length = pixel length
       val danweiY=(grid_width-40) /500
       val danweiX=(grid_width)
       for (index in 0 until dataList.size-1) {if (dataList[index]==dataList[index+1]){
               caves_path.quadTo(
                   (grid_width * index + grid_width * (1 + index)) / 2.0f,
                   grid_width * (index + 1),
                   (dataList[index + 1].toFloat()) * danweiY
               )
           }else {
               caves_path.quadTo(
                   (grid_width * index + grid_width * (1 + index)) / 2,
                   (dataList[index].toFloat() * danweiY + dataList[index + 1].toFloat() * danweiY) / 2 +100,
                   grid_width * (index + 1),
                   (dataList[index + 1].toFloat()) * danweiY
               )
           }
       }
       canvas.drawCircle(0f.0f.10f,text_paint)
       canvas.drawPath(caves_path,text_paint)
    }

Copy the code

But we’ll see that there are no radians at either focus. It’s hard, and then we go on to analyze and solve it to perfection. As for the end result, we can be even cooler.

4. The rescue of the third-order curve

When y1y_1y1< y2y_2y2, as shown in the figure 1 above, find the coordinates of the midpoint: control point x+40px in the lower part of the X-axis and x-40px in the upper part, and adjust the Y-axis to make the smoothness of control point Y-40x in the lower part and y+40 in the upper part. 1. Obtain the coordinates of the midpoint (X_ in X, Y_ in Y) =((x1x_1x1+ x2x_2X2)/2, (y1y_1y1+ y2y_2y2)/2) 2. The coordinates between x1x_1x1 and X_ in X =(x1x_1x1+ y2y_2y2)/2 The coordinates between x_ in x and X2X_2X2 =((x_ in x + x_ in y + y_ in y)/2, (y_ in y + y2y_2y2)/2) when y1y_1y1> Y2y_2y2 is shown in figure 2. Figure out the coordinates of the midpoint: the upper part of the X-axis +40px, the lower part x-40px, the Y-axis can also be adjusted, and the Y-axis can also be adjusted to make the smoothness of the upper part of the control point Y +40x, the lower part y-40. 1. Obtain the coordinates of the midpoint (X_ in X, Y_ in Y) =((x1x_1x1+ x2x_2X2)/2, (y1y_1y1+ y2y_2y2)/2) 2. The coordinates between x1x_1x1 and X_ in X =(x1x_1x1+ y2y_2y2)/2 The coordinates between x_ in x and X2X_2X2 =(x_ in x + x_ in y + y_ in y)/2, (y_ in y + y2y2)/2)

Next we heap code:

   // Draw a curve
    private fun drawCaves(canvas: Canvas) {
        val text_paint = Paint()
        text_paint.strokeWidth = 2f
        text_paint.style = Paint.Style.STROKE
        text_paint.color = Color.argb(100.111.111.111)

        val caves_path = Path()
        //500=grid_width-40 Each unit of length = pixel length
        val danweiY = (grid_width - 40) / 500
        val danweiX = (grid_width)
        for (index in 0 until dataList.size - 1) {
            // The display of the second-order curve is awkward
            // if (dataList[index]==dataList[index+1]){
            // caves_path.quadTo(
            // (grid_width * index + grid_width * (1 + index)) / 2,
            // 0f,
            // grid_width * (index + 1),
            // (dataList[index + 1].toFloat()) * danweiY
            / /)
            // }else {
            // caves_path.quadTo(
            // (grid_width * index + grid_width * (1 + index)) / 2,
            // (dataList[index].toFloat() * danweiY + dataList[index + 1].toFloat() * danweiY) / 2 +100,
            // grid_width * (index + 1),
            // (dataList[index + 1].toFloat()) * danweiY
            / /)
            / /}
            // Third order curve to display
            val xMoveDistance=40
            val yMoveDistance=40

            if (dataList[index] == dataList[index + 1]) {
                caves_path.lineTo(danweiX*(index+1),0f)}else if(dataList[index] < dataList[index + 1]) {/ / y1 < y2
                val centerX=(grid_width * index + grid_width * (1 + index)) / 2
                val centerY=(dataList[index].toFloat() * danweiY + dataList[index + 1].toFloat() * danweiY) / 2
                val controX0=(grid_width * index+centerX)/2
                val controY0=(dataList[index].toFloat() * danweiY+centerY)/2
                val controX1=(centerX+ grid_width * (1 + index))/2
                val controY1=(centerY+dataList[index+1].toFloat() * danweiY)/2caves_path.cubicTo(controX0+xMoveDistance,controY0-yMoveDistance,controX1-xMoveDistance,controY1+yMoveDistance,grid_widt h * (1 + index),dataList[index + 1].toFloat() * danweiY)
            }else{
                val centerX=(grid_width * index + grid_width * (1 + index)) / 2
                val centerY=(dataList[index].toFloat() * danweiY + dataList[index + 1].toFloat() * danweiY) / 2
                val controX0=(grid_width * index+centerX)/2
                val controY0=(dataList[index].toFloat() * danweiY+centerY)/2
                val controX1=(centerX+ grid_width * (1 + index))/2
                val controY1=(centerY+dataList[index+1].toFloat() * danweiY)/2caves_path.cubicTo(controX0+xMoveDistance,controY0+yMoveDistance,controX1-xMoveDistance,controY1-yMoveDistance,grid_widt h * (1 + index),dataList[index + 1].toFloat() * danweiY)

            }
        }
        canvas.drawCircle(0f.0f.10f, text_paint)
        canvas.drawPath(caves_path, text_paint)
    }
Copy the code

Let’s see what happens

  • Is there a feeling, no more than the control point for adjustment, we see other people’s more sharp, we forxMoveDistance.yMoveDistanceTo adjust

val xMoveDistance=20

val yMoveDistance=40

So far our biggest difficulty is simply solved, right? Nothing more than simple addition, subtraction, multiplication and division, right? Next, we are going to decorate beautiful curves. After learning from the previous article, I think we are all familiar with these skills, right? Gradient fill, animation, click, gesture, texture.

6. Beautify curves

  • I remember said to want to draw more SAO than it, I think we should be able to do, much and SAO not necessarily beautiful, but everywhere is technology, that comes down us in much and SAO aspect proceed with, as to the result we dare not say more beautiful than UI design right.

1. Embellish your skillsThe gradient

  • We noticed a gradual fading of the design from top to bottom. Let’s choose a linear gradient from 0,y to 0,0. Heart no number of traversal for maximum. My data is written down for convenience.
 val linearGradient = LinearGradient(
            0f.1500 * danweiY,
            0f.0f,
            Color.argb(255.229.160.144),
            Color.argb(255.251.244.240),
            Shader.TileMode.CLAMP
        )
        text_paint.shader = linearGradient

Copy the code

2. Embellish your skillsSurrounded by the Path

  • We notice that the UI has a red line around it. So let’s just take the curve and set the brush. I need to add a circle to each vertex.
         // Draw a closed gradient curve
        canvas.drawPath(caves_path, text_paint)
        val line_paint = Paint()
        line_paint.strokeWidth = 3f
        line_paint.style = Paint.Style.STROKE
        line_paint.color = Color.argb(255.212.100.77)
        // Draw the outer ring red line
        canvas.drawPath(caves_path, line_paint)
        line_paint.style = Paint.Style.FILL
        / / circle.
        for (index in 0 until dataList.size ) {
           canvas.drawCircle(grid_width*index,danweiY*dataList[index],6f,line_paint)
        }

Copy the code

Look at the results:Rendering text

 // Draw the first and last 7 days text button
    private fun drawTextButton(canvas: Canvas) {
        val line_paint = Paint()
        line_paint.strokeWidth = 2f
        line_paint.style = Paint.Style.STROKE
        line_paint.color = Color.argb(188.76.126.245)
        line_paint.textSize=38f
        val buttonPath = Path()
        buttonPath.addRoundRect(100f, -120f.320f, -200f.80f.80f, Path.Direction.CCW)
        canvas.drawPath(buttonPath,line_paint)
        canvas.save()
        canvas.scale(1f, -1f)
        canvas.drawText("The first seven days".140f.175f,line_paint)
        canvas.restore()

        canvas.save()
        canvas.translate(260f.0f)
        canvas.drawPath(buttonPath,line_paint)
        canvas.scale(1f, -1f)
        canvas.drawText("The last seven days".140f.175f,line_paint)

        canvas.restore()

    }

Copy the code

The effect is as follows:

3. Embellish your skillsmap

Stickers have been used in blogs before, but I won’t go into them here. Add a clipping and you’re done.

  // Draw the avatar at the top
    private fun drawHeaderToCanvas(canvas: Canvas) {
        val bitmap_paint = Paint()
        bitmap_paint.strokeWidth = 2f
        bitmap_paint.style = Paint.Style.STROKE
        bitmap_paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
        bitmap_paint.isAntiAlias =true
        canvas.save()
        val srcRect1=Rect(0.0.80.80)
        val dstRect1=Rect(0.0.40.40)
        val danweiY = (grid_width - 40) / 500
        for (index in 0 until dataList.size) {
            val mdrawable = ContextCompat.getDrawable(context, imgList[index])
            val bitmap = getBitmap(bitmap_paint, mdrawable!!)
            canvas.save()
            canvas.translate(
                grid_width * index - bitmap.width / 4,
                danweiY * dataList[index] + 20
            )
            // Here draw the image onto the canvas
            val circlePath = Path()
            circlePath.addCircle(20f.20f.20f, Path.Direction.CCW)
            canvas.clipPath(circlePath)
            canvas.drawBitmap(bitmap, srcRect1, dstRect1, bitmap_paint)
            canvas.restore()
        }
        canvas.restore()


    }
Copy the code

I think I’ve reached the UI design at this point. We added an extra picture. I won’t bore you with something as simple as words.

4. Ocd

In order to achieve the same effect, their obsessive-compulsive disorder no way to draw the final effect. Those dead words and distances are not the quality of a professional developer. I hope you measure and calculate as you write. I’m writing dead data here for speed. Don’t imitate.

 private fun drawTopTextToCanvas(canvas: Canvas) {
        val text_paint = Paint()
        text_paint.strokeWidth = 2f
        text_paint.style = Paint.Style.FILL
        text_paint.color = Color.argb(255.0.0.0)
        text_paint.textSize =66f
        val rectText = Rect()
        val rectTextYuan = Rect()

        canvas.save()
        canvas.scale(1f, -1f)
        canvas.translate((width/2).toFloat()-100, -500f)
        val text="1347"
        val textyu="Yuan"

        text_paint.getTextBounds(text, 0,text.length, rectText)

        canvas.drawText(
            text,
            -rectText.width().toFloat() - 42f,
            rectText.height().toFloat() / 2,
            text_paint
        )
        text_paint.color = Color.argb(111.111.111.111)
        text_paint.getTextBounds(textyu, 0,textyu.length, rectTextYuan)
        text_paint.textSize =33f
        canvas.drawText(
            textyu,
             80+ -rectTextYuan.width().toFloat() - 42f,
            rectTextYuan.height().toFloat() / 2,
            text_paint
        )

        canvas.translate(0f.50f)
        canvas.drawText(
            "From the day before yesterday",
            -rectTextYuan.width().toFloat() - 180f,
            rectTextYuan.height().toFloat() / 2,
            text_paint
        )
        canvas.translate(100f.0f)
        text_paint.color = Color.argb(255.223.129.120)
        canvas.drawText(
            "+ 971.99 (251.19%)",
            -rectTextYuan.width().toFloat() - 180f,
            rectTextYuan.height().toFloat() / 2,
            text_paint
        )
        canvas.translate(-100f.50f)
        text_paint.color = Color.argb(111.111.111.111)
        canvas.drawText(
            "The highest prize is awarded for the part of the dotted line.",
            -rectTextYuan.width().toFloat() - 180f,
            rectTextYuan.height().toFloat() / 2,
            text_paint
        )
        // I haven't found a way to draw rich text on canvas yet. We can only measure and draw the text one by one. Don't learn from me, good measurement measurement to improve their primary school calculations.

// val textSpanned1 = SpannableString("Hello World");
// textSpanned1.setSpan(ForegroundColorSpan(Color.RED), 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
// text_paint.reset()
// text_paint.textSize=44f
//        canvas.drawText(textSpanned1,0,10,0f,0f,text_paint)
        canvas.restore()
    }
Copy the code

7. Embellish your skillsgesturesaddanimation

If you don’t understand gestures, sliding, zooming, and animating, take a look at the previous articles.

Curve – the game

I’ll write it tomorrow sleep