Android自定義ProgressBar,內部顯示圖標和文字,文字被進度條覆蓋時會變色

不多說,先上效果圖:
效果圖

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慢慢顯示出來,達到一種動態增加的效果。注意這裏與前一種實現方式相比還需要多準備一套白色的圖標,具體實現可以自行動手嘗試。

最後附上第一種實現的源碼下載

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