I have sorted out some basic operations of property animation before, and I have passed all the requirements related to animation for this period of time. Until this time…

One, another animation requirement

Animations in most interactions animate individual page elements, which is a good fit for property animations. But for multiple elements, non-page elements of the animation requirements, it is not convenient to use View+ property animation to achieve.

Here’s an example, which I did this time:

The ripple effect requires multiple concentric circles to be drawn simultaneously, and these circles are not elements in the page and do not need to be displayed until triggered. If you use property animation, you need to add at least one ImageView drawing circle to the XML layout file, which is inefficient and difficult to reuse.

WaveView analysis

The main idea for animating a custom View is as follows:

  1. Decompose animation into frames, consider how inonDrawDraw each frame in
  2. Parameters required for drawing are extracted, which can be divided into time-varying and immutable. Immutable parameters can be exposed (setter method/attribute setting)
  3. Summarize the rule of parameter change with time and realize the time axis
  4. Provide play, pause, stop, reset and other methods as required

Let’s implement WaveView step by step.

Step 1: Draw a circle in onDraw using the Canvas. drawCircle method with four parameters: the center x and y coordinates, the radius, and Paint. WaveView draws each frame as a circle, the difference is the radius, number and transparency of the circle. You also need to set the minimum and maximum radius of the circle, and the radius difference between the two circles as they spread out.

Each time you draw, you just draw all the circles:

Step 2: The only parameters that change with time during diffusion are radius and transparency. The number of circles, the minimum maximum radius, and the radius difference are immutable parameters. All have default values to prevent

Step 3: The hardest step, the timeline. We need a time-triggered mechanism to change every value in mWaveList and also change the alpha value of paint. For this variable, less is more convenient. The effect of ripple is that the larger the radius, the smaller the alpha. Alpha can be calculated by controlling the radius change. Then there is the problem of circulation. When the ripple radius is larger than the maximum radius, the ripple disappears, and then it can be recycled and spread again from the minimum radius.

My timeline here uses CountDownTimer, which is probably not a good choice, but I’m used to writing it easily…

Step 4: This last one is easy, directly control the start and cancel of CountDownTimer.

Three, perfect it again?

Internal function has been realized, but also extract the parameter setting method, convenient for other places to use ah. In Java, you need to add setter/getter methods, which can be generated using template code. Kotlin said to expose the field to public.

If you need to set the default parameters in the XML layout file, you need to add the corresponding attribute, which is sufficient for now, so I will not add 🤣

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.CountDownTimer
import android.util.AttributeSet
import android.view.View


class WaveView@JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {


    private val paint:Paint = Paint()
    public var mBgColor = Color.TRANSPARENT
    public var mWaveColor = Color.WHITE

    public var mRadiusMinIn the 80f public var mRadiusMax = 1080F public var mWaveInterval = 80f public var speed = 10F private Val mWaveList = ArrayList<Float>() private val timeline = object:CountDownTimer(300000,16){override fun OnTick (zipuntilfinished: Long) {// Every time increases the radius of the diffusing ripplefor (i in 0 until mWaveList.size){
                mWaveList[i] = mWaveList[i] + speed
                if (mWaveList[i] < mRadiusMin + mWaveInterval){
                    break}} // When the outermost ripple exceeds its maximum value, it is added to the end of the ripple queueif (mWaveList[0] > mRadiusMax){
                mWaveList[0] = mRadiusMin
                val newList = transList(mWaveList)
                mWaveList.clear()
                mWaveList.addAll(newList)
            }

            invalidate()
        }

        override fun onFinish() {// Try to call waveStop manually, Reset ()}} init {initWave(mWaveList) paint.color = mWaveColor} Override fun onDraw(canvas: canvas?) { super.onDraw(canvas) canvas!! .drawColor(mBgColor) val centerX = canvas.width.div(2).toFloat() val centerY = canvas.height.div(2).toFloat()for (i in 0 until mWaveList.size){
            paint.alpha = calcAlpha(mWaveList[i])
            canvas.drawCircle(centerX, centerY, mWaveList[i], paint)
        }
    }

    fun wave(){
        timeline.start()
    }

    fun stopWave(){
        timeline.cancel()
    }

    fun reset(){
        timeline.cancel()
        mWaveList.clear()
        initWave(mWaveList)
    }

    private fun initWave(waveList: ArrayList<Float>){
        val waveNum = ((mRadiusMax-mRadiusMin)/mWaveInterval).toInt() + 2
        for (i in1.. waveNum){ waveList.add(mRadiusMin)
        }
    }

    private fun transList(list: ArrayList<Float>):ArrayList<Float>{
        val newList = ArrayList<Float>()

        (1 until list.size).mapTo(newList) { list[it] }
        newList.add(list[0])
        returnNewList} // Calculate transparency by radius, the trend is that the larger the radius, the more transparent, Private fun calcAlpha(r:Float):Int = ((mRadiusMax -r)/ mRadiusMax * 120).toint ()} private fun calcAlpha(r:Float):Int = (mRadiusMax -r)/ mRadiusMax * 120).toint ()}Copy the code

The resulting View looks something like this:

  1. Put a WaveView in XML
  2. After findViewById, set the basic parameters
  3. Execute when neededwave()methods

Four, afterword.

This should be able to meet the needs of designers dalao, hoo ~

Custom View is a very wide range of topics, in addition to the complete implementation of a display, interactive control, but also to optimize the layout of the drawing efficiency, to achieve the interaction of the mystery, as well as this special animation effect, there are a lot of in-depth learning content ah.

Recently, I am working on Kotlin, and I plan to put it into practice, so I always use Kotlin when WRITING Demo. But the current projects are all Java, so we translated a version of Java. See Github for the full code.

To making

If you have any questions or suggestions, please leave a comment.