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.