1. 程式人生 > >自定義實現橫向圓角進度條——簡易版

自定義實現橫向圓角進度條——簡易版

在這裡插入圖片描述 UI 說需要實現這樣圓角橫向進度條,好,於是我就去屁顛屁顛的 Google。下面就是我的辛酸歷程。

1、 設定 ProgressBar 的 android:progressDrawable 屬性

首先找到的一種實現方法就是為 ProgressBar 設定 android:progressDrawable 屬性,類似於 Progress內外圓角進度條 這篇文章裡面說的。

實現起來比較簡單方便,但是後來在測試的時候就會發現問題,比如說在 progress 值很小的時候(max == 100 && progress == 1),進度條的顯示就會出現如下情況: 在這裡插入圖片描述 所以,不行!不行!不行!

2、 自定義 View 來實現

2.1 模仿輪子階段

首先,我還是去了 GayHub 去找看有沒有現成的或者類似的輪子,畢竟我還是喜歡偷懶的。

	@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //畫背景
        RectF bgRectF = new RectF(0, 0, mViewWidth, mViewHeight);
        canvas.drawRoundRect(bgRectF, mViewHeight / 2, mViewHeight / 2, mBackgroundPaint);
        //畫進度條
        int width = (int) (mViewWidth * (mProgress * 1.0f / mMaxProgress) + 0.5);
        if (width <= mViewHeight) {//圓形
            canvas.drawCircle(width / 2, mViewHeight / 2, width / 2, mProgressPaint);
        } else {
            RectF progressRectF = new RectF(0, 0, width, mViewHeight);
            canvas.drawRoundRect(progressRectF, mViewHeight / 2, mViewHeight / 2, mProgressPaint);
        }
    }

其實現原理,就是先用個 canvas.drawRoundRect 畫背景(即圖中灰色部分),然後根據進度值,如果是還沒有超過圓弧的區域時,就直接畫一個圓,否則就同樣畫一個 RoundRect。 這樣,在進度值小的時候,就會感覺怪怪的,因為它右邊的弧沒有契合。 在這裡插入圖片描述

2.2 改造輪子階段

於是乎,追求完美主義的我(實際上是混不過產品眼睛)就犯軸了,怎麼才能實現下面那樣更加完美的效果呢? 在這裡插入圖片描述

首先我想到的是,黃色進度區域用畫橢圓來實現,但是苦逼的是以我現在的能力,無法算出下圖示記出的兩個點的 y 座標。(當然了,賽貝爾曲線我更加 Hold 不住) 在這裡插入圖片描述

於是乎,我就想,我既然無法直接畫出黃色進度條的部分,那麼我可以再畫一個跟背景一樣的 RoundRect,然後去移動它,再利用 android Xfermode 去實現只顯示重疊的部分(也就是進度條部分)以及原本的背景部分。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    int width = (int) (mViewWidth * (mProgress * 1.0f / mMaxProgress) + 0.5f);
    //int saved = canvas.saveLayer(0,0,mViewWidth,mViewHeight, null, Canvas.ALL_SAVE_FLAG); 設定生效的區域
    //canvas.saveLayer() 有兩個過載函式,如果沒有指定 Layer 的區域,則預設為當前 View 的範圍
    int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);//開始
    onDrawPaint.setColor(colorBcg);
    //畫背景
    canvas.drawRoundRect(new RectF(0, 0, mViewWidth, mViewHeight), mViewHeight / 2, mViewHeight / 2, onDrawPaint);
    onDrawPaint.setXfermode(xfermode);
    onDrawPaint.setColor(colorProgress);
    //畫進度條
    canvas.drawRoundRect(new RectF(width-mViewWidth, 0, width, mViewHeight), mViewHeight / 2, mViewHeight / 2, onDrawPaint);
    onDrawPaint.setXfermode(null);
    canvas.restoreToCount(saved);//結束
}

上面就是核心程式碼了,主要是利用 xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP); 來實現的。

注意,畫背景和進度條的邏輯需要根據 Xfermode 設定的模式結合畫的順序來弄,且需要包含在註釋標註的 “開始” 與 “結束” 之間。

題外話,我開始天真的以為 Xfermode 只適用於 drawBitmap…

參考文章:

完整的原始碼實現

public class RoundedProgressBar extends View {

    private int mViewWidth;
    private int mViewHeight;
    private int mMaxProgress = 100;//最大進度
    private int mProgress = 0;//當前進度

    Paint onDrawPaint;
    Xfermode xfermode;

    @ColorInt
    private int colorBcg;
    @ColorInt
    private int colorProgress;

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

    public RoundedProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        setBackgroundResource(0);//移除設定的背景資源

        xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP);

        onDrawPaint = new Paint();
        onDrawPaint.setFilterBitmap(false);

        colorBcg = 0xff999999;
        colorProgress = 0xFFFFCC44;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = (int) (mViewWidth * (mProgress * 1.0f / mMaxProgress) + 0.5f);
//        int saved = canvas.saveLayer(0,0,mViewWidth,mViewHeight, null, Canvas.ALL_SAVE_FLAG); //設定生效的區域
        int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
        onDrawPaint.setColor(colorBcg);
        canvas.drawRoundRect(new RectF(0, 0, mViewWidth, mViewHeight), mViewHeight / 2, mViewHeight / 2, onDrawPaint);
        onDrawPaint.setXfermode(xfermode);
        onDrawPaint.setColor(colorProgress);
        canvas.drawRoundRect(new RectF(width-mViewWidth, 0, width, mViewHeight), mViewHeight / 2, mViewHeight / 2, onDrawPaint);
        onDrawPaint.setXfermode(null);
        canvas.restoreToCount(saved);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mViewWidth = w;
        mViewHeight = h;
        invalidate();
    }

    /**
     * 設定當前進度
     *
     * @param progress
     */
    public void setProgress(int progress) {
        progress = progress >= 0 ? progress : 0;
        progress = progress <= mMaxProgress ? progress : mMaxProgress;
        mProgress = progress;
        invalidate();
    }

    /**
     * 設定最大進度
     *
     * @param maxProgress
     */
    public void setMaxProgress(int maxProgress) {
        mMaxProgress = maxProgress;
        invalidate();
    }
}