不多說,先上效果圖:
Android內置的ProgressBar很雞肋,樣式過於簡單,現在需要一個內部能顯示圖標和文字的ProgressBar,同時,當進度條與圖標和文字重疊時,圖標和文字會相應變化顏色。這裏的關鍵效果就在於變色的處理,我這裏是通過設置相交模式實現這一效果的,應用了SrcIn模式,在圖標和文字上方繪製一層白色圖層,隨progress進度調整圖層寬度,當白色圖層與圖標和文字相交時,會將圖標和文字顯示爲白色。
下面看一下主要實現代碼:
public class CustomProgressBar extends ProgressBar {
private Context mContext;
private Paint mPaint;
private PorterDuffXfermode mPorterDuffXfermode;
private float mProgress;
private int mState;
// IconTextProgressBar的狀態
private static final int STATE_DEFAULT = 101;
private static final int STATE_DOWNLOADING = 102;
private static final int STATE_PAUSE = 103;
private static final int STATE_DOWNLOAD_FINISH = 104;
// IconTextProgressBar的文字大小(sp)
private static final float TEXT_SIZE_SP = 17f;
// IconTextProgressBar的圖標與文字間距(dp)
private static final float ICON_TEXT_SPACING_DP = 5f;
public CustomProgressBar(Context context) {
super(context, null, android.R.attr.progressBarStyleHorizontal);
mContext = context;
init();
}
public CustomProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
/**
* 設置下載狀態
*/
public synchronized void setState(int state) {
mState = state;
invalidate();
}
/**
* 設置下載進度
*/
public synchronized void setProgress(float progress) {
super.setProgress((int) progress);
mProgress = progress;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
switch (mState) {
case STATE_DEFAULT:
drawIconAndText(canvas, STATE_DEFAULT, false);
break;
case STATE_DOWNLOADING:
drawIconAndText(canvas, STATE_DOWNLOADING, false);
break;
case STATE_PAUSE:
drawIconAndText(canvas, STATE_PAUSE, false);
break;
case STATE_DOWNLOAD_FINISH:
drawIconAndText(canvas, STATE_DOWNLOAD_FINISH, true);
break;
default:
drawIconAndText(canvas, STATE_DEFAULT, false);
break;
}
}
private void init() {
setIndeterminate(false);
setIndeterminateDrawable(ContextCompat.getDrawable(mContext,
android.R.drawable.progress_indeterminate_horizontal));
setProgressDrawable(ContextCompat.getDrawable(mContext,
R.drawable.pb_shape_blue));
setMax(100);
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setTextAlign(Paint.Align.LEFT);
mPaint.setTextSize(MeasureUtil.sp2px(mContext, TEXT_SIZE_SP));
mPaint.setTypeface(Typeface.MONOSPACE);
mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
}
private void initForState(int state) {
switch (state) {
case STATE_DEFAULT:
setProgress(100);
mPaint.setColor(Color.WHITE);
break;
case STATE_DOWNLOADING:
mPaint.setColor(ContextCompat.getColor(mContext, R.color.pb_blue));
break;
case STATE_PAUSE:
mPaint.setColor(ContextCompat.getColor(mContext, R.color.pb_blue));
break;
case STATE_DOWNLOAD_FINISH:
setProgress(100);
mPaint.setColor(Color.WHITE);
break;
default:
setProgress(100);
mPaint.setColor(Color.WHITE);
break;
}
}
private void drawIconAndText(Canvas canvas, int state, boolean onlyText) {
initForState(state);
String text = getText(state);
Rect textRect = new Rect();
mPaint.getTextBounds(text, 0, text.length(), textRect);
if (onlyText) {
// 僅繪製文字
float textX = (getWidth() / 2) - textRect.centerX();
float textY = (getHeight() / 2) - textRect.centerY();
canvas.drawText(text, textX, textY, mPaint);
} else {
// 繪製圖標和文字
Bitmap icon = getIcon(state);
float textX = (getWidth() / 2) -
getOffsetX(icon.getWidth(), textRect.centerX(), ICON_TEXT_SPACING_DP, true);
float textY = (getHeight() / 2) - textRect.centerY();
canvas.drawText(text, textX, textY, mPaint);
float iconX = (getWidth() / 2) - icon.getWidth() -
getOffsetX(icon.getWidth(), textRect.centerX(), ICON_TEXT_SPACING_DP, false);
float iconY = (getHeight() / 2) - icon.getHeight() / 2;
canvas.drawBitmap(icon, iconX, iconY, mPaint);
if (state == STATE_DEFAULT) return;
Bitmap bufferBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas bufferCanvas = new Canvas(bufferBitmap);
bufferCanvas.drawBitmap(icon, iconX, iconY, mPaint);
bufferCanvas.drawText(text, textX, textY, mPaint);
// 設置混合模式
mPaint.setXfermode(mPorterDuffXfermode);
mPaint.setColor(Color.WHITE);
RectF rectF = new RectF(0, 0, getWidth() * mProgress / 100, getHeight());
// 繪製源圖形
bufferCanvas.drawRect(rectF, mPaint);
// 繪製目標圖
canvas.drawBitmap(bufferBitmap, 0, 0, null);
// 清除混合模式
mPaint.setXfermode(null);
if (!icon.isRecycled()) {
icon.isRecycled();
}
if (!bufferBitmap.isRecycled()) {
bufferBitmap.recycle();
}
}
}
private Bitmap getIcon(int state) {
Bitmap icon;
switch (state) {
case STATE_DEFAULT:
icon = BitmapFactory.decodeResource(getResources(), R.drawable.pb_download);
break;
case STATE_DOWNLOADING:
icon = BitmapFactory.decodeResource(getResources(), R.drawable.pb_pause_blue);
break;
case STATE_PAUSE:
icon = BitmapFactory.decodeResource(getResources(), R.drawable.pb_continue_blue);
break;
default:
icon = BitmapFactory.decodeResource(getResources(), R.drawable.pb_download);
break;
}
return icon;
}
private String getText(int state) {
String text;
switch (state) {
case STATE_DEFAULT:
text = getResources().getString(R.string.pb_download);
break;
case STATE_DOWNLOADING:
DecimalFormat decimalFormat = new DecimalFormat("#0.00");
text = decimalFormat.format(mProgress) + "%";
break;
case STATE_PAUSE:
text = getResources().getString(R.string.pb_continue);
break;
case STATE_DOWNLOAD_FINISH:
text = getResources().getString(R.string.pb_open);
break;
default:
text = getResources().getString(R.string.pb_download);
break;
}
return text;
}
private float getOffsetX(float iconWidth, float textHalfWidth, float spacing, boolean isText) {
float totalWidth = iconWidth + MeasureUtil.dip2px(mContext, spacing) + textHalfWidth * 2;
// 文字偏移量
if (isText) return totalWidth / 2 - iconWidth - spacing;
// 圖標偏移量
return totalWidth / 2 - iconWidth;
}
}
這裏再補充一種思路,相交模式中還有一種SrcATop,可以通過這種模式疊加繪製兩層形狀相同進度滿格的ProgressBar,藍色背景的在底部,顯示白色字體,白色背景的在頂部,顯示藍色字體,然後利用canvas的clip方法再結合progress進度,將白色的ProgressBar從左往右一點點裁減掉,令底部藍色的ProgressBar慢慢顯示出來,達到一種動態增加的效果。注意這裏與前一種實現方式相比還需要多準備一套白色的圖標,具體實現可以自行動手嘗試。