Dmytrodanylyk/circular-progress-button source code parsing (2), circularprogress

Source: Internet
Author: User

Dmytrodanylyk/circular-progress-button source code parsing (2), circularprogress


Source code download http://download.csdn.net/detail/kangaroo835127729/8755815

In the previous article, when the progress (such as downloading a file) is unknown (such as a json Data Request), there is a special rotation style (the implementation of this style is complicated, this article mainly aims to talk about it ).

The preceding style is defined by the mIndeterminateProgressMode attribute (the set method is provided ). If mIndeterminateProgressMode is set to false (the progress style is displayed) and true, the rotation style is displayed.

Next, let's take a look at these two styles (we will not cut down the animation, but upload it in a few days)

For the two styles, let's start with the ondraw () method of circular-progress-button.

@Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (mProgress > 0 && mState == State.PROGRESS && !mMorphingInProgress) {            if (mIndeterminateProgressMode) {                drawIndeterminateProgress(canvas);            } else {                drawProgress(canvas);            }        }    }
The ondraw () method shows that when the mState is mState = State. PROGRESS, that is, when the status is loaded, the function is called to customize the painting (drawing the ring). Otherwise, you can draw the button style by yourself.

Let me first look at the drawProgress (canvas) method, that is, to draw a ring based on the progress, without rotating

/*** Progress ring background ** @ param canvas */private void drawProgress (Canvas canvas) {if (mProgressDrawable = null) {// offset, because the ring must be int offset = (getWidth ()-getHeight ()/2; // The Ring height int size = getHeight ()-mPaddingProgress * 2; mProgressDrawable = new CircularProgressDrawable (size, mStrokeWidth, mColorIndicator); int left = offset + mPaddingProgress; mProgressDrawable. setBounds (left, mPaddingProgress, left, mPaddingProgress);} float sweepAngle = (360f/mMaxProgress) * mProgress; mProgressDrawable. setSweepAngle (sweepAngle); mProgressDrawable. draw (canvas );}
The key here is that we have defined a CircularProgressDrawable and set its position, range, and color.

class CircularProgressDrawable extends Drawable {    private float mSweepAngle;    private float mStartAngle;    private int mSize;    private int mStrokeWidth;    private int mStrokeColor;    public CircularProgressDrawable(int size, int strokeWidth, int strokeColor) {        mSize = size;        mStrokeWidth = strokeWidth;        mStrokeColor = strokeColor;        mStartAngle = -90;        mSweepAngle = 0;    }    public void setSweepAngle(float sweepAngle) {        mSweepAngle = sweepAngle;    }    public int getSize() {        return mSize;    }    @Override    public void draw(Canvas canvas) {        final Rect bounds = getBounds();        if (mPath == null) {            mPath = new Path();        }        mPath.reset();        mPath.addArc(getRect(), mStartAngle, mSweepAngle);        mPath.offset(bounds.left, bounds.top);        canvas.drawPath(mPath, createPaint());    }    @Override    public void setAlpha(int alpha) {    }    @Override    public void setColorFilter(ColorFilter cf) {    }    @Override    public int getOpacity() {        return 1;    }    private RectF mRectF;    private Paint mPaint;    private Path mPath;    private RectF getRect() {        if (mRectF == null) {            int index = mStrokeWidth / 2;            mRectF = new RectF(index, index, getSize() - index, getSize() - index);        }        return mRectF;    }    private Paint createPaint() {        if (mPaint == null) {            mPaint = new Paint();            mPaint.setAntiAlias(true);            mPaint.setStyle(Paint.Style.STROKE);            mPaint.setStrokeWidth(mStrokeWidth);            mPaint.setColor(mStrokeColor);        }        return mPaint;    }}
The key is the draw method. mpath is used to set the path of the ring to be drawn. We know that mPath is called. addArc () can be set to draw a ring. So what is RectF? See the getRect () method. We have noticed that we have created a RectF, and the left of this RectF is half of the strokes, why is it half? Because I draw along half of the image, the width of the stroke is evenly divided by the half. (If it is not half, it will cross the border during painting, because the strokes also have width)
int index = mStrokeWidth / 2;            mRectF = new RectF(index, index, getSize() - index, getSize() - index);
Then, move it to the center.
mPath.offset(bounds.left, bounds.top);
Finally, mSweepAngle considers it a scanned radian, which is calculated based on the Progress process divided by 100*360.
mPath.addArc(getRect(), mStartAngle, mSweepAngle);

OK, it's that simple. We have drawn an arc.


Next let's look at another method: drawIndeterminateProgress ()

/*** Circular background ** @ param canvas */private void drawIndeterminateProgress (Canvas canvas) {if (mAnimatedDrawable = null) {int offset = (getWidth () -getHeight ()/2; mAnimatedDrawable = new CircularAnimatedDrawable (mColorIndicator, mStrokeWidth); // It causes int left = offset + mPaddingProgress in the middle; int right = getWidth () -offset-mPaddingProgress; int bottom = getHeight ()-mPaddingProgress; int top = mPaddingProgress; mAnimatedDrawable. setBounds (left, top, right, bottom); mAnimatedDrawable. setCallback (this); mAnimatedDrawable. start ();} else {mAnimatedDrawable. draw (canvas );}}
This method is similar to the above, but it is different from the created object. Here we create a CircularAnimatedDrawable object (from here we can see that different styles are actually different Drawable objects)

This object is complex. Let's take a look at the basic attributes and constructor methods.

Class CircularAnimatedDrawable extends Drawable implements Animatable {/*** linear time insertor */private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator ();/** time insertor, fast and slow */private static final Interpolator SWEEP_INTERPOLATOR = new DecelerateInterpolator (); private static final int ANGLE_ANIMATOR_DURATION = 2000; private static final int SWEEP_ANIMATOR_DURATION = 600; public static final int MIN_SWEEP_ANGLE = 30; private final RectF fBounds = new RectF (); private ObjectAnimator attributes;/*** whether to change the head and tail */private boolean mModeAppearing; private Paint mPaint;/*** the cycle starts from 0 to 360. 2 * MIN_SWEEP_ANGLE (60) */private float mCurrentGlobalAngleOffset is added each time./*** the current angle, linear growth */private float mCurrentGlobalAngle;/*** current scanning angle, fast and slow growth */private float mCurrentSweepAngle;/*** Border Width */private float mBorderWidth; /*** whether the animation is running */private boolean mRunning;

Constructor

Public CircularAnimatedDrawable (int color, float borderWidth) {mBorderWidth = borderWidth; // initialize the Paint brush mPaint = new Paint (); mPaint. setAntiAlias (true); mPaint. setStyle (Paint. style. STROKE); // set the paint brush width mPaint. setStrokeWidth (borderWidth); mPaint. setColor (color); setupAnimations ();}

Color is the ring color, and borderwidth is the stroke size.

Then the paint brush is initialized, And the setupAnimations () method is called to initialize the animation.

/*** Load The animation */private void setupAnimations () {// angle animation/*** target The object whose property is to be animated. the animation object * property The property being animated. the property * values A set of values that the animation will animate between over time. set the value * constructor description. For a CircularAnimatedDrawable Class Object, set an animation for the mCurrentGlobalAngle attribute * that is, to linearly assign a value to mCurrentGlobalAngle from 0 to 360f */mObjectAnimatorAngle = ObjectAnimator. ofFloat (this, mAngleProperty, 360f); // you can specify the inserter mObjectAnimatorAngle. setInterpolator (ANGLE_INTERPOLATOR); // sets the animation duration mObjectAnimatorAngle. setDuration (ANGLE_ANIMATOR_DURATION); // you can specify the cycle mode. setRepeatMode (ValueAnimator. RESTART); // set the number of cycles mObjectAnimatorAngle. setRepeatCount (ValueAnimator. INFINITE); // scan the animation. // assign the value of mObjectAnimatorSweep = ObjectAnimator to mCurrentSweepAngle from 0 to (360f-MIN_SWEEP_ANGLE * 2) (that is, 300. ofFloat (this, mSweepProperty, 360f-MIN_SWEEP_ANGLE * 2); mObjectAnimatorSweep. setInterpolator (SWEEP_INTERPOLATOR); mObjectAnimatorSweep. setDuration (SWEEP_ANIMATOR_DURATION); mObjectAnimatorSweep. setRepeatMode (ValueAnimator. RESTART); mObjectAnimatorSweep. setRepeatCount (ValueAnimator. INFINITE); mObjectAnimatorSweep. addListener (new Animator. animatorListener () {@ Override public void onAnimationStart (Animator animation) {}@ Override public void onAnimationEnd (Animator animation) {}@ Override public void Merge (Animator animation) {}@ Override public void onAnimationRepeat (Animator animation) {// At the end of the record, change toggleAppearingMode ();}});}

In essence, two types of animations are created here, both of which are ObjectAnimator and ObjectAnimator inherited from ValueAnimator. Therefore, both animations provide the current value and do not change the control style.

One is mObjectAnimatorAngle, linear growth, used to calculate the starting Angle

One is mObjectAnimatorSweep, which is used to calculate the scanning angle after fast and slow growth.

We need to carefully observe the animation process (it may be very fast. during testing, you can increase the default playback time of the animation for observation). Note that the process is like this. First, it is fast and slow, make the arc continuously narrow (the whole arc is moving at the same time, because both ends are moving fast and fast), catch up (catch up when the distance is MIN_SWEEP_ANGLE = 30 ), fast and slow switching (that is, fast switching to slow, slow switching to fast), and then quickly continue to slow, so that the arc continues to increase, after catching up, switch again, repeat the above process.


After setting the above animation, let's look at the ondraw () method.

@ Override public void draw (Canvas canvas) {float startAngle = mCurrentGlobalAngle-mCurrentGlobalAngleOffset; float sweepAngle = mCurrentSweepAngle; if (! MModeAppearing) {startAngle = startAngle + sweepAngle; sweepAngle = 360-sweepAngle-MIN_SWEEP_ANGLE;} else {sweepAngle + = MIN_SWEEP_ANGLE;}/*** draw the arc * oval: specifies the rectangular area of the outer contour of the arc. * StartAngle: the starting angle of the arc, measured in degrees. * SweepAngle: the angle from which the arc is scanned. It is clockwise and measured in degrees. * UseCenter: if it is True, the center of the circle is included when the arc is drawn, which is usually used to draw slices. * Paint: Specifies the drawing board attribute of an arc, for example, color or fill. */Canvas. drawArc (fBounds, startAngle, sweepAngle, false, mPaint );}

The first is the mModeAppearing attribute. The initial value is false. Then we noticed that this value is reversed every time the mObjectAnimatorSweep reaches 300 degrees.

First, the mModeAppearing value is false, and the mCurrentGlobalAngleOffset value is 0,

StartAngle (starting point) = mCurrentGlobalAngle maintains linear growth. sweepAngle = mCurrentSweepAngle indicates nonlinear growth.

Then, the conditional statement will call

StartAngle = startAngle + sweepAngle;
SweepAngle = 360-sweepAngle-MIN_SWEEP_ANGLE;

At this time, startAngle is the sum of linear growth and non-linear growth, so it is essentially non-linear growth (Fast first and slow)

Let's consider the ending point of the arc, which is equal to startAngle + sweepAngle = 360 + startAngle-MIN_SWEEP_ANGLE (note that the difference between startAngle and startAngle is actually the addition of the above two formulas)

We can see that the termination point increases linearly.

Obviously, the starting point will catch up with the ending point, so in the first draw, the fast moving is actually the starting point, the slow is the ending point

Because of the speed difference between the two, we draw an arc from the starting point to the ending point, which will naturally gradually shrink.


OK. When the start point catches up with the end point (MIN_SWEEP_ANGLE = 30 is called catch up), we need to exchange the growth mode between the two, so mModeAppearing is set to true.

What did I do when I checked "true "?

float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset;
Start Point: linear growth

float sweepAngle = mCurrentSweepAngle;
Radians are non-linear, resulting in non-linear termination points.

else {            sweepAngle += MIN_SWEEP_ANGLE;                   }
MIN_SWEEP_ANGLE is added, because after the swap, mCurrentSweepAngle is set to zero, and we need to keep the original style with a difference of 30 degrees, so we need to add a 30 degree


In this way, the effect on the graph can be generated through the repeated exchange between the start point and the end point.

OK, circular-progress-button. The following is a piece of code for using circular-progress-button. Let's take a look.

Public class MainActivity extends Activity {private CircularProgressButton circularProgressButton; private CircularProgressButton circularProgressButton2; @ Override public void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. main); circularProgressButton = (CircularProgressButton) findViewById (R. id. mp); circularProgressButton2 = (CircularProgressButton) findViewById (R. id. mp2); // set to the rotation style circularProgressButton2.setIndeterminateProgressMode (true); circularProgressButton2.setOnClickListener (new View. onClickListener () {@ Override public void onClick (View v) {if (circularProgressButton2.getProgress () = 0) {circularProgressButton2.setProgress (50);} else if (Progress () ==- 1) {circularProgressButton2.setProgress (0);} else {circularProgressButton2.setProgress (-1) ;}}); circularProgressButton. setOnClickListener (new OnClickListener () {@ Overridepublic void onClick (View v) {new getdatatask(.exe cute () ;}});} private class GetDataTask extends AsyncTask <Void, Integer, void> {int I = 0; @ Override protected Void doInBackground (Void... params) {while (I <= 100) {try {Thread. sleep (20);} catch (InterruptedException e) {e. printStackTrace ();} publishProgress (I ++);} return null ;}@ Override protected void onProgressUpdate (Integer... values) {circularProgressButton. setProgress (values [0]); super. onProgressUpdate (values) ;}@ Override protected void onPostExecute (Void result) {circularProgressButton. setProgress (100); super. onPostExecute (result );}}}

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.