dmytrodanylyk/circular-progress-button源碼解析(二),circularprogress

來源:互聯網
上載者:User

dmytrodanylyk/circular-progress-button源碼解析(二),circularprogress


源碼下載http://download.csdn.net/detail/kangaroo835127729/8755815

在上篇文章http://blog.csdn.net/crazy__chen/article/details/46278423中,我主要講述了circular-progress-button狀態切換的動畫過程,接下來我們看一個最特殊的狀態,就是載入狀態,這個狀態會顯示一個圓環來表示當前載入的進度,但是其實circular-progress-button提供給了我們兩個選擇,一個是圓環弧度代表進度(例如下載檔案),一個是在不知道進度(例如json資料請求)的情況下,有一個特別的旋轉樣式(這個樣式的實現比較複雜,這篇文章主要也是為了講它)。

上述樣式,由一個屬性mIndeterminateProgressMode來定義(有提供set方法)。當mIndeterminateProgressMode為false(出現進度樣式),為true,出現旋轉樣式

下面看一下這兩種樣式(還不會截動圖,過幾天上傳)

對於這兩個兩個樣式,我們先從circular-progress-button的ondraw()方法說起

@Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (mProgress > 0 && mState == State.PROGRESS && !mMorphingInProgress) {            if (mIndeterminateProgressMode) {                drawIndeterminateProgress(canvas);            } else {                drawProgress(canvas);            }        }    }
從ondraw()方法可以看出,當mState為mState == State.PROGRESS,也就是為載入狀態時,才調用函數來進行自訂繪製(繪製圓環),否則自己繪製按鈕樣式就可以了。

我先來看drawProgress(canvas)方法,也就是根據進度繪圓環,不旋轉

/**     * 進度環形背景     * @param canvas     */    private void drawProgress(Canvas canvas) {        if (mProgressDrawable == null) {            //位移,因為要求環形在原按鈕的正中間            int offset = (getWidth() - getHeight()) / 2;            //環形高度                       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);    }
這裡關鍵是我們定義了一個CircularProgressDrawable,設定了它的位置,範圍,和顏色等

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;    }}
關鍵是裡面的draw方法,mpath用於設定繪製圓環的路徑,我們知道調用mPath.addArc()就可以設定繪製圓環,那麼RectF是什麼呢,看getRect()方法注意到,我們建立一個RectF,這個RectF的left是筆觸的一半,為什麼是一半呢?因為我沿著一半畫,筆觸寬度剛好被這一半平分。(如果不是一半,就畫的時候就會越界,因為筆觸也是有寬度的)
int index = mStrokeWidth / 2;            mRectF = new RectF(index, index, getSize() - index, getSize() - index);
然後是,使之移動到正中間
mPath.offset(bounds.left, bounds.top);
最後,mSweepAngle覺得了掃過的弧度,而這個弧度,是根據進度process除以100*360計算出來的
mPath.addArc(getRect(), mStartAngle, mSweepAngle);

OK,就是這麼簡單,我們繪製出了圓弧。


接下來我們看另外一個方法drawIndeterminateProgress()

/**     * 環形迴圈式背景     * @param canvas     */    private void drawIndeterminateProgress(Canvas canvas) {        if (mAnimatedDrawable == null) {            int offset = (getWidth() - getHeight()) / 2;            mAnimatedDrawable = new CircularAnimatedDrawable(mColorIndicator, mStrokeWidth);            //使之在正中間            int left = offset + mPaddingProgress;            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);        }    }
這個方法與上面的其實類似,只是建立的對象不同,我們這裡建立了一個CircularAnimatedDrawable對象(從這裡可以看出,每種不同的樣式,其實就是不同的Drawable對象)

這個對象比較複雜,我們慢慢來看,首先是一些基本屬性和構造方法

class CircularAnimatedDrawable extends Drawable implements Animatable {/** * 線性時間插入器 */    private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator();    /** * 時間插入器,先快後慢 */    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 mObjectAnimatorSweep;    private ObjectAnimator mObjectAnimatorAngle;    /**     * 是否改變頭尾     */    private boolean mModeAppearing;    private Paint mPaint;    /**     * 迴圈從0到360,每次增加2*MIN_SWEEP_ANGLE(60)     */    private float mCurrentGlobalAngleOffset;    /**     * 當前角度,線性增長     */    private float mCurrentGlobalAngle;    /**     * 當前掃過角度,先快後慢增長     */    private float mCurrentSweepAngle;    /**     * 邊框寬度     */    private float mBorderWidth;    /**     * 是否正在運行動畫     */    private boolean mRunning;

構造方法

public CircularAnimatedDrawable(int color, float borderWidth) {        mBorderWidth = borderWidth;        //初始化畫筆        mPaint = new Paint();        mPaint.setAntiAlias(true);        mPaint.setStyle(Paint.Style.STROKE);        //設定畫筆寬度        mPaint.setStrokeWidth(borderWidth);        mPaint.setColor(color);        setupAnimations();    }

color是圓環顏色,borderwidth是筆觸大小

然後是畫筆初始化,接著調用setupAnimations()方法初始化動畫

/**     * 裝載動畫     */    private void setupAnimations() {    //角度動畫    /**     * target The object whose property is to be animated.被設定動畫的對象 * property The property being animated. 被設定動畫的屬性 * values A set of values that the animation will animate between over time. 設定的值 * 這個建構函式說明,對CircularAnimatedDrawable類對象,設定關於mCurrentGlobalAngle屬性的動畫 * 也就是線性給mCurrentGlobalAngle從0到360f賦值     */        mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, 360f);        //設定插入器        mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);        //設定動畫時間長度        mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);        //設定迴圈模式        mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);        //設定迴圈次數        mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);        //掃過動畫        //從快到慢地給mCurrentSweepAngle從0到(360f -MIN_SWEEP_ANGLE * 2)(也就是300)賦值        mObjectAnimatorSweep = ObjectAnimator.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 onAnimationCancel(Animator animation) {            }            @Override            public void onAnimationRepeat(Animator animation) {            //當尾追上頭,改變頭尾                toggleAppearingMode();            }        });    }

這裡實質上建立了兩種動畫,都是ObjectAnimator動畫,而ObjectAnimator繼承自ValueAnimator,所以說這兩個動畫,都是做提供當前值的作用,本身不改變控制項樣式

一個是mObjectAnimatorAngle,線性增長,用於計算起始角度

一個是mObjectAnimatorSweep,先快後慢增長,用於計算掃過的角度

我們要仔細觀察動畫的過程(可能很快,大家在測試時,可以加大動畫預設播放時間,便於觀察),注意到過程是這樣的,首先是快的追慢的,使圓弧不斷縮小(同時整個圓弧在移動,因為快慢兩頭都在動),追上以後(相距MIN_SWEEP_ANGLE = 30稱為追上),快的和慢的交換(也就是快的變成慢,慢的變成快),之後快的繼續追慢的,使圓弧不斷增大,追上以後,再次交換,重複上述過程。


設定好上面的動畫以後,我們來看ondraw()方法

 @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;                   }                /**         * 繪製圓弧                * oval :指定圓弧的外輪廓矩形地區。         * startAngle: 圓弧起始角度,單位為度。         * sweepAngle: 圓弧掃過的角度,順時針方向,單位為度。         * useCenter: 如果為True時,在繪製圓弧時將圓心包括在內,通常用來繪製扇形。     * paint: 繪製圓弧的畫板屬性,如顏色,是否填充等。         */        canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint);    }

首先是mModeAppearing這個屬性,初始值為false,然後我們注意到,在每次mObjectAnimatorSweep增長到300度時,就會對這個值取反,為什麼要這樣,繼續看。

我們類比一下最開始的過程,首先這時候mModeAppearing為false,mCurrentGlobalAngleOffset為0,

那麼startAngle(起始點) = mCurrentGlobalAngle維持線性增長,sweepAngle = mCurrentSweepAngle為非線性增長

然後到條件陳述式,會調用

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

這時startAngle為線性增長和非線性增長的和,所以本質為非線性增長(先快後慢)

而我們來考慮圓弧的終止點,等於startAngle+sweepAngle=360 + startAngle- MIN_SWEEP_ANGLE(注意前後startAngle意義不同,其實就是上面兩式相加)

可以看出終止點是線性增長的。

所以顯然,起始點會追上終止點,所以在第一次繪製中,運動地快的那個,其實是起始點,慢的是終止點

由於兩者速度差異,而我們繪製的是起始點到終止點的圓弧,自然就會逐漸縮小了。


OK,當起始點追上終止點(相距MIN_SWEEP_ANGLE = 30稱為追上)時,我們要交換兩者的增長方式,所以mModeAppearing被設定為true

我再來看true的時候,做了些什麼

float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset;
起始點為線性增長

float sweepAngle = mCurrentSweepAngle;
掃過弧度為非線性,導致終止點為非線性

else {            sweepAngle += MIN_SWEEP_ANGLE;                   }
還要加上MIN_SWEEP_ANGLE,因為交換完之後,mCurrentSweepAngle被置零了,而我們還要保持原來相差30度的樣式,所以要加上一個30度


就這樣,通過起始點和終止點計算方式的反覆交換,就可以產生圖上的效果。

OK,circular-progress-button到這裡就講解完畢了,下面貼一段對circular-progress-button進行使用的代碼,不進行過多說明,大家簡單看看

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);    //設定為旋轉樣式    circularProgressButton2.setIndeterminateProgressMode(true);    circularProgressButton2.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if (circularProgressButton2.getProgress() == 0) {                circularProgressButton2.setProgress(50);                } else if (circularProgressButton2.getProgress() == -1) {                circularProgressButton2.setProgress(0);                } else {                circularProgressButton2.setProgress(-1);                }            }        });        circularProgressButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {new GetDataTask().execute();}});    }    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);        }    }    }

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

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.