Github address: TickView, a nice little checkbox animation github.com/ChengangFen…

Let’s start with the renderings, or we can’t read any more. Right?

Dynamic figure

Figure. GIF

Static figure

Static figure


1. Review

In the last article, we achieved basically the effect of the control, but… But… After three or four days, carefully look back to their own code, although the train of thought is still in, but part of the code still can not see all of a sudden understand…

My god, this needs to be refacedright away. Incidentally, a netizen ChangQin wrote a copy of this control. After reading it, I thought I could do the same.

2. Careful

For control drawing ideas, you can go to the last article, not here. Here first to analyze the last article inside, control inside some of the stubborn place, which places need to improve.

Take the step of drawing a circle

/ / counter
private int ringCounter = 0;

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if(! isChecked) { ...return;
    }
    // Add 12 units each time you draw the arc, that is, the arc has swept 12 degrees
    // We can do a configuration to customize the 12 units
    ringCounter += 12;
    if (ringCounter >= 360) {
        ringCounter = 360;
    }
    canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing); .// Force redraw
    postInvalidate();
}Copy the code

Here, we define a counter, ringCounter, which, when drawn, increments by 12 units to 360 to simulate progress changes.

Think about it

  1. By changing the increment of units to control the change of animation speed, it is difficult to adjust to their own satisfaction, at this time we can think of the animation speed is the basic control of time ah, if you can use time to control animation speed that much more convenient
  2. The animation is divided into four steps, and if each step of the animation is implemented using a handwritten counter, you have to define four or more member variables. Too many member variables will only clutter the code further
  3. If the animation needs an interpolator, the handwritten counter will not suffice
  4. I can’t accept the above analysis

3. Change to change

So how to improve the above problem, the answer is to use custom property animation to solve, so this article is mainly about the use of property animation to replace handwritten counters, as much as possible to ensure that the logic of the code, especially in the onDraw() method of the code.

One of the benefits of using attribute animations is that, given a range of values, it will help you generate a bunch of values you want. With the interpolator, you can expect unexpected effects. The next step is to reconstruct the animation step by step

3.1 Drawing a Progress bar in a Ring

First, use a custom ObjectAnimator to simulate the progress

//ringProgress is a custom attribute name that generates values ranging from 0 to 360, i.e. the Angle of a circle
ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this."ringProgress".0.360);
// Define the animation execution time, a good alternative to the previous use of increment units to control animation execution speed
mRingAnimator.setDuration(mRingAnimatorDuration);
// Interpolators are not needed for the time being
mRingAnimator.setInterpolator(null);Copy the code

Custom property animation, you also need to configure the corresponding setter and getter, because when the animation is executed, the corresponding setter will be found to change the corresponding value.

private int getRingProgress(a) {
    return ringProgress;
}

private void setRingProgress(int ringProgress) {
    // When the animation executes, the setter is called
    // Here we can record the values generated by the animation and store them as variables for use in ondraw
    this.ringProgress = ringProgress;
    // Remember to redraw
    postInvalidate();
}Copy the code

Finally, draw in onDraw()

// Draw the arc progress
canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);Copy the code

3.2 Draw an animation that shrinks to the center of a circle

Similarly, create a property animation

// The custom property here is the radius of the circle's contraction
ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this."circleRadius", radius - 5.0);
// add a decelerating interpolator
mCircleAnimator.setInterpolator(new DecelerateInterpolator());
mCircleAnimator.setDuration(mCircleAnimatorDuration);Copy the code

Setter /getter is the same thing

Finally draw in onDraw()

/ / draw the background
mPaintCircle.setColor(checkBaseColor);
canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
// When the progress circle is drawn, draw the shrinking circle
if (ringProgress == 360) {
    mPaintCircle.setColor(checkTickColor);
    canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
}Copy the code

3.3 Draw hook and zoom in on rebound effect

These are two separate effects, and I’m going to say it together

The first is also defining property animation

// A transparent gradient
ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this."tickAlpha".0.255);
mAlphaAnimator.setDuration(200);
// Finally zoom in and bounce back animation, change the brush width to achieve
// The width of the brush, then, is the range of change
// Start with the initial width, then 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 tick is executed with the zoomed-in rebound animation
AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);Copy the code

getter/setter

private int getTickAlpha(a) {
    return 0;
}

private void setTickAlpha(int tickAlpha) {
    // Set transparency, you can save without variables
    // Set the transparency value directly inside the brush
    mPaintTick.setAlpha(tickAlpha);
    postInvalidate();
}

private float getRingStrokeWidth(a) {
    return mPaintRing.getStrokeWidth();
}

private void setRingStrokeWidth(float strokeWidth) {
    // Set the brush width without using a variable
    // Set the brush width directly inside the brush
    mPaintRing.setStrokeWidth(strokeWidth);
    postInvalidate();
}Copy the code

Finally, do the same with onDraw()

if (circleRadius == 0) {
    canvas.drawLines(mPoints, mPaintTick);
    canvas.drawArc(mRectF, 0.360.false, mPaintRing);
}Copy the code

3.4 Perform animations in sequence

AnimatorSet playTogether() is implemented together, and playSequentially() is implemented side by side, step by step.

mFinalAnimatorSet = new AnimatorSet();
mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);Copy the code

Finally, execute the animation in onDraw()

// An identifier is defined to tell the program that the animation can only be executed once at a time
if(! isAnimationRunning) { isAnimationRunning =true;
    // Perform the animation
    mFinalAnimatorSet.start();
}Copy the code

3.5 It is desirable for each method to have a single responsibility

If I put the method of defining property animations in onDraw(), I personally feel confused, and look more closely, these property animations do not need to change dynamically, why not pull them out and initialize them at the beginning?

So, we pull out the code that defines the property animation and initialize it in the constructor

public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr); . initAnimatorCounter(); }Copy the code
/** * Initialize some counters with ObjectAnimator */
private void initAnimatorCounter(a) {
    // loop progress
    ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this."ringProgress".0.360); .// Shrink animation
    ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this."circleRadius", radius - 5.0); .// A transparent gradient
    ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this."tickAlpha".0.255); .// Finally zoom in and bounce back animation, change the brush width to achieve
    ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this."ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES); .// The tick is executed with the zoomed-in rebound animation
    AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
    mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);

    mFinalAnimatorSet = new AnimatorSet();
    mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
}Copy the code

And finally, in the onDraw() method, you just do simple drawing and nothing 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 the arc progress
    canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);
    // Draw a yellow background
    mPaintCircle.setColor(checkBaseColor);
    canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
    // Draw a shrinking white circle
    if (ringProgress == 360) {
        mPaintCircle.setColor(checkTickColor);
        canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
    }
    // Draw a tick, and zoom in and out animation
    if (circleRadius == 0) {
        canvas.drawLines(mPoints, mPaintTick);
        canvas.drawArc(mRectF, 0.360.false, mPaintRing);
    }
    //ObjectAnimator replaces the counter
    if(! isAnimationRunning) { isAnimationRunning =true; mFinalAnimatorSet.start(); }}Copy the code

The end result is the same, the code logic is clear

Final effect.gif

Therefore, I find it helpful to review my code periodically during development, both for myself and for future maintenance.

That’s all~ Thank you for reading, and finally, the github address of the project

Github address: TickView, a nice little checkbox animation github.com/ChengangFen…