likes
comments
collection
share

【自定义View】关于多色进度条GradientProgressView的绘制

作者站长头像
站长
· 阅读数 24

目录

  • 前言
  • 一、GradientProgressView准备工作
  • 二、绘制
  • 1.初始化属性
  • 2.测量宽高
  • 3.根据情况来画渐变色进度
  • 4.绘制
  • 总结

前言

我们常使用shape实现渐变色,但是shape的极限却只有三色,如果有超过三种颜色的View的要求,那么我们就不得不去自定义View来实现这个需求。不过它的难度也是很低的,实现起来很轻松。接下来我分析一下我的思路。 先上图: 【自定义View】关于多色进度条GradientProgressView的绘制

一、GradientProgressView准备工作

  1. color:渐变进度条的多种颜色
  2. 图形:如果是圆柱形就使用RectF,矩形就用Rect,一个用于显示原色进度,一个用于显示渐变进度条
  3. 渐变染色器:LinearGradient
  4. 宽高:mHeight、mWidth
  5. 圆柱的半径:mRadius 属性代码如下
    private int colorStart; // 颜色:进度1
    private int colorEnd; // 颜色:进度5
    private int colorStart2;// 颜色:进度2
    private int colorEnd0;// 颜色:进度4
    private int colorCenter;// 颜色:进度3
    private Paint mGradientPaint;// 渐变画笔
    private Paint mPaint;// 默认画笔
    private RectF mBackGroundRect; // 原色图形
    private RectF mGradientRect;// 染色图形
    private LinearGradient backGradient;// 渐变色

    private int mHeight;
    private int mWidth;
    private int mRadius;

二、绘制

1.初始化属性

因为这个自定义View只为了满足公司需求,所有我并未将其的功能扩展开来,所有没有自定义属性,都是写死在代码中,后续有空再优化。 主要三步骤:初始化颜色,初始化画笔,初始化图形。 最好先写死需要的宽高,避免在使用的使用还未测量就进行绘制

    private void init(Context context, @Nullable AttributeSet attrs){
        // 初始化画笔
        mGradientPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        colorStart = Color.parseColor("#63DC80");
        colorStart2 = Color.parseColor("#A8DB5B");
        colorCenter = Color.parseColor("#FFCE48");
        colorEnd0 = Color.parseColor("#FE7B39");
        colorEnd = Color.parseColor("#E8402B");
        mPaint.setColor(Color.parseColor("#EDEDED"));
        if (attrs!=null){

        }
        //设置抗锯齿
        mGradientPaint.setAntiAlias(true);
        //设置防抖动
        mGradientPaint.setDither(true);
        mGradientPaint.setStyle(Paint.Style.FILL);
        //设置抗锯齿
        mPaint.setAntiAlias(true);
        //设置防抖动
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.FILL);
        mBackGroundRect = new RectF();
        // 防止渲染还未完成就显示,使得宽高还未测量。先在初始化这里定义好跟xml中相同的宽高
        mWidth = (int) (Resources.getSystem().getDisplayMetrics().widthPixels-context.getResources().getDimension(R.dimen.base_dp_56));
        mHeight = (int) (context.getResources().getDimension(R.dimen.base_dp_8));
    }

2.测量宽高

这一步一般来说已经是有标准的代码模板了,所以我这里也没什么创新的地方,照抄就完事了,这里便浅浅介绍一下测量的步骤吧。 其流程是:

  1. 先用getMode()和getSize()获取测量模式和宽高,再根据不同的模式获取宽高。
  2. 模式有三种:EXACTLY(精确值模式)、AT_MOST(最大值模式)、UNSPECIFIED(未指定模式)
  3. 如果是EXACTLY(精确值模式),那么该属性就是确定的,即当前View的宽高
  4. 如果是AT_MOST(最大值模式),那么该属性就是父容器的宽高
  5. 如果是UNSPECIFIED(未指定模式),那么该属性要么是0,要么是EXACTLY(精确值模式)下当前view的属性。 用图来说明,即为: 【自定义View】关于多色进度条GradientProgressView的绘制
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        /**
         * 从提供的测量规格中获取测量模式和宽高值
         */
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        Log.d("TAG", "onMeasure: "+widthMode);
        Log.d("TAG", "onMeasure: "+widthSize);
        if (widthMode == MeasureSpec.EXACTLY) {
            //测量模式是EXACTLY,说明view的宽度值是确定的
            mWidth = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            /**
             * 测量模式是AT_MOST,说明view的宽度值最大是父View能提供的大小
             * 比如开发者设置wrap_content,那可能是希望填充父View
             */
            mWidth = Math.max(mWidth,widthSize);
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            mHeight = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            mHeight = Math.min(mHeight,heightSize);
        }
        /**
         * 为了View能更好的展示,需要设置下宽高比
         */
        float times = mWidth / (float)mHeight;
        if (times < 2.5 ) {
            if (mHeight < DensityUtil.dip2px(getContext(),60)) {
                mHeight = DensityUtil.dip2px(getContext(),60);
            }
            mWidth = (int) (mHeight * 2.5);
        }
        mRadius = (int) getContext().getResources().getDimension(R.dimen.base_dp_20);
        //保存计算后的宽高
        setMeasuredDimension(mWidth,mHeight);
    }

3.根据情况来画渐变色进度

这里因为需求原因,我将原本的进度条五色平分进度,所以这里用到已经测量好的宽度属性mWidth 。 这里的流程也很简单,简要说下

  1. 暴露方法给外界调用
  2. 获取进度
  3. 根据进度来给渐变染色器:LinearGradient初始化
  4. 同时给圆柱形RectF初始化宽高
  5. 最后调用invalidate()重新绘制该View
 public void setAirType(String degree){
        int level = mWidth / 5;
        switch (degree){
            case "1":
                mGradientRect = new RectF(0,0,level,mHeight);
                // 因为渐变色集合,需要颜色大于等于2,这里取巧,设置相同颜色
                backGradient = new LinearGradient(0, 0, level, 0, new int[]{colorStart,colorStart}, null, Shader.TileMode.CLAMP);
                break;
            case "2":
                mGradientRect = new RectF(0,0,level*2,mHeight);
                backGradient = new LinearGradient(0, 0, level*2, 0, new int[]{colorStart, colorStart2}, null, Shader.TileMode.CLAMP);
                break;
            case "3":
                mGradientRect = new RectF(0,0,level*3,mHeight);
                backGradient = new LinearGradient(0, 0, level*3, 0, new int[]{colorStart, colorStart2,colorCenter}, null, Shader.TileMode.CLAMP);
                break;
            case "4":
                mGradientRect = new RectF(0,0,level*4,mHeight);
                backGradient = new LinearGradient(0, 0, level*4, 0, new int[]{colorStart, colorStart2,colorCenter,colorEnd0}, null, Shader.TileMode.CLAMP);
                break;
            case "5":
                mGradientRect = new RectF(0,0,mWidth,mHeight);
                backGradient = new LinearGradient(0, 0, mWidth, 0, new int[]{colorStart, colorStart2,colorCenter,colorEnd0,colorEnd}, null, Shader.TileMode.CLAMP);
                break;
        }
        invalidate();
    }

4.绘制

这里我采用最简单粗暴的方法,直接叠了两个圆柱进度条,一个是原色的进度条,一个是渐变色的进度条,这里大家可以自行优化,即在backGradient 属性初始化的时候进度调整等。

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawRoundRect(canvas);
    }

    private void drawRoundRect(Canvas canvas) {
        mBackGroundRect.left = 0;
        mBackGroundRect.top = 0;
        mBackGroundRect.bottom = mHeight;
        mBackGroundRect.right = mWidth;
        // 绘制底色圆角矩形
        canvas.drawRoundRect(mBackGroundRect, mRadius, mRadius, mPaint);
        // 渐变绘图
        mGradientPaint.setShader(backGradient);
        //绘制背景 圆角矩形
        if (mGradientRect != null) {
            canvas.drawRoundRect(mGradientRect, mRadius, mRadius, mGradientPaint);
        }
    }

完整代码贴上,方便各位友友使用

public class GradientProgressView extends View {
    private int colorStart; // 颜色:进度1
    private int colorEnd; // 颜色:进度5
    private int colorStart2;// 颜色:进度2
    private int colorEnd0;// 颜色:进度4
    private int colorCenter;// 颜色:进度3
    private List<Integer> colorList; // 颜色集合
    private List<Float> colorSize; // 颜色位置
    private Paint mGradientPaint;// 渐变画笔
    private Paint mPaint;// 默认画笔
    private RectF mBackGroundRect; // 原色图形
    private RectF mGradientRect;// 染色图形
    private LinearGradient backGradient;// 渐变色

    private int mHeight;
    private int mWidth;
    private int mRadius;

    public GradientProgressView(Context context) {
        super(context);
    }

    public GradientProgressView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context,attrs);
    }

    public GradientProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private void init(Context context, @Nullable AttributeSet attrs){
        // 初始化画笔
        mGradientPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        colorStart = Color.parseColor("#63DC80");
        colorStart2 = Color.parseColor("#A8DB5B");
        colorCenter = Color.parseColor("#FFCE48");
        colorEnd0 = Color.parseColor("#FE7B39");
        colorEnd = Color.parseColor("#E8402B");
        mPaint.setColor(Color.parseColor("#EDEDED"));
        if (attrs!=null){

        }
        //设置抗锯齿
        mGradientPaint.setAntiAlias(true);
        //设置防抖动
        mGradientPaint.setDither(true);
        mGradientPaint.setStyle(Paint.Style.FILL);
        //设置抗锯齿
        mPaint.setAntiAlias(true);
        //设置防抖动
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.FILL);
        mBackGroundRect = new RectF();
        // 防止渲染还未完成就显示,使得宽高还未测量。先在初始化这里定义好跟xml中相同的宽高
        mWidth = (int) (Resources.getSystem().getDisplayMetrics().widthPixels-context.getResources().getDimension(R.dimen.base_dp_56));
        mHeight = (int) (context.getResources().getDimension(R.dimen.base_dp_8));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        /**
         * 从提供的测量规格中获取测量模式和宽高值
         */
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        Log.d("TAG", "onMeasure: "+widthMode);
        Log.d("TAG", "onMeasure: "+widthSize);
        if (widthMode == MeasureSpec.EXACTLY) {
            //测量模式是EXACTLY,说明view的宽度值是确定的
            mWidth = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            /**
             * 测量模式是AT_MOST,说明view的宽度值最大是父View能提供的大小
             * 比如开发者设置wrap_content,那可能是希望填充父View
             */
            mWidth = Math.max(mWidth,widthSize);
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            mHeight = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            mHeight = Math.min(mHeight,heightSize);
        }
        /**
         * 为了View能更好的展示,需要设置下宽高比
         */
        float times = mWidth / (float)mHeight;
        if (times < 2.5 ) {
            if (mHeight < DensityUtil.dip2px(getContext(),60)) {
                mHeight = DensityUtil.dip2px(getContext(),60);
            }
            mWidth = (int) (mHeight * 2.5);
        }
        mRadius = (int) getContext().getResources().getDimension(R.dimen.base_dp_20);
        //保存计算后的宽高
        setMeasuredDimension(mWidth,mHeight);
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
          mWidth = w;
          mHeight = h;
    }
    public void setAirType(String degree){
        int level = mWidth / 5;
        switch (degree){
            case "1":
                mGradientRect = new RectF(0,0,level,mHeight);
                // 因为渐变色集合,需要颜色大于等于2,这里取巧,设置相同颜色
                backGradient = new LinearGradient(0, 0, level, 0, new int[]{colorStart,colorStart}, null, Shader.TileMode.CLAMP);
                break;
            case "2":
                mGradientRect = new RectF(0,0,level*2,mHeight);
                backGradient = new LinearGradient(0, 0, level*2, 0, new int[]{colorStart, colorStart2}, null, Shader.TileMode.CLAMP);
                break;
            case "3":
                mGradientRect = new RectF(0,0,level*3,mHeight);
                backGradient = new LinearGradient(0, 0, level*3, 0, new int[]{colorStart, colorStart2,colorCenter}, null, Shader.TileMode.CLAMP);
                break;
            case "4":
                mGradientRect = new RectF(0,0,level*4,mHeight);
                backGradient = new LinearGradient(0, 0, level*4, 0, new int[]{colorStart, colorStart2,colorCenter,colorEnd0}, null, Shader.TileMode.CLAMP);
                break;
            case "5":
                mGradientRect = new RectF(0,0,mWidth,mHeight);
                backGradient = new LinearGradient(0, 0, mWidth, 0, new int[]{colorStart, colorStart2,colorCenter,colorEnd0,colorEnd}, null, Shader.TileMode.CLAMP);
                break;
        }
        invalidate();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawRoundRect(canvas);
    }

    private void drawRoundRect(Canvas canvas) {
        mBackGroundRect.left = 0;
        mBackGroundRect.top = 0;
        mBackGroundRect.bottom = mHeight;
        mBackGroundRect.right = mWidth;
        // 绘制底色圆角矩形
        canvas.drawRoundRect(mBackGroundRect, mRadius, mRadius, mPaint);
        // 渐变绘图
        mGradientPaint.setShader(backGradient);
        //绘制背景 圆角矩形
        if (mGradientRect != null) {
            canvas.drawRoundRect(mGradientRect, mRadius, mRadius, mGradientPaint);
        }
    }
}

xml调用

        <com.itaem.blviewtest.GradientProgressView
            android:id="@+id/gradientProgress"
            android:layout_width="match_parent"
            android:layout_height="@dimen/base_dp_8"
            android:layout_marginTop="12dp"
            android:layout_marginHorizontal="@dimen/base_dp_44"/>

总结

还是因为时间问题,该自定义View只是单纯满足需求,并没有太大的扩展性功能,后续有时间我也行会出一篇后续将自定义View补上,实现一个功能性较为强大的渐变色进度条。 自定义View的魅力就是当你实现功能需求的时候的满足感,不亚于以前做完一道数学大题。那种畅快和欣喜,老实说我有点爱上自定义View了。

当然如果有可以优化的点,大家记得分享在评论区,一起进步。