手把手教你實現一個安卓環形進度條

背景:

最近做了個如圖所示環形進度條,下面來記錄一下實現過程,廢話不多說,先上圖
這是進度條
除了圖中所示的樣子之外,還實現了進度自動增長,點擊復位

所用到的知識

  • 基礎的安卓view的繪製
  • 基礎的安卓屬性動畫

怎麼做:

  • 首先,這個進度條由三部分組成:1.淺灰背景,2.白色進度,3.中間一個圖片
  • 所以就分別畫這三部分就可以了,從最下面開始畫

開始:

class KsFloatProgressView extends View {
    private static final float STROKE_WIDTH = 2f;
    private static final float BITMAP_WIDTH = 20f;
    private Context mContext;
    private int mWidth;
    private int mHeight;
    private Paint mBgPaint;
    private Paint mProgressPaint;
    private float mCircleRadius;
    private Bitmap mCmBitmap;
    private Rect mSrcRect;
    private Rect mDestRect;
    private Path mCirclePath;
    private Path mStrokePath;
    private int mBitmapMargin;
    private int mBitmapWidth;
    private int mStrokeWidth;
    private OnCompleteListener mOnCompleteListener;
    private float mProgress;
    private ObjectAnimator mProgressAnim;
    private int mAllProgress;


    public KsFloatProgressView(Context context) {
        this(context, null);
    }

    public KsFloatProgressView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public KsFloatProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }
    
    private void init() {
        //背景畫筆
        mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBgPaint.setStyle(Paint.Style.STROKE);
        mBgPaint.setColor(Color.WHITE);
        mBgPaint.setAlpha(36);
        mBgPaint.setStrokeWidth(DimenUtils.dp2px(mContext, STROKE_WIDTH));
        //進度畫筆
        mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mProgressPaint.setStyle(Paint.Style.STROKE);
        mProgressPaint.setColor(Color.WHITE);
        mProgressPaint.setStrokeWidth(DimenUtils.dp2px(mContext, STROKE_WIDTH));
        mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
        //初始化圖片
        mCmBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_ks_float_cm);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        mCircleRadius = w / 2f - DimenUtils.dp2px(mContext, STROKE_WIDTH);
        mSrcRect = new Rect(0, 0, mCmBitmap.getWidth(), mCmBitmap.getHeight());
        mBitmapMargin = DimenUtils.dp2px(mContext, 5);
        mBitmapWidth = DimenUtils.dp2px(mContext, BITMAP_WIDTH);
        mStrokeWidth = DimenUtils.dp2px(mContext, STROKE_WIDTH);
        mDestRect = new Rect(mBitmapMargin, mBitmapMargin, mBitmapMargin + mBitmapWidth, mBitmapMargin + mBitmapWidth);
        mCirclePath = new Path();
        mCirclePath.addCircle(mWidth / 2f, mHeight / 2f, mCircleRadius, Path.Direction.CW);
        mStrokePath = new Path();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mStrokePath.addArc(mStrokeWidth, mStrokeWidth, mWidth - mStrokeWidth, mHeight - mStrokeWidth, -90, 90);
        }
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //畫背景
        canvas.drawPath(mCirclePath, mBgPaint);
        //畫圖片
        canvas.drawBitmap(mCmBitmap, mSrcRect, mDestRect, mProgressPaint);
        //畫進度
        canvas.drawPath(mStrokePath, mProgressPaint);
    }

    public void setAllProgress(int allProgress){
        mAllProgress = allProgress;
    }

    public void startProgress() {
        post(new Runnable() {
            @Override
            public void run() {
                mProgressAnim = ObjectAnimator.ofFloat(KsFloatProgressView.this, "RealProgress", 0, 1);
                mProgressAnim.setDuration(mAllProgress*1000);
                mProgressAnim.setInterpolator(new LinearInterpolator());
                mProgressAnim.start();
                mProgressAnim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        if (mOnCompleteListener != null) {
                            mOnCompleteListener.onComplete();
                        }
                    }
                });
            }
        });
    }

    private void setRealProgress(float progress) {
        mProgress = progress;
        float angle = progress * 360;
        mStrokePath.reset();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mStrokePath.addArc(mStrokeWidth, mStrokeWidth, mWidth - mStrokeWidth, mHeight - mStrokeWidth, -90, angle);
        }
        invalidate();
    }

    public void resetProgress() {
        if (mProgressAnim != null && mProgressAnim.isRunning()) {
            mProgressAnim.cancel();
        }
        ObjectAnimator anim = ObjectAnimator.ofFloat(this, "RealProgress", mProgress, 0);
        anim.setDuration(300);
        anim.setInterpolator(new AccelerateInterpolator());
        anim.start();
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mProgressAnim.start();
            }
        });
    }

    public float getProgress() {
        if (mProgressAnim == null) {
            return 0;
        }
        return (float) mProgressAnim.getAnimatedValue();
    }

    public void setOnCompleteListener(OnCompleteListener listener) {
        mOnCompleteListener = listener;
    }

    public interface OnCompleteListener {
        void onComplete();
    }

}

好的,現在就開始從上向下分析代碼

  • 首先,因爲這個view不需要子view,所以直接繼承自view並重寫三個構造方法
  • 構造方法裏調用了init方法,這個方法主要用來設置畫筆屬性以及初始化圖片資源
  • 然後,在onSizeChanged方法裏獲取view的寬高,並由此計算出圓的半徑,根據獲得的view的寬高來設置圖片的寬高,以及位置
  • 新建一個path,add一個circle用來畫圓背景,再新建另一個path,add一個圓弧用來畫進度
  • 然後在ondraw方法裏畫背景,畫圖片,畫進度,當然一開始是沒有進度的

下面開始介紹讓進度動起來的方法

  • 首先通過外部調用setAllProgress來設置總的轉一圈的時間(秒)
  • 然後通過startProgress開始執行動畫,這個方法還是值得講一下的,忽略post,首先構建了一個ObjectAnimator,這個動畫的對象是this,沒錯,就是當前這個進度條控件,設置的屬性是RealProgress,這個屬性view裏是沒有的,所以我們待會兒需要寫一個設置這個屬性的方法,動畫值是從0~1的float
  • 然後設置動畫時間,就是剛剛設置的allprogress,再設置一個線性插值器,然後開始動畫
  • 下面這個setRealProgress就是在第二步屬性動畫設置的屬性“RealProgress”執行過程中會自動調用的方法,從這個方法的參數裏裏獲取進度(0~1的float),算出圓弧大小,再通知界面重繪
  • resetProgress方法將動畫重置

總結

很簡單的一個自定義view,用到了自定義View中的path,以及部分屬性動畫只是,上面貼出的代碼就是全部代碼,可根據實際需求進行修改

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章