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;
        }
    }

        以上就是对自定义进度条的实现。掌握关键的方法后,我们可以根据自己的需求,轻松的自定义进度条。下一篇博客,将会介绍几种让进度条动起来的方式。

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