Author: Chen Gang not family name is Chen links: http://www.jianshu.com/p/1b2cdba03d23 copyright owned by the author. Reproduced by authorisation of the author, reproduced please indicate the source.

1. Introduction

Recently, when I was reading light mang magazine, I saw an animation with a sense of delicacy.

I happen to be watching HenCoder’s custom View tutorial (which is very detailed, there are corresponding exercises and so on), so I will strike while the iron is hot and get familiar with the knowledge I have learned.

International practice, first on the light mount magazine to mark the read animation

Do you feel very delicate after reading it?

So let’s look at my own imitation

Static figure

Is it possible to imitate a bit similar, ha ha ~, let’s look at the idea of my implementation

2. The analysis

This animation is not complicated to achieve, master a few basic custom view methods.

The idea of implementation is divided into selected state and unselected state

2.1 Unselected Status

The unchecked state is simple and requires two graphs to be drawn

  • The ring

  • tick

2.2 Selected status

Drawing the selected animation is a little more complicated, mainly including

1. Draw the progress bar in a circle. This is easy to do using drawArc()

2. Draw the animation shrinking toward the center of the circle. At the beginning, I wanted to use drawArc() and strokeWidth of the brush to achieve this, but the changed width was expanded outward, so this idea was decisively abandoned. And then, my idea is this, look at the picture below

I’m going to draw a yellow background, then in this layer draw a white circle, the radius of narrow, until to 0, which, in turn, get a contraction of the animation to the center, it can call reverse thinking, recently read a book in said to sometimes think, in turn, may have different effect.

As for this √, I have searched the Internet, but there is no clear indication of how to draw the standard, so you can use it freely and feel good about it. You can just use drawLine() and you can do it all in one step.

4. Finally, the effect of circle enlargement and rebound can be enlarged and rebound can be achieved by using drawArc() and changing the width of the brush

3. Implementation

3.1 Determine the position of the progress ring and hook

According to the above analysis, the position of the progress ring and hook is unchanged whether it is selected or not, so we first determine the position of the ring and hook

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); . Radius is the radius of the circle, centerX, Set (centerx-radius, centerY - radius, centerX + radius, centerY + RADIUS); // Set the position of the tick point (s) // set the position of the tick point (s). // Use canvas.drawLines(mPoints, MPoint [4]~mPoint[7] mPoints[0] = centerX; mPoint[0] = centerX  - tickRadius + tickRadiusOffset; mPoints[1] = (float) centerY; mPoints[2] = centerX - tickRadius / 2 + tickRadiusOffset; mPoints[3] = centerY + tickRadius / 2; mPoints[4] = centerX - tickRadius / 2 + tickRadiusOffset; mPoints[5] = centerY + tickRadius / 2; mPoints[6] = centerX + tickRadius * 2 / 4 + tickRadiusOffset; mPoints[7] = centerY - tickRadius * 2 / 4; }Copy the code

3.2 Define variables and mark states

Since the sorting state and the unselected state, the drawing process must determine whether the current drawing is unselected or selected.

So in this case, I’ve defined a variable isChecked

Private Boolean isChecked = false; Public void setChecked(Boolean checked) {if (this.ischecked! = checked) { isChecked = checked; reset(); }}Copy the code

3.3 Drawing the unselected state

I’m not going to go into details about the brushes that you draw, but when you initialize the brushes you call them when you draw them at the end

@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); if (! IsChecked) {// Draw the circle, mRectF is the tangent rectangle we determined before // Since it is static, set the Angle of sweep to 360 degrees cancan. drawArc(mRectF, 90, 360, false, mPaintRing); DrawLines (mPoints, mPaintTick); return; }}Copy the code

3.4 Drawing the selected state

The selected state is an animation, so we need to call postInvalidate() and keep redrawing until the animation is finished; In addition, I use a counter here to control the progress of the drawing.

3.4.1 Drawing a Progress bar in a Ring

Here, we define a counter ringCounter with a peak value of 360 (i.e. 360 degrees). Each time we execute the onDraw() method, we auto-increment ringCounter to simulate the progress.

Finally, remember to call postInvalidate() to redraw

// counter private int ringCounter = 0; @Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); if (! isChecked) { ... return; RingCounter += 12; ringCounter += 12; ringCounter += 12; if (ringCounter >= 360) { ringCounter = 360; } canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing); . // forcibly redraw postInvalidate(); }Copy the code

After this step, the effect is shown below

3.4.2 Draw an animation for shrinking to the center of a circle

The contraction of the center of the circle will not be animated until the progress of the circle reaches 100%. Similarly, the method of the counter circleCounter is also used to control the drawing time and speed

// count private int circleCounter = 0; @Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); . If (ringCounter == 360) {// First draw the background circle mpaintcirce.setcolor (checkBaseColor); canvas.drawCircle(centerX, centerY, radius, mPaintCircle); Mpaintcircles.setcolor (checkTickColor); // On the background circle layer, draw a white circle (radius shrinking). CircleCounter += 6; canvas.drawCircle(centerX, centerY, radius - circleCounter, mPaintCircle); } // postInvalidate() must be redrawn; }Copy the code

After this step, the effect is shown below

The rule 3.4.3 draw hook

When the radius of the white circle shrinks to 0, it is time to draw the checkbox.

Draw the check. This is not a problem because the three coordinate points of the check have been calculated in onMeasure() and can be drawn using drawLine().

@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); . canvas.drawCircle(centerX, centerY, radius - circleCounter, mPaintCircle); // When the radius of the white circle has shrunk to 0, // the counter circleCounter is larger than the radius of the background circle. If (circleCounter >= radius + 40) {alphaCount += 20; if (alphaCount >= 255) alphaCount = 255; mPaintTick.setAlpha(alphaCount); DrawLines (mPoints, mPaintTick); drawLines(mPoints, mPaintTick) } postInvalidate(); }Copy the code

3.5 Exposing External Interfaces

In order to have the flexibility to control the state of the drawing, we can expose an interface to whether the external Settings are selected or not

Public void setChecked(Boolean checked) {if (this.ischecked! = checked) { isChecked = checked; reset(); * * *}} / reset, and draw * / private void reset () {/ / reset brushes... // counter reset ringCounter = 0; circleCounter = 0; scaleCounter = 45; alphaCount = 0; . invalidate(); }Copy the code

3.6 Adding click events

The control is pretty much ready at this point, but it’s not particularly polished.

Think of checkBox, which can be checked or unchecked by clicking on a control without exposing an external interface, so the next step is to add click events to the control

Define an interface OnCheckedChangeListener to listen for the listener events of this control

private OnCheckedChangeListener mOnCheckedChangeListener; public interface OnCheckedChangeListener {         void onCheckedChanged(TickView tickView, boolean isCheck);
}public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {        this.mOnCheckedChangeListener = listener;
}Copy the code

Next, initialize the click event for the controller

Public TickView(Context Context, @nullable AttributeSet attrs, int defStyleAttr) {super(Context, attrs, defStyleAttr); . setUpEvent(); }/** * Private void setUpEvent() {this.setonClickListener (new OnClickListener() {@override public void setupeListener () onClick(View view) { isChecked = ! isChecked; reset(); if (mOnCheckedChangeListener ! = null) {/ / callback mOnCheckedChangeListener here. OnCheckedChanged (TickView) view, isChecked (); }}}); }Copy the code

Look at the renderings

3.7 Customizing Configuration Items

<declare-styleable name="TickView"> <! <attr name="uncheck_base_color" format="color" /> <! <attr name="check_base_color" format="color" /> <! <attr name="check_tick_color" format="color" /> <! <attr name="radius" format="dimension" /> <! <attr name="rate"> <enum name="slow" value="0"/> <enum name="normal" value="1"/> <enum name="fast" value="2"/> </attr> </declare-styleable>Copy the code

Here is a brief description of the animation speed configuration, here I set 3 speed, I use enumeration to define three speed configuration items

Enum TickRateEnum {// SLOW(6, 4, 2), // NORMAL speed NORMAL(12, 6, 4), // High speed FAST(20, 14, 8); public static final int RATE_MODE_SLOW = 0; public static final int RATE_MODE_NORMAL = 1; public static final int RATE_MODE_FAST = 2; Private int ringCounterUnit; private int ringCounterUnit; Private int circleCounterUnit; Private int scaleCounterUnit; private int scaleCounterUnit; public static TickRateEnum getRateEnum(int rateMode) { TickRateEnum tickRateEnum; switch (rateMode) { case RATE_MODE_SLOW: tickRateEnum = TickRateEnum.SLOW; break; case RATE_MODE_NORMAL: tickRateEnum = TickRateEnum.NORMAL; break; case RATE_MODE_FAST: tickRateEnum = TickRateEnum.FAST; break; default: tickRateEnum = TickRateEnum.NORMAL; break; } return tickRateEnum; }... }Copy the code

Get the configuration of the XML, get the corresponding enumeration, and get some parameters for the animation speed

/** * constructor */public TickView(Context Context, @nullable AttributeSet attrs, int defStyleAttr) {super(Context, attrs, defStyleAttr); . initAttrs(attrs); } private void initAttrs(AttributeSet attrs) {TypedArray TypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.TickView); . Int rateMode = typeDarray. getInt(r.tyleable.tickView_rate, TickRateEnum.RATE_MODE_NORMAL); mTickRateEnum = TickRateEnum.getRateEnum(rateMode); typedArray.recycle(); }Copy the code

Final Result diagram

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

Making address: TickView, a delicate off small animation at https://github.com/ChengangFeng/TickView

Review past

Android SVG vector animation mechanism

2

Android project summary (a) : arc ViewPager and arc HeaderView

3

Android- Image processing notes

If you find this article helpful, please share it with others

Pay attention to Android technology grocery store, there are Android dry goods articles to share every day!

Long click the QR code to follow us