English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Android Custom View Implementation of Check Animation Function

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.

You May Also Like