自定义实现横向圆角进度条——简易版

在这里插入图片描述
UI 说需要实现这样圆角横向进度条,好,于是我就去屁颠屁颠的 Google。下面就是我的辛酸历程。

1、 设置 ProgressBar 的 android:progressDrawable 属性

首先找到的一种实现方法就是为 ProgressBar 设置 android:progressDrawable 属性,类似于 Progress内外圆角进度条 这篇文章里面说的。

实现起来比较简单方便,但是后来在测试的时候就会发现问题,比如说在 progress 值很小的时候(max == 100 && progress == 1),进度条的显示就会出现如下情况:
在这里插入图片描述
所以,不行!不行!不行!

2、 自定义 View 来实现
2.1 模仿轮子阶段

首先,我还是去了 GayHub 去找看有没有现成的或者类似的轮子,毕竟我还是喜欢偷懒的。

在筛选了一番之后,找到一个类似的 【yongfengnice/CircleProgress】

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

    private String colorBcg;
    private String 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 = "#1B1E33";
        colorProgress = "#FFD600";
        //需要注意的是,如果颜色设置透明度,叠加后的图也会因为设置的透明度而变化,
        //且会因 xfermode 的各种模式而受到影响,导致跟预期不同
    }

    @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(Color.parseColor(colorBcg));
        canvas.drawRoundRect(new RectF(0, 0, mViewWidth, mViewHeight), mViewHeight / 2, mViewHeight / 2, onDrawPaint);
        onDrawPaint.setXfermode(xfermode);
        onDrawPaint.setColor(Color.parseColor(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();
    }
}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值