Android自定義ProgressBar

        好久不寫博客了,今天來總結一下自定義ProgressBar的實現。上週做一個遊戲資源的在線更新功能,設計給的加載進度條設計圖,是無法使用Android原生的ProgressBar來實現的。在百度和GitHub上搜了搜相關的資源,都不符合我的要求。於是,我只能自己去寫。先給一下最終的效果:

        上面就是我實現的ProgressBar的效果,與業務結合起來,分爲幾個階段:

(1)檢查配置階段:檢查本地現有的資源和服務器的配置,確定是否存在新的資源需要下載。

(2)下載階段:如果存在新的資源,開始下載新的資源。

(3)解壓縮階段:下載完成新資源後,開始解壓縮資源包。

一.擼代碼前的思考

        剛工作的時候,做的項目或者功能都有很長的開發週期。我們會在開發前充分的去做需求分析和設計,討論技術難點和可能遇到的一些坑。而且,由於開發週期長,允許我們在開發的過程中犯一些錯誤。不過,現在的工作,根本不會給我推倒重來的時間。所以,逐漸的,我養成了習慣,那就是在寫代碼之前先想一想如何去實現:

(1)我需要畫一個進度條,這個進度條至少包括已達到的進度和未達到的進度

(2)這個進度條需要和更新進度聯動,需要包括一個展示更新階段的文本

(3)這個進度條需要一個用於告知用戶百分比的文本

(4)這個進度條需要一個小滑塊,來使我們的進度條更美觀

二.開始擼代碼

        我們可以把這個進度條看做兩個主要部分:已經達到的進度和未達到的進度,還有進度文本和百分比展示,最後是一個滑塊。而且,這個進度條是個圓角的矩形(只只不過長度遠遠大於高度)。所以,我們的思路是,畫兩個圓角矩形。那麼,我們該如何確定我們進度條的各個元素從什麼地方開始畫,畫到哪個位置結束呢?這就需要我們簡單的計算一下。

1.計算未達到進度的位置,這裏其實就是進度條的背景

        mUnreachedRectF.left = getPaddingLeft();
        mUnreachedRectF.top = (getHeight() - mBarHeight) / 2.0f;
        mUnreachedRectF.right = getWidth() - getPaddingRight();
        mUnreachedRectF.bottom = (getHeight() + mBarHeight) / 2.0f;

(1)left是矩形的最左邊,就是我們畫布的最左邊,其實就是0。但是爲了美觀,我們不可能從畫布的最左邊開始畫的,我們可能會設置一個padding,所以,我們使用的初始x座標爲:paddingLeft

(2)top是我們矩形的最上面,我們取的座標是(畫布的高度-進度條的高度)/2。

(3)right是矩形的最右邊,取的座標是:畫布的寬度減去右邊的padding。

(4)bottom是矩形的底部,這裏可以對照top的計算,其實目的就是要讓我們的矩形處於畫布的中間。因此,以畫布高度的中間爲參考點,top就是畫布高度的一半減去進度條高度的一半,而bottom則是畫布高度的一半加上進度條高度的一半:

2.計算已達到的進度條的位置

        mReachedRectF.left = getPaddingLeft() + dp2px(2);
        mReachedRectF.top = (getHeight() - mBarHeight) / 2.0f + dp2px(2);
        mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight()) / 
        (getMax() * 1.0f) * getProgress() + getPaddingLeft();
        mReachedRectF.bottom = (getHeight() + mBarHeight) / 2.0f - dp2px(2);

(1)left:比起背景,我們更往右了2dp,因爲我們的已達到進度會比背景小一些,以實現嵌套的效果

(2)top:也是一樣,比背景更往下2dp

(3)right:這個雖然很長,其實就是進度條背景的長度去乘上當前的進度(例如當前的進度是80%,那其實就是背景的長度*80%),後面加上paddingleft,其實,還應該加上2dp,不過,我們可以忽略不計。

(4)bottom:比背景往上2dp

3.計算兩個文本區域的位置

(1)比起畫矩形我們需要告訴canvas我們的上下左右位置,畫文本我們只需要告訴canvas最左邊和最上邊,下面是百分比的顯示位置:

canvas.drawText(mCurrentDrawText, mUnreachedRectF.right - dp2px(36), getHeight() / 2 + 2 * mBarHeight, mPaint);

        最左邊,就是矩形的最右邊減去一個值,這個值就是我們百分比這幾個數字的大體寬度。最上邊,就是畫布高度的一半再加上一塊高度,在這裏我取了兩倍的進度條高度。這樣就可以讓進度條百分比最右側與進度條終點大致對齊,讓其位於進度條下方。

 (2)下面是顯示更新階段的文本的位置計算:

canvas.drawText(mHint, mReachedRectF.left, getHeight() / 2 + 2 * mBarHeight, mPaint);

        最左邊的位置,就是矩形的最左邊,最上邊,跟百分比的最上面一樣,不再贅述。

4.計算滑塊的位置

        最後,是計算滑塊的位置,其實它的位置也比較好計算。然後我們調用canvas.drawBitmap即可:

mSliderLeft = (getWidth() - getPaddingLeft() - getPaddingRight()) / (getMax() * 1.0f) * getProgress() + getPaddingLeft() - mSliderWidth / 2;
mSliderTop = (getHeight() - mSliderHeight) / 2.0f;
canvas.drawBitmap(mSlider, mSliderLeft, mSliderTop, mPaint);

left:其實就是已達成進度的最右邊減去滑塊寬度的一半,也就是讓滑塊的中間與已達成進度大致重合。

top:與進度條的top一樣的計算。爲了美觀,我們會讓滑塊的位置稍微超出進度條頂部一點點。

5.繪製

(1)繪製進度條背景(未達成進度):

private void drawUnReachedRectF(Canvas canvas) {
        mPaint.setShader(null);
        mPaint.setColor(mUnreachedBarColor);
        canvas.drawRoundRect(mUnreachedRectF, mRadius, mRadius, mPaint);
    }

(2)繪製進度條已達到部分:

private void drawReachedRectF(Canvas canvas) {
        mLinearGradient = new LinearGradient(0, 0, mReachedRectF.right,
                0,
                startColor,
                endColor,
                Shader.TileMode.CLAMP);
        mPaint.setShader(mLinearGradient);
        canvas.drawRoundRect(mReachedRectF, mRadius, mRadius, mPaint);
    }

 (3)繪製進度條進度:

    private void drawProgress(Canvas canvas) {
        mPaint.setShader(null);
        mPaint.setColor(mTextColor);
        mPaint.setTextSize(dp2px(20f));
        String mCurrentDrawText = new DecimalFormat("#").format(getProgress() * 100 / getMax());
        mCurrentDrawText = mCurrentDrawText + "%";
        canvas.drawText(mCurrentDrawText, mUnreachedRectF.right - dp2px(36), getHeight() / 2 + 2 * mBarHeight, mPaint);
    }

(4)繪製進度條文本:

    private void drawHint(Canvas canvas) {
        mPaint.setShader(null);
        mPaint.setColor(mTextColor);
        mPaint.setTextSize(dp2px(12f));
        if (mProgress <= 20) {
            mHint = "正在檢查配置...";
        } else if (mProgress > 20 && mProgress <= 60) {
            mHint = "正在下載資源...";
        } else {
            mHint = "正在解壓,本過程不消耗流量...";
        }
        canvas.drawText(mHint, mReachedRectF.left, getHeight() / 2 + 2 * mBarHeight, mPaint);
    }

(5)繪製滑塊:

    private void drawSlider(Canvas canvas) {
        mPaint.setShader(null);
        mPaint.setColor(Color.WHITE);
        canvas.drawBitmap(mSlider, mSliderLeft, mSliderTop, mPaint);
    }

三.其他注意事項

1.滑塊bitmap的獲取

使用BitmapFactory獲取bitmap,然後創建指定大小的滑塊:

mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.idiom_load_cloud);
mSlider = Bitmap.createScaledBitmap(mBitmap, (int) dp2px(26), (int) dp2px(16), true);

2.bitmap的回收

爲了防止內存泄漏,需要在合適的時機銷燬bitmap:

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mBitmap != null && !mBitmap.isRecycled()) {
            mBitmap.recycle();
            mBitmap = null;
        }
        if (mSlider != null && !mSlider.isRecycled()) {
            mSlider.recycle();
            mSlider = null;
        }
    }

        以上就是對自定義進度條的實現。掌握關鍵的方法後,我們可以根據自己的需求,輕鬆的自定義進度條。下一篇博客,將會介紹幾種讓進度條動起來的方式。

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