English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
First show the effect diagram
Dynamic image
Static image
1. Review
[Android Custom View: A delicate checkmark animation] In the previous article, we have basically realized the effect of the control, but... but... after three or four days, looking back at the code I wrote, although the idea is still there, some of the code is still not easy to understand at a glance...
My God, this needs to be refactored immediately! Fortunately, there is a simple friend ChangQin who imitated to write this control, and after I saw it, I thought I could also implement it in this way.
2. Deep thought
About the ideas of control drawing, you can go and see the previous article, and there is no need to analyze it here. First, let's analyze some of the stubborn problems in the previous article, which parts need to be improved.
Take Draw the ring progress From this step to see
//Counter private int ringCounter = 0; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (!isChecked) { ... return; } //Draw the arc progress, increase by itself each time it is drawn12Unit, that is, the arc has swept over12Degree //Here12Unit is written dead first, and then we can make a configuration to implement customization ringCounter += 12; if (ringCounter >= 360) { ringCounter = 360; } canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing); ... //Force redraw postInvalidate(); }
Here, we define a counter ringCounter, when drawing, it is based on12Unit for self-increment to reach360, thus simulating the change of progress.
Think carefully
By changing the increment unit to control the change of animation speed, it is very difficult to adjust it to your satisfaction. At this point, we can think of, the fundamental way to control the speed of animation execution is to control time. If you canUsing time to control the speed of animationIt will be much more convenient. Animation is divided into4Step execution, if each step of the animation is implemented with a manually written counter, then you need to define4Too many member variables will only make the code more confusingIf you want to add animationInterpolator, the manually written counter can't meet the analysis above,I can't accept it
3. Keep changing
So how to improve the problem mentioned above? The answer is to use custom property animation to solve it, so the main content of this article is to use property animation to replace the manually written counter, and try to ensure the clarity of the code logic, especially the code in the onDraw() method.
One of the benefits of using property animation is that, given a range of values, it can help you generate a set of desired values for you, and with the help of an interpolator, you can achieve unexpected effects. The next section will step by step reconstruct the part of the animation.
3.1 Draw the circular progress bar
Firstly, use a custom ObjectAnimator to simulate the progress
//ringProgress is the custom property name, the range of generated values is 0 - 360, which is the angle of a circle ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360); //Define the animation execution time, which is a good alternative to using the increment unit to control the speed of animation execution before mRingAnimator.setDuration(mRingAnimatorDuration); //No interpolator is needed temporarily mRingAnimator.setInterpolator(null);
Custom property animation also requires configuring the corresponding setter and getter because the setter will be found during the animation execution to change the corresponding value.
private int getRingProgress() { return ringProgress; } private void setRingProgress(int ringProgress) { //The setter will be called when the animation is executed //Here, we can record the values generated by the animation, store them in variables, and use them in ondraw this.ringProgress = ringProgress; //Remember to redraw postInvalidate(); }
Finally, draw the image in the onDraw() method
//Draw the circular progress canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);
3.2 Draw the animation of the circle shrinking towards the center
Similarly, create a property animation
//Here, the customized property is the radius of the circle shrinking ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0); //Add a decelerating interpolator mCircleAnimator.setInterpolator(new DecelerateInterpolator()); mCircleAnimator.setDuration(mCircleAnimatorDuration);
setter/The getter is similar and will not be mentioned
Finally, draw in the onDraw() method
//Draw the background mPaintCircle.setColor(checkBaseColor); canvas.drawCircle(centerX, centerY, ringProgress == 360 &63; radius : 0, mPaintCircle); //Once the progress ring is drawn, draw the shrinking circle if (ringProgress == 360) { mPaintCircle.setColor(checkTickColor); canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle); }
3.3 Draw the hook and zoom in and bounce back effects
These are two independent effects, executed simultaneously here, so I will mention them together
Firstly, define the property animation
//The transparency gradient of the tick drawn ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255); mAlphaAnimator.setDuration(200); //The final scaling and rebound animation changes the pen width to achieve this //The width of the pen varies as follows: //Firstly, start from the initial width, then to n times the initial width, and finally back to the initial width. ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES); mScaleAnimator.setInterpolator(null); mScaleAnimator.setDuration(mScaleAnimatorDuration); //The animation of ticking and scaling back is executed together AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet(); mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);
getter/setter
private int getTickAlpha() { return 0; } private void setTickAlpha(int tickAlpha) { //Setting the alpha can be done without saving it in a variable //Set the alpha value directly into the brush mPaintTick.setAlpha(tickAlpha); postInvalidate(); } private float getRingStrokeWidth() { return mPaintRing.getStrokeWidth(); } private void setRingStrokeWidth(float strokeWidth) { //Setting the brush width can be done without saving it in a variable //Set the brush width directly into the brush mPaintRing.setStrokeWidth(strokeWidth); postInvalidate(); }
Similarly, draw in onDraw() at the end
if (circleRadius == 0) { canvas.drawLines(mPoints, mPaintTick); canvas.drawArc(mRectF, 0, 360, false, mPaintRing); }
3.4 Execute animations sequentially
To execute multiple animations, AnimatorSet can be used, where playTogether() executes them together, and playSequentially() executes them one after another, step by step
mFinalAnimatorSet = new AnimatorSet(); mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
Finally, execute the animation in onDraw()
//Here, an identifier is defined to inform the program that the animation can only be executed once each time if (!isAnimationRunning) { isAnimationRunning = true; //Execute animation mFinalAnimatorSet.start(); }
3.5 Each method should have a single responsibility
If the method defining the property animation is placed in onDraw(), I personally feel it is disorganized, and upon closer inspection, these property animations do not need to change dynamically, why not extract them and initialize them at the beginning?
So, we will define the code for property animation and put it in the constructor for initialization
public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); ... initAnimatorCounter(); }
/** * Initialize some counters using ObjectAnimator */ private void initAnimatorCounter() { //Ring progress ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360); ... //Contraction animation ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0); ... //The transparency gradient of the tick drawn ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255); ... //The final scaling and rebound animation changes the pen width to achieve this ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES); ... //The animation of ticking and scaling back is executed together AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet(); mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator); mFinalAnimatorSet = new AnimatorSet(); mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet); }
Finally, the onDraw() method is only responsible for simple drawing and does not care about anything else
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (!isChecked) { canvas.drawArc(mRectF, 90, 360, false, mPaintRing); canvas.drawLines(mPoints, mPaintTick); return; } //Draw arc progress canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing); //Draw a yellow background mPaintCircle.setColor(checkBaseColor); canvas.drawCircle(centerX, centerY, ringProgress == 360 &63; radius : 0, mPaintCircle); //Draw the shrinking white circle if (ringProgress == 360) { mPaintCircle.setColor(checkTickColor); canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle); } //Draw checkmark, as well as the zoom in and out animation if (circleRadius == 0) { canvas.drawLines(mPoints, mPaintTick); canvas.drawArc(mRectF, 0, 360, false, mPaintRing); } //ObjectAnimator animation replacement counter if (!isAnimationRunning) { isAnimationRunning = true; mFinalAnimatorSet.start(); } }
The final effect is the same, and the code logic is clear at a glance
So, personally, I think it's very helpful to review your own code from time to time during development, whether for yourself or for future maintenance
That's all~ Thank you for reading, and here's the GitHub address of the project one more time
> GitHub address: TickView, a delicate checkmark animationhttps://github.com/ChengangFeng/TickView
That's all~ Thank you for reading. Here's the GitHub address of the project one more time.