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
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
- 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
- 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
- If the animation needs an interpolator, the handwritten counter will not suffice
- 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
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…