Custom View of Lenovo Mobile ZUI system loading animation

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/smile_Running/article/details/99694931

Bloggers statement:

Please reprint this article at the beginning of additional links and author information, and marked as reserved. This article by the blogger  Whiskers Meow  original audience for support and advice.

This article first appeared in this    blogger : Whiskers Meow   |   blog home page : https://blog.csdn.net/smile_running

    The Custom View is written by the effect of a Lenovo Mobile ZUI system loading animation imitation, a few days ago received a blogger updates ZUI 11's, after the discovery updated and no major improvement, but has been with the touch gesture is changed , a time not in the habit, always slip wrong, I have to say this Lenovo phone system is not very good. Bloggers also the first time to buy Lenovo Mobile, Tucao about, ha ha.

    But then, on the loading animation ZUI systems we became interested, we have used may notice that Lenovo Mobile, which is three small ball spinning around, and moved to the center, into one. Then they split into three, changed color, and has been circulating to do so. The expression is not clear, direct view renderings of it, the following are some of my phone to record a video, just to find a Bluetooth search function, there will be loading animation three balls in the search, as follows:

    See the effect of it, of course, it is the first wave of the analysis. It is the rotation of three balls, coupled to the center of the animation polymerization, performing animation with both, and then begin to diverge, and the color has changed.

    First it, we need to draw three small round, small round every three angles are the same, i.e. 120 °, we get to the small circle coordinates x, y values, need to know the radius r and center c1 these two are our own set. Look at the picture below:

    The coordinates of the point P on behalf of the small circle, viewed from above, the use of trigonometric formulas, we can easily draw the coordinate values ​​of P points. Point C is the coordinates of the center, as the center position of the screen, the value of radius r, we give a default fine. code show as below:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ZUILoadingView">
        <attr name="circle_radius" format="float" />
        <attr name="circle_distance" format="float" />
    </declare-styleable>
</resources>

    Three small round, we can use to draw cycle. Kotlin looked a little grammar, learn feeling very relaxed, very easy, this is my first use kotlin to write. Bo Lord to be used in a future article kotlin, after all, to swim, to float up, ha ha.

    private fun drawCircles(canvas: Canvas) {
        for (i in 0..2) {
            val diff = mRad * mAngle + mRad * 120f * i
            val circleX: Float = mDefDistance * Math.cos(diff).toFloat();
            val circleY: Float = mDefDistance * Math.sin(diff).toFloat();
            mPaint.color = getColors(mCurColor)[i]
            canvas.drawCircle(circleX, circleY, mDefRadius, mPaint)
        }
        startAnimator()
    }

    The above code is nothing more difficult, we can easily draw the distribution of three small round circle, their spacing is 120 °, the effect of this difficulty, which is handling the animation of the small circle of three.

    The animation effect is divided into two parts, the first is: converge to the center circle of rotation and the second is: change color after convergence, divergence, and then rotated to the surrounding. To achieve this effect, I spent a little effort. I see a problem, that is AnimatorSet no repeatable method, but the same instance ValueAnimator, we are adding to AnimatorSet in time, can not be achieved animation loop effects. As for what it means, they can go to try, I will not explained. Direct look at the animation bar code

    private fun startAnimator() {
        // 旋转动画
        if (mRotateAnimator == null) {
            mRotateAnimator = ObjectAnimator.ofFloat(0f, 360f)
            mRotateAnimator?.addUpdateListener { animation: ValueAnimator ->
                mAngle = animation.getAnimatedValue() as Float
                postInvalidate()
            }
            mRotateAnimator?.duration = 1000L
        }
        if (mRotateAnimator2 == null) {
            mRotateAnimator2 = ObjectAnimator.ofFloat(360f, 720f)
            mRotateAnimator2?.addUpdateListener { animation: ValueAnimator ->
                mAngle = animation.getAnimatedValue() as Float
                postInvalidate()
            }
            mRotateAnimator2?.duration = 1000L
        }
        // 平移动画
        if (mTranslateAnimator == null) {
            mTranslateAnimator = ObjectAnimator.ofFloat(mDefDistance, 0f)
            mTranslateAnimator?.addUpdateListener { animation: ValueAnimator ->
                mDefDistance = animation.getAnimatedValue() as Float
            }
            mTranslateAnimator?.duration = 1000L
        }
        if (mTranslateAnimator2 == null) {
            mTranslateAnimator2 = ObjectAnimator.ofFloat(0f, mDefDistance)
            mTranslateAnimator2?.addUpdateListener { animation: ValueAnimator ->
                mDefDistance = animation.getAnimatedValue() as Float
            }
            mTranslateAnimator2?.duration = 1000L
        }

        if (mAnimTogether == null) {
            mAnimTogether = AnimatorSet()
            mAnimTogether?.playTogether(mRotateAnimator, mTranslateAnimator)

        }
        if (mAnimTogether2 == null) {
            mAnimTogether2 = AnimatorSet()
            mAnimTogether2?.playTogether(mRotateAnimator2, mTranslateAnimator2)
            mAnimTogether2?.addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animation: Animator?) {
                    changeColor()
                }
            })
        }

        if (mAnimSequentially == null) {
            mAnimSequentially = AnimatorSet()
            mAnimSequentially?.playSequentially(mAnimTogether, mAnimTogether2)
            mAnimSequentially?.start()
            //动画循环
            mAnimSequentially?.addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    mAnimSequentially?.start()
                }
            })
        }
    }

    A brief look at the above code, the effect of rotation of the small circle, a circle is 360 ° change, and this is easy to understand. Then the convergence and divergence of animation, it actually changes is the distance from the center point of the small center of the circle, which is actually our given radius R, that is, following a circle_distance property. Change this value, we will be able to achieve convergence and divergence of animation.

    <nd.no.xww.learnkotlin.ZUILoadingView
        android:layout_width="200dp"
        app:circle_radius="10"
        app:circle_distance="40"
        android:layout_height="200dp"
        android:layout_centerInParent="true" />

    So the above is that we simply use activity_main in, you can control the radius of the small circle, and the center of the circle and the distance. Finally, our complete code is as follows:

package nd.no.xww.learnkotlin

import android.animation.*
import android.annotation.TargetApi
import android.content.Context
import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.Build
import android.util.AttributeSet
import android.view.View

/**
 *@desciption : 联想手机 zui 系统的加载动画
 *@author xww
 *@date 2019/8/16
 *@time 16:10
 * 博主:威威喵
 * 博客:https://blog.csdn.net/smile_Running
 */
class ZUILoadingView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : View(context, attrs, defStyleAttr) {

    private var mPaint: Paint = Paint()
    private var mDefRadius: Float = 20f
    private var mDefDistance: Float = 100f
    private val mRad = 2 * Math.PI / 360f;

    init {
        val array: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.ZUILoadingView)
        mDefRadius = array.getFloat(R.styleable.ZUILoadingView_circle_radius, mDefRadius)
        mDefDistance = array.getFloat(R.styleable.ZUILoadingView_circle_distance, mDefDistance)
        array.recycle()

        mPaint.isDither = true
        mPaint.isAntiAlias = true
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        var widthSize = MeasureSpec.getSize(widthMeasureSpec)
        if (widthMode == MeasureSpec.AT_MOST)
            widthSize = 200
        val heigthMode = MeasureSpec.getMode(widthMeasureSpec)
        var heigthSize = MeasureSpec.getSize(widthMeasureSpec)
        if (heigthMode == MeasureSpec.AT_MOST)
            widthSize = 200

        if (widthSize != heigthSize) {
            widthSize = Math.min(widthSize, heigthSize)
            heigthSize = widthSize
        }
        setMeasuredDimension(widthSize, heigthSize)
    }

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

        canvas.translate(width / 2f, height / 2f)
        drawCircles(canvas)
    }

    private val COLORS_ONE: IntArray = intArrayOf(Color.BLUE, Color.YELLOW, Color.RED)
    private val COLORS_TWO: IntArray = intArrayOf(Color.YELLOW, Color.RED, Color.BLUE)
    private val COLORS_THREE: IntArray = intArrayOf(Color.RED, Color.BLUE, Color.YELLOW)

    private var mCurColor: ColorSelected = ColorSelected.BLUE

    private enum class ColorSelected {
        BLUE, YELLOW, RED
    }

    private fun getColors(colorSelected: ColorSelected): IntArray {
        return when (colorSelected) {
            ColorSelected.BLUE -> COLORS_TWO
            ColorSelected.YELLOW -> COLORS_THREE
            ColorSelected.RED -> COLORS_ONE
            else -> COLORS_ONE
        }
    }

    private fun drawCircles(canvas: Canvas) {
        for (i in 0..2) {
            val diff = mRad * mAngle + mRad * 120f * i
            val circleX: Float = mDefDistance * Math.cos(diff).toFloat();
            val circleY: Float = mDefDistance * Math.sin(diff).toFloat();
            mPaint.color = getColors(mCurColor)[i]
            canvas.drawCircle(circleX, circleY, mDefRadius, mPaint)
        }
        startAnimator()
    }

    var mAngle: Float = 0f

    var mRotateAnimator: ValueAnimator? = null
    var mRotateAnimator2: ValueAnimator? = null

    var mTranslateAnimator: ValueAnimator? = null
    var mTranslateAnimator2: ValueAnimator? = null

    var mAnimTogether: AnimatorSet? = null
    var mAnimTogether2: AnimatorSet? = null
    var mAnimSequentially: AnimatorSet? = null

    @TargetApi(Build.VERSION_CODES.O)
    private fun startAnimator() {
        // 旋转动画
        if (mRotateAnimator == null) {
            mRotateAnimator = ObjectAnimator.ofFloat(0f, 360f)
            mRotateAnimator?.addUpdateListener { animation: ValueAnimator ->
                mAngle = animation.getAnimatedValue() as Float
                postInvalidate()
            }
            mRotateAnimator?.duration = 1000L
        }
        if (mRotateAnimator2 == null) {
            mRotateAnimator2 = ObjectAnimator.ofFloat(360f, 720f)
            mRotateAnimator2?.addUpdateListener { animation: ValueAnimator ->
                mAngle = animation.getAnimatedValue() as Float
                postInvalidate()
            }
            mRotateAnimator2?.duration = 1000L
        }
        // 平移动画
        if (mTranslateAnimator == null) {
            mTranslateAnimator = ObjectAnimator.ofFloat(mDefDistance, 0f)
            mTranslateAnimator?.addUpdateListener { animation: ValueAnimator ->
                mDefDistance = animation.getAnimatedValue() as Float
            }
            mTranslateAnimator?.duration = 1000L
        }
        if (mTranslateAnimator2 == null) {
            mTranslateAnimator2 = ObjectAnimator.ofFloat(0f, mDefDistance)
            mTranslateAnimator2?.addUpdateListener { animation: ValueAnimator ->
                mDefDistance = animation.getAnimatedValue() as Float
            }
            mTranslateAnimator2?.duration = 1000L
        }

        if (mAnimTogether == null) {
            mAnimTogether = AnimatorSet()
            mAnimTogether?.playTogether(mRotateAnimator, mTranslateAnimator)

        }
        if (mAnimTogether2 == null) {
            mAnimTogether2 = AnimatorSet()
            mAnimTogether2?.playTogether(mRotateAnimator2, mTranslateAnimator2)
            mAnimTogether2?.addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animation: Animator?) {
                    changeColor()
                }
            })
        }

        if (mAnimSequentially == null) {
            mAnimSequentially = AnimatorSet()
            mAnimSequentially?.playSequentially(mAnimTogether, mAnimTogether2)
            mAnimSequentially?.start()
            //动画循环
            mAnimSequentially?.addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    mAnimSequentially?.start()
                }
            })
        }
    }

    private fun changeColor() {
        if (mCurColor == ColorSelected.BLUE)
            mCurColor = ColorSelected.YELLOW
        else if (mCurColor == ColorSelected.YELLOW)
            mCurColor = ColorSelected.RED
        else if (mCurColor == ColorSelected.RED)
            mCurColor = ColorSelected.BLUE
    }

    fun cancle() {
        mAnimSequentially?.cancel()
    }
}

Finally, to see the effect it achieved

 Of course, you can try to change the size and distance, but I still feel this effect had a flaw, writing is not good enough, so be it.

Guess you like

Origin blog.csdn.net/smile_Running/article/details/99694931