This is a very simple animation effect, which can be achieved by using attribute animation, in the hope that the effect of learning animation for the reader can be valuable.
1. Custom Animation Effect - Loading Effect
So here's a Loading animation we need to do.Loading effects are a common kind of animation. The simplest implementation is to draw a dynamic picture of the design, or to draw a static picture and then use a frame animation.But today we're using pure code, no picture resources.
Rough ideas
We customize a View, inherit the View class, and then draw two arcs of different radii, turning different angles.
Draw two arcs with different radii
First, the Recf() of the outer and inner circles is initialized;
private RectF mOuterCircleRectF = new RectF(); private RectF mInnerCircleRectF = new RectF();
Then draw the arc in the onDraw method:
//Get the center of the View float centerX = getWidth() / 2; float centerY = getHeight() / 2; if (lineWidth > centerX) { throw new IllegalArgumentException("lineWidth Value is too big"); } //The radius of the outer circle, because our arc is wide, this part should be subtracted when calculating the radius, otherwise it will have a cutting effect float outR = centerX - lineWidth; //Small circle radius float inR = outR * 0.6f - lineWidth; //Set the distance of the arc from top to bottom, that is, the rectangle that surrounds the park. mOuterCircleRectF.set(centerX - outR, centerY - outR, centerX + outR, centerY + outR); mInnerCircleRectF.set(centerX - inR, centerY - inR, centerX + inR, centerY + inR); //Draw Outer Circle canvas.drawArc(mOuterCircleRectF, mRotateAngle % 360, OUTER_CIRCLE_ANGLE, false, mStrokePaint); //Draw Inner Circle canvas.drawArc(mInnerCircleRectF, 270 - mRotateAngle % 360, INTER_CIRCLE_ANGLE, false, mStrokePaint);
The code is simple, just like a comment:
- Get the width and height of the entire loadView and calculate the center of the loadview
- Use the center to calculate the radius of the outer circle and the inner circle, because the arc edges of the arc have a width, this part of the width should be subtracted, otherwise the top, bottom, left and right will be cut.
- Set Recf Rectangle with Circle Radius as Edge Length
- Draw an arc in the canvas with rectangular data. An angle is set so that the circle has a missing angle, as long as the circle is not 360 degrees.
Drawing a circle should be done in the onDraw method so that we can redraw it continuously or get the true width and height of the view
Of course, we still need to set a brush to draw our circle
mStrokePaint = new Paint(); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setStrokeWidth(lineWidth); mStrokePaint.setColor(color); mStrokePaint.setAntiAlias(true); mStrokePaint.setStrokeCap(Paint.Cap.ROUND); mStrokePaint.setStrokeJoin(Paint.Join.ROUND);
2. Set property animation
The arc is drawn and then animated using attributes.This is a ValueAnimator, value attribute animation, we can set a range of values and let it change within that range.
mFloatValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); mFloatValueAnimator.setRepeatCount(Animation.INFINITE); mFloatValueAnimator.setDuration(ANIMATION_DURATION); mFloatValueAnimator.setStartDelay(ANIMATION_START_DELAY); mFloatValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
This setting is very simple, set the value range, this is a wireless loop, set the animation execution time, this animation loop delay time, set the interpolator.
3. Arc move
The principle of moving an arc is to listen for changes in the value of the value attribute animation, then continuously change the angle of the arc in the process of this change, and then let it redraw.
Let's have our loadview implement the ValueAnimator.AnimatorUpdateListener interface, and then onAnimationUpdate listen for animation changes.We set the value range to float when we initialize the value property animation, so we can get this change here.This value can then be used to change the angle size of the circle to be drawn, and then the redrawing method can be invoked to achieve:
@Override public void onAnimationUpdate(ValueAnimator animation) { mRotateAngle = 360 * (float)animation.getAnimatedValue(); invalidate(); }
That's roughly the whole idea.The complete code is as follows:
public class LoadingView extends View implements Animatable, ValueAnimator.AnimatorUpdateListener { private static final long ANIMATION_START_DELAY = 200; private static final long ANIMATION_DURATION = 1000; private static final int OUTER_CIRCLE_ANGLE = 270; private static final int INTER_CIRCLE_ANGLE = 90; private ValueAnimator mFloatValueAnimator; private Paint mStrokePaint; private RectF mOuterCircleRectF; private RectF mInnerCircleRectF; private float mRotateAngle; public LoadingView (Context context) { this(context, null); } public LoadingView (Context context, @Nullable AttributeSet attrs) { this(context, attrs, -1); } public LoadingView (Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, -1); } public LoadingView (Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context, attrs); } float lineWidth; private void initView(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomLoadingView); lineWidth = typedArray.getFloat(R.styleable.MyCustomLoadingView_lineWidth, 10.0f); int color = typedArray.getColor(R.styleable.MyCustomLoadingView_viewColor, context.getColor(R.color.colorAccent)); typedArray.recycle(); initAnimators(); mOuterCircleRectF = new RectF(); mInnerCircleRectF = new RectF(); //Initialization Brush initPaint(lineWidth, color); //Rotation angle mRotateAngle = 0; } private void initAnimators() { mFloatValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); mFloatValueAnimator.setRepeatCount(Animation.INFINITE); mFloatValueAnimator.setDuration(ANIMATION_DURATION); mFloatValueAnimator.setStartDelay(ANIMATION_START_DELAY); mFloatValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); } /** * Initialization Brush */ private void initPaint(float lineWidth, int color) { mStrokePaint = new Paint(); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setStrokeWidth(lineWidth); mStrokePaint.setColor(color); mStrokePaint.setAntiAlias(true); mStrokePaint.setStrokeCap(Paint.Cap.ROUND); mStrokePaint.setStrokeJoin(Paint.Join.ROUND); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float centerX = getWidth() / 2; float centerY = getHeight() / 2; //Maximum size if (lineWidth > centerX) { throw new IllegalArgumentException("lineWidth Value is too big"); } float outR = centerX - lineWidth; //Small circle size float inR = outR * 0.6f; mOuterCircleRectF.set(centerX - outR, centerY - outR, centerX + outR, centerY + outR); mInnerCircleRectF.set(centerX - inR, centerY - inR, centerX + inR, centerY + inR); //Save the state of the drawing board first canvas.save(); //Outer Circle canvas.drawArc(mOuterCircleRectF, mRotateAngle % 360, OUTER_CIRCLE_ANGLE, false, mStrokePaint); //Inner Circle canvas.drawArc(mInnerCircleRectF, 270 - mRotateAngle % 360, INTER_CIRCLE_ANGLE, false, mStrokePaint); //Restore the state of the drawing board canvas.restore(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); startLoading(); } public void startLoading() { start(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); stopLoading(); } public void stopLoading() { stop(); } @Override public void onAnimationUpdate(ValueAnimator animation) { mRotateAngle = 360 * (float)animation.getAnimatedValue(); invalidate(); } protected void computeUpdateValue(float animatedValue) { mRotateAngle = (int) (360 * animatedValue); } @Override public void start() { if (mFloatValueAnimator.isStarted()) { return; } mFloatValueAnimator.addUpdateListener(this); mFloatValueAnimator.setRepeatCount(Animation.INFINITE); mFloatValueAnimator.setDuration(ANIMATION_DURATION); mFloatValueAnimator.start(); } @Override public void stop() { mFloatValueAnimator.removeAllUpdateListeners(); mFloatValueAnimator.removeAllListeners(); mFloatValueAnimator.setRepeatCount(0); mFloatValueAnimator.setDuration(0); mFloatValueAnimator.end(); } @Override public boolean isRunning() { return mFloatValueAnimator.isRunning(); } }
The attr file code is as follows:
<declare-styleable name="LoadingView"> <attr name="lineWidth" format="float" /> <attr name="viewColor" format="color" /> </declare-styleable>