Kotlin wave animation

Time:2021-5-9

Learn kotlin and rearrange some of the previous knowledge

First of all, for the operation that needs a lot of drawing, you can’t draw directly   Surfaceview can directly put the drawing work into the sub thread for operation. Otherwise, the drawing work will get stuck when it is increased. However, surfaceview is an independent layer of view, and can’t pan, zoom, rotate or set transparency. If you need these operations, you can consider using TextureView, both of which can update the UI in the sub thread through callback

Thinking:

Use two-level Bezier curve to complete, simply half the screen, and then use Bezier to draw a fan, copy a copy outside the screen, change the control point, to achieve the effect of circular movement

Compared with before:

Simplify the calculation logic, directly cycle the calculation of control points, reuse the thread pause and resume operation, use the interpolator to replace the direct calculation of moving coordinates (for different scenes, different interpolators can be replaced), and extract the drawing logic

technological process:

1. From complexity to simplification, first of all, we don’t consider the others. The first level Bessel is a straight line with two control points. The second level Bessel has one more control point. The shape is drawn by using formula algorithm through the control points. The following is a direct Bessel Soha

 

 

 

Suppose that a quarter of the starting point (0,0) screen is the second point, and a half of the screen is the third point. Then add a second level Bessel, and the sector will be formed

path.reset()path.moveTo(starPoint[0], starPoint[1])
path.quadTo(quadPoint[0], quadPoint[1], quadPoint[2], quadPoint[3])
canvas.drawPath(path, paint)

2. Draw the second sector, the same sector, but in the opposite direction (change the control point), path will take the first connection point as the last control point of the first sector as the starting point of the second sector, the second coordinate is three quarters of the screen, the third coordinate is the width of the screen, and then connect to form two connected sectors, which do not look very similar, There are only lines

path.reset()
path.moveTo(starPoint[0], starPoint[1])
path.quadTo(quad1Point[0], quad1Point[1], quad1Point[2], quad1Point[3])
path.quadTo(quad2Point[0], quad2Point[1], quad2Point[2], quad2Point[3])
canvas.drawPath(path, paint)

3., as like as two peas, the brush is set to fill. This looks a bit like a picture. If you can make the wave move, the basic effect will be changed. The position of the X coordinate can be changed. But after two waves, it is obviously not yet able to achieve the desired effect. At this time, two identical fans are needed.

Cycle switching

paint.style = Paint.Style.FILL

//Closed area
path.lineTo(mWidth, mHeight)
path.lineTo(0f, mHeight)
path.close()

4. Draw the moving waves. Suppose there is a screen on the left, which is also divided into four equal parts, and so on, so that the waves are drawn

5. Change the X coordinate of all waves, let it move from left to right, to the critical point set to 0, repeat the operation, here you can give the interpolator to do, move directly from 0, the critical point is the width of the screen, just the distance between two waves

It’s OK not to use an interpolator. You can calculate it by yourself. You can see that it’s moving

In order to better see the effect, I draw a small dot for each X coordinate control point, which makes it more intuitive and helps to debug some bugs. I can also print out the coordinates to facilitate calculation, and then simplify the logic. Above, I directly draw the coordinates of each point one by one, However, since the effect of wireless wave has not been achieved, it is actually a copy of the same wave on two screens, so you can use a cycle to dynamically change its coordinates from left to right to draw the same fan (I * width) on two screens

 

Then you can see the coordinate information

The y-coordinate basically doesn’t need to be looked at. The y-coordinate mainly controls the radian of the sector. Because it is two opposite sectors, one is high and the other is low. Then the x-coordinate is divided into four equal parts according to the width of the screen, and connected with the Bezier curve. It’s much clearer to look at

Because the dots are drawn, it looks rather stiff visually. If the dots are removed, it will be much smoother

The preliminary idea here is completed. If you want to enrich it, you can also add some operations, such as dynamically changing the height of the control point, making the waves move up and down at the same time, or drawing more waves

Kotlin wave animationKotlin wave animation

class DrawWaterUtils(private val holder: SurfaceHolder) : Thread() {

    private var mWidth: Float = 0f
    private var mHeight: Float = 0f

    //Wave height
    private var mWaterHeight: Float = 0f

    //The height of undulation
    private var mWaterUp: Float = 0f

    /**
     *Off = offset value
     */
    private var offx: Float = 0f

    private var path: Path = Path()
    private var paint: Paint = Paint()
    private var isRun: Boolean = false

    private var circlePaint: Paint = Paint()

    private lateinit var mValueAnimator: ValueAnimator

    init {
        //Remove brush serration
        paint.isAntiAlias = true
        //Set the style to solid line
        paint.style = Paint.Style.FILL
        paint.strokeWidth = 2f

        circlePaint.style = Paint.Style.FILL
    }

    fun init(width: Int, height: Int) {
        mWidth = width.toFloat()
        mHeight = height.toFloat()
        //Linear differentiator
        mValueAnimator = ValueAnimator.ofFloat(0f, mWidth)
        mValueAnimator.duration = 1700
        mValueAnimator.interpolator = LinearInterpolator()
        mValueAnimator.repeatCount = ValueAnimator.INFINITE
        mValueAnimator.addUpdateListener { animation ->
            offx = animation.animatedValue as Float
        }
        //Wave height
        mWaterHeight = mHeight / 2
        //The height of undulation
        mWaterUp = mWaterHeight / 2
    }
    /**Start thread*/
    fun runDraw() {
        isRun = true
        start()
        mValueAnimator.start()
    }
    /**Restore thread*/
    fun resumeThread() {
        synchronized(this) {
            isRun = true
            notify()
            mValueAnimator.start()
        }
    }
    /**Pause thread*/
    fun stopDraw() {
        isRun = false
        mValueAnimator.cancel()
    }

    override fun run() {
        synchronized(this) {
            while (true) {
                if (!isRun) {
                    wait()
                }
                val canvas = holder.lockCanvas()
                if (canvas != null) {
                    //Clear canvas
                    canvas.drawColor(
                        KtxProvider.mContext.getColor(R.color.text_color_titleBar_title),
                        android.graphics.PorterDuff.Mode.CLEAR
                    )
                    paint.color = KtxProvider.mContext.getColor(R.color.bg_color_3159c7_83)
                    circlePaint.color = KtxProvider.mContext.getColor(R.color.black)
                    water(canvas)
                    //Cyclic fluctuation
//            offx += 3
//            if (offx >= mWidth) {
//                offx = 0f
//            }
                    //Unlock and submit changes
                    holder.unlockCanvasAndPost(canvas)
                }
            }
        }
    }
    /**Draw waves*/
    private fun water(canvas: Canvas) {
        path.reset()
        //Starting point
        path.moveTo(-mWidth + offx, mWaterHeight)
        //Number of waves
        for (i in 0 until 2) {
            path.quadTo(
                -mWidth * 3 / 4 + i * mWidth + offx,
                mWaterHeight - mWaterUp,
                -mWidth / 2 + i * mWidth + offx,
                mWaterHeight
            )
            //canvas.drawCircle(-mWidth * 3 / 4 + i * mWidth + offx, mWaterHeight - mWaterUp,5f,circlePaint)
            //canvas.drawCircle(-mWidth / 2 + i * mWidth + offx, mWaterHeight,5f,circlePaint)
            path.quadTo(
                -mWidth / 4 + i * mWidth + offx,
                mWaterHeight + mWaterUp,
                i * mWidth + offx,
                mWaterHeight
            )
            //canvas.drawCircle(-mWidth / 4 + i * mWidth + offx, mWaterHeight + mWaterUp,5f,circlePaint)
            //canvas.drawCircle(i * mWidth + offx, mWaterHeight,5f,circlePaint)
            Log.e(
                "===\n", "i = $i\n" +
                        "x1 ${-mWidth * 3 / 4 + i * mWidth} y1 ${mWaterHeight - mWaterUp} x2 ${-mWidth / 2 + i * mWidth} y2 $mWaterHeight \n" +
                        "x1 ${-mWidth / 4 + i * mWidth} y1 ${mWaterHeight + mWaterUp} x2 ${i * mWidth} y2 $mWaterHeight"
            )
        }
        //Closing operation
        path.lineTo(mWidth, mHeight)
        path.lineTo(0f, mHeight)
        path.close()
        canvas.drawPath(path, paint)
    }

}

View Code

Kotlin wave animationKotlin wave animation

/**
 * created by YooJin.
 * date: 2021/2/4 14:44
 *Desc: wave animation
 */
class WaterBgSurfaceView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) :
    SurfaceView(context, attrs, defStyleAttr), SurfaceHolder.Callback {

    init {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?):this(context, attrs, 0)

    constructor(context: Context):this(context, null)

    private lateinit var drawWater: DrawWaterUtils


    private fun init() {
        holder.addCallback(this)
        //Transparent treatment
        holder.setFormat(PixelFormat.TRANSPARENT)
        setZOrderOnTop(true)
        drawWater = DrawWaterUtils(holder)
    }

    @SuppressLint("DrawAllocation")
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        if (changed){
            drawWater.init(width,height)
        }
    }

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

    override fun surfaceCreated(p0: SurfaceHolder) {
        //Log.e("===","surfaceCreated drawWater.isAlive ${drawWater.isAlive}")
        if (!drawWater.isAlive) {
            //Log.e("===","run")
            drawWater.runDraw()
        }else{
            //Log.e("===","notify")
            drawWater.resumeThread()
        }
    }

    override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {

    }

    override fun surfaceDestroyed(p0: SurfaceHolder) {
        //Log.e("===","surfaceDestroyed")
        drawWater.stopDraw()
    }

}

View Code

Although it’s very smooth here, surfaceview also has some disadvantages, that is, it can’t set the offset, rotation and other animation transparency like the general view. If your drawing is not complicated, you can draw it directly, or you can use it   TextureView to achieve, the project also has a set of TextureView drawing class, no big difference, the following is the project address

github:https://github.com/1024477951/KotlinStrong

Recommended Today

Looking for frustration 1.0

I believe you have a basic understanding of trust in yesterday’s article. Today we will give a complete introduction to trust. Why choose rust It’s a language that gives everyone the ability to build reliable and efficient software. You can’t write unsafe code here (unsafe block is not in the scope of discussion). Most of […]