自定義實現橫向圓角進度條——簡易版
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();
}
}