實現一個Material效果的ProgressBar

rowandjj發表於2015-08-09

先來看看效果:
這裡寫圖片描述

分析下難點:
1. 動畫的實現;
2. 邊界的控制;
3. 狀態儲存與恢復;
4. 兩種狀態的實現,loading狀態(不停旋轉)、progress狀態。

分別來看下。
1. 動畫如何實現:
將動畫進行拆解,可以發現它其實是一個弧不斷變長變短的一個過程+弧本身在繞圓形轉動兩部分組成。
所以可以分開來處理,弧度變長變短可以通過canvas.drawArc的引數startAngle/SweeepAngle控制,只要改變這兩個值即可實現效果。怎麼改變?有幾種方案,1是通過hander+thread;2是通過View.post();3是通過PropertyAnimation.
弧本身繞圓心運動可以通過Canvas.rotate實現。

private static final float delta = 6f;
    private float temp = 0;
    class AnimRunnable implements Runnable{
        @Override
        public void run() {
            if (mStartAngle == temp) {
                mSweepAngle += delta;
            }
            if (mSweepAngle >= 280 || mStartAngle > temp) {
                mStartAngle += delta;
                if(mSweepAngle > 20) {
                    mSweepAngle -= delta;
                }
            }
            if (mStartAngle > temp + 280) {
                temp = mStartAngle;
                mStartAngle = temp;
                mSweepAngle = 20;
            }
            postInvalidate();
            postDelayed(this,mSpinSpeed);
        }
    }

2.邊界的控制:
需要在onMeasure中控制。在onSizeChanged方法中可以拿到最終的width、height,通過width/height就可以控制progressbar的邊界了。
需要注意的是,邊界需要是正方形的,所以得考慮寬高不相等的情況以及四個方向padding的大小。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //計算自己需要的寬度和高度
        int width = mCircleRadius*2;
        int height = mCircleRadius*2;
        //考慮父容器的測量規則
        setMeasuredDimension(getResolvedSize(width, widthMeasureSpec), getResolvedSize(height, heightMeasureSpec));
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        int paddingRight = getPaddingRight();
        int paddingBottom = getPaddingBottom();
        //簡化處理,以最大的padding作為padding
        int padding = Math.max(Math.max(paddingLeft, paddingRight), Math.max(paddingTop, paddingBottom));
        int diameter;
        //保證bounds是一個正方形
        if(w >= h){
            diameter = h;
            mBounds = new RectF(padding+mBarWidth+(w-h)/2,padding+mBarWidth,diameter-padding-mBarWidth+(w-h)/2,diameter-padding-mBarWidth);
        }else if(w < h){
            diameter = w;
            mBounds = new RectF(padding+mBarWidth,padding+mBarWidth+(h-w)/2,diameter-padding-mBarWidth,diameter-padding-mBarWidth+(h-w)/2);
        }
    }

3.狀態的儲存與恢復:
progressbar的狀態不能因為橫豎屏切換等問題丟失,所以需要通過重寫onSaveInstanceState/onRestoreInstanceState來儲存/恢復狀態.

@Override
    protected void onRestoreInstanceState(Parcelable state) {
        if(! (state instanceof SavedState)){
            super.onRestoreInstanceState(state);
            return;
        }
        //先恢復父類的狀態
        SavedState savedState = (SavedState) state;
        super.onRestoreInstanceState(savedState.getSuperState());
        //在恢復自己的狀態
        this.mCurMode = savedState.mCurMode == 0 ? Mode.INDETERMINATE : Mode.DETERMINATE;
        this.mRimWidth = savedState.mRimWidth;
        this.mRimColor = savedState.mRimColor;
        this.mBarColor = savedState.mBarColor;
        this.mBarWidth = savedState.mBarWidth;
        this.showRim = savedState.showRim;
        this.isAnimStart = savedState.isAnimStart;
        this.mProgress = savedState.mProgress;
    }
    @Override
    protected Parcelable onSaveInstanceState() {
        //相當於是做了一層包裝
        //先儲存父類的狀態,然後包裝,再儲存自己的狀態
        Parcelable parcelable = super.onSaveInstanceState();
        SavedState savedState = new SavedState(parcelable);
        savedState.mCurMode = (this.mCurMode == Mode.INDETERMINATE) ? 0 : 1;
        savedState.mRimWidth = this.mRimWidth;
        savedState.mRimColor = this.mRimColor;
        savedState.mBarColor = this.mBarColor;
        savedState.mBarWidth = this.mBarWidth;
        savedState.showRim = this.showRim;
        savedState.isAnimStart = this.isAnimStart;
        savedState.mProgress = this.mProgress;
        return savedState;
    }

4.兩種狀態的實現:
自然是通過一個變數記錄當前模式,在onDraw中通過判斷模式進行不同的繪製操作。

地址:​https://github.com/Rowandjj/MaterialProgressBar/

相關文章