Android自定义View实现渐变色仪表盘效果

作者: ruancw | 来源:发表于2018-06-20 17:37 被阅读46次

    前言:最近一直在学自定义View的相关知识,感觉这在Android中还是挺难的一块,当然这也是每个程序员必经之路,正好公司项目要求实现类似仪表盘的效果用于直观的显示公司数据,于是就简单的写了个demo,记录实现的过程。上篇《Android自定义View实现圆弧进度效果》简单记录了圆弧及文字的绘制,渐变色的仪表盘效果将更加升入的介绍canvas及paint的使用(如画布旋转,paint的渐变色设置等)。

    知识梳理

    1.圆弧渐变色(SweepGradient)

    2.圆弧上刻度绘制

    3.指针指示当前数据位置(Bitmap)

    4.数据文本跟随弧度显示(drawTextOnPath)

    效果图:

    效果图

    1.继承自View

    (1)重写构造方法,初始化Paint

    public DashBoardView(Context context) {
        this(context, null); 
    }
    
    public DashBoardView(Context context, AttributeSet attrs) {
        this(context, attrs, 0); 
    }
    
    public DashBoardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
      init(); 
    }
    

    初始化相关Paint

    /**
    *  初始化Paint
    */ 
    private void init() {
        //设置默认宽高值
      defaultSize = dp2px(260);    
      //设置图片线条的抗锯齿
      mPaintFlagsDrawFilter = new PaintFlagsDrawFilter
                (0, Paint.*ANTI_ALIAS_FLAG* | Paint.*FILTER_BITMAP_FLAG*);
        
      //最外层圆环渐变画笔设置
      mOuterGradientPaint = new Paint(Paint.*ANTI_ALIAS_FLAG*);
      //设置圆环渐变色渲染
      mOuterGradientPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.*SRC_ATOP*));
      float position[] = {0.1f, 0.3f, 0.8f};
      Shader mShader = new SweepGradient(width / 2, radius, mColors, position);
      mOuterGradientPaint.setShader(mShader);
      mOuterGradientPaint.setStrokeCap(Paint.Cap.*ROUND*);
      mOuterGradientPaint.setStyle(Paint.Style.*STROKE*);
      mOuterGradientPaint.setStrokeWidth(30);   
     
      //最外层圆环刻度画笔设置
      mCalibrationPaint = new Paint(Paint.*ANTI_ALIAS_FLAG*);
      mCalibrationPaint.setColor(Color.*WHITE*);
      mCalibrationPaint.setStyle(Paint.Style.*STROKE*);    
    
      //中间圆环画笔设置
      mMiddlePaint = new Paint(Paint.*ANTI_ALIAS_FLAG*);
      mMiddlePaint.setStyle(Paint.Style.*STROKE*);
      mMiddlePaint.setStrokeCap(Paint.Cap.*ROUND*);
      mMiddlePaint.setStrokeWidth(5);
      mMiddlePaint.setColor(*GRAY_COLOR*);    
      
      //内层圆环画笔设置
      mInnerPaint = new Paint(Paint.*ANTI_ALIAS_FLAG*);
      mInnerPaint.setStyle(Paint.Style.*STROKE*);
      mInnerPaint.setStrokeCap(Paint.Cap.*ROUND*);
      mInnerPaint.setStrokeWidth(4);
      mInnerPaint.setColor(*GRAY_COLOR*);
      PathEffect mPathEffect = new DashPathEffect(new float[]{5, 5, 5, 5}, 1);
      mInnerPaint.setPathEffect(mPathEffect);    
    
      //外层圆环文本画笔设置
      mTextPaint = new Paint(Paint.*ANTI_ALIAS_FLAG*);
      mTextPaint.setColor(*GRAY_COLOR*);
      mTextPaint.setTextSize(dp2px(12));    
      
      //中间文字画笔设置
      mCenterTextPaint = new Paint(Paint.*ANTI_ALIAS_FLAG*);
      mCenterTextPaint.setTextAlign(Paint.Align.*CENTER*);    
      
      //中间圆环进度画笔设置
      mMiddleProgressPaint = new Paint(Paint.*ANTI_ALIAS_FLAG*);
      mMiddleProgressPaint.setColor(*GREEN_COLOR*);
      mMiddleProgressPaint.setStrokeCap(Paint.Cap.*ROUND*);
      mMiddleProgressPaint.setStrokeWidth(5);
      mMiddleProgressPaint.setStyle(Paint.Style.*STROKE*);    
      
      //指针图片画笔
      mPointerBitmapPaint = new Paint(Paint.*ANTI_ALIAS_FLAG*);
      mPointerBitmapPaint.setColor(*GREEN_COLOR*);    
      //获取指针图片及宽高
      mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.*pointer*);
      mBitmapHeight = mBitmap.getHeight();
      mBitmapWidth = mBitmap.getWidth(); 
    }
    

    注:

    A、最外层圆弧的渐变色使用的是SweepGradient类实现的,SweepGradient继承自Shader;

    B、注意渐变色的开始角度问题,如果跟圆弧起始角度不一致,记得使用矩阵转换进行旋转,再让paint去设置shader;

    C、SweepGradient的第3个参数int[] colors必须包含两个及以上颜色值,不然会报错;

    D、SweepGradient的第四个参数的数组大小必须和第三个参数的数组大小一样,也可以填入null。

    (2)重写onMeasure,用于测量view宽高

    onMeasure方法:

    @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(remeasure(widthMeasureSpec, defaultSize),
                remeasure(heightMeasureSpec, defaultSize)); 
    }
    

    remeasure方法:

    /**
    * 根据传入的值进行重新测量
    */
    public int remeasure(int measureSpec, int defaultSize) {
    
     int result;
     int specSize = MeasureSpec.getSize(measureSpec);
     switch (MeasureSpec.getMode(measureSpec)) {
            case MeasureSpec.*UNSPECIFIED*:
                //未指定
                result = defaultSize;
                break; 
            case MeasureSpec.*AT_MOST*:
                //设置warp_content时设置默认值
                result = Math.min(specSize, defaultSize);
                break; 
           case MeasureSpec.*EXACTLY*:
                //设置math_parent 和设置了固定宽高值
                result=specSize;
                break; 
          default:
                result = defaultSize;
      }
        return result; 
    }
    

    (3)重写onChange,用于获取view宽高

    在onChange方法中获取当前View的宽高及获取圆弧的半径,初始化圆弧的RectF等

    @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);    
        //确定View宽高
        width = w;
        height = h;    
        //圆环半径
        radius = width / 2;   
        //外层圆环
        float oval1 = radius - mOuterGradientPaint.getStrokeWidth() * 0.5f;
        mOuterRectF = new RectF(-oval1, -oval1, oval1, oval1);    
        //中间和内层圆环
        float oval2 = radius * 5 / 8;
        float oval3 = radius * 3 / 4;
        mInnerRectF = new RectF(-oval2 + dp2px(5), -oval2 + dp2px(5), oval2 - dp2px(5), oval2 - dp2px(5));
        mMiddleRectF = new RectF(-oval3 + dp2px(10), -oval3 + dp2px(10), oval3 - dp2px(10), oval3 - dp2px(10));    
        //中间进度圆环
        oval4 = radius * 6 / 8;
        mMiddleProgressRectF = new RectF(-oval4+ dp2px(10), -oval4+ dp2px(10), oval4- dp2px(10), oval4- dp2px(10)); 
    }
    

    (4)重写onDraw方法,用于绘制view

    @SuppressLint("DrawAllocation")
    @Override 
    protected void onDraw(Canvas canvas) {
      //设置画布绘图无锯齿
      canvas.setDrawFilter(mPaintFlagsDrawFilter);
      //绘制圆弧
      drawArc(canvas);
      //绘制圆弧上的刻度
      drawCalibration(canvas);
      //绘制跟随圆弧path的文字
      drawArcText(canvas);
      //绘制圆弧中心文字
      drawCenterText(canvas);
      //绘制当前bitmap指针指示进度
      drawBitmapProgress(canvas); 
    }
    

    2.Canvas绘制view

    mStartAngle=105f,mEndAngle=250f

    (1)绘制圆弧

    /**
    *  分别绘制外层 中间 内层圆环
    */
    private void drawArc(Canvas canvas) {
      canvas.save();
      canvas.translate(width / 2, height / 2);
      //画布旋转140°
      canvas.rotate(140);
      //最外层的渐变圆环
      canvas.drawArc(mOuterRectF, -*mStartAngle*, -*mEndAngle*, false, mOuterGradientPaint);    
      //绘制内层虚线圆弧
      canvas.drawArc(mInnerRectF, -*mStartAngle*, -*mEndAngle*, false, mInnerPaint);
      //绘制中间圆弧
      canvas.drawArc(mMiddleRectF, -*mStartAngle*, -*mEndAngle*, false, mMiddlePaint);
      canvas.restore(); 
    }
    

    (2)绘制渐变色圆弧上的大小刻度

    /**
    * 绘制外层渐变色圆弧上的大小刻度线
    */
    private void drawCalibration(Canvas canvas) {
        int dst = (int) (2 * radius - mOuterGradientPaint.getStrokeWidth());
        for (int i = 0; i <= 40; i++) {
            canvas.save();
            canvas.rotate(-(-30 + 6 * i), radius, radius);
             if (i % 10 == 0) {
                mCalibrationPaint.setStrokeWidth(4);
                //绘制大刻度
                canvas.drawLine(dst, radius, 2 * radius, radius, mCalibrationPaint);
              } else {
                //小刻度
                mCalibrationPaint.setStrokeWidth(1);
                canvas.drawLine(dst, radius, 2 * radius, radius, mCalibrationPaint);
              }
            canvas.restore();
      }
    }
    

    注:

    A、圆弧的总弧度为240f,循环40次

    B、小刻度每次旋转6弧度,每绘制10次小刻度就会绘制一次大刻度,即大刻度每次旋转60弧度

    (3)绘制跟随圆弧弧度描述文字

    /**
    *绘制跟随圆弧弧度的文本
    */
    private void drawArcText(Canvas canvas) {
      canvas.save();
      //每次旋转角度
      int rotateAngle = 30;
      //旋转画布
      canvas.rotate(-118, radius - dp2px(26), radius-dp2px(103));
      for (int i = 0; i < valueList.size(); i++) {
            //计算起始角度
            int startAngle = 30 * i - 108;
            //设置数据跟着圆弧绘制
            Path paths = new Path();
            paths.addArc(mInnerRectF, startAngle, rotateAngle);
            float textLen = mTextPaint.measureText(valueList.get(i));
            canvas.drawTextOnPath(valueList.get(i), paths, -textLen / 2 + dp2px(20), -dp2px(22), mTextPaint);
            //canvas.drawText(text[i], radius - 10, radius * 3 / 16+dp2px(10), mTextPaint);
      }
      canvas.restore(); 
    }
    

    注:

    A、drawTextOnPath为文字随path路径显示,drawTextOnPath的第3个参数hOffset为文字水平方向的偏移量,第4个参数vOffset为文字垂直方向的偏移量;

    B、重点是画布开始时的旋转角度及不同文字的起始角度

    (4)绘制圆弧中心的数据及描述信息

    /**
    * 绘制圆弧中间的文本内容
    */
    private void drawCenterText(Canvas canvas) {
      //绘制当前数据值
      mCenterTextPaint.setColor(*GREEN_COLOR*);
      mCenterTextPaint.setTextSize(dp2px(25));
      mCenterTextPaint.setStyle(Paint.Style.*STROKE*);
      canvas.drawText(String.valueOf(mAnimatorValue), radius, radius, mCenterTextPaint);    
      //绘制当前数据描述   
      mCenterTextPaint.setTextSize(dp2px(20));
      canvas.drawText(mCurrentDes, radius, radius + dp2px(25), mCenterTextPaint);   
    }
    

    (5)绘制当前数值对应的圆弧及指针图片指示

    /**
    * 绘制当前进度和指示图片
    */ 
    private void drawBitmapProgress(Canvas canvas) {
        //如果当前角度为0,则不绘制指示图片
        if (mCurrentAngle==0f){
            return;
        }
        canvas.save();
        canvas.translate(radius, radius);
        canvas.rotate(270);
        //绘制对应的圆弧
        canvas.drawArc(mMiddleProgressRectF, -*mStartAngle*-20, mCurrentAngle+5, false, mMiddleProgressPaint);
        canvas.rotate(60 + mCurrentAngle);
        //利用矩阵平移使图片指针方向始终指向刻度
        Matrix matrix = new Matrix();
        matrix.preTranslate(-oval4 - mBitmapWidth * 3 / 8 + 10, -mBitmapHeight / 2);
        canvas.drawBitmap(mBitmap, matrix, mPointerBitmapPaint);
        canvas.restore(); 
    }
    

    注:为了使指针图片的指针一直指向刻度盘上的刻度,这里使用了矩阵的平移。

    3.添加动画及数据

    (1)动画效果

    /**
    *当前数据对应弧度旋转及当前数据自增动画
    */
    public void startRotateAnim() {
      //当前数据对应的弧度
      ValueAnimator mAngleAnim = ValueAnimator.ofFloat(mCurrentAngle, mTotalAngle);
      mAngleAnim.setInterpolator(new AccelerateDecelerateInterpolator());
      mAngleAnim.setDuration(2500);
      mAngleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mCurrentAngle = (float) valueAnimator.getAnimatedValue();
                postInvalidate();
            }
        });
      mAngleAnim.start();    
      //当前数据
      ValueAnimator mNumAnim = ValueAnimator.ofInt(mAnimatorValue, mCurrentValue);
      mNumAnim.setDuration(2500);
      mNumAnim.setInterpolator(new LinearInterpolator());
      mNumAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mAnimatorValue = (int) valueAnimator.getAnimatedValue();
                postInvalidate();
            }
        });
      mNumAnim.start(); 
    }
    

    (2)设置数据及描述信息

    /**
    *设置数据
    */
    public void setValues(int values, List<String> valueList) {
         this.valueList=valueList;
         if (values <= 0) {
            mCurrentValue = values;
            mTotalAngle = 0f;
            mCurrentDes = "";
         } else if (values <= 14000) {
            mCurrentValue = values;
            mTotalAngle = values / 14000f * 60-2;
            Log.e("rcw","mTotalAngle="+mTotalAngle);
            mCurrentDes = "基础目标";
         } else if (values>14000&&values <= 17000) {
            mCurrentValue = values;
            mCurrentDes = "测试目标";
            mTotalAngle = values / 17000f * 120-2;
         } else if (values>17000&&values <= 21000) {
            mCurrentValue = values;
            mTotalAngle = values / 21000f * 180-2;
            mCurrentDes = "保底目标";
         } else {
            mCurrentValue=values;
            float ratio=values / 21000f;
            if (ratio<20){
                mTotalAngle = ratio+180;
         }else {
                mTotalAngle = (float) (ratio*0.2+200);
         }
         mCurrentDes = "冲刺目标";
      }
    
        startRotateAnim(); 
    }
    

    总结:自定义View实现仪表盘效果用到了canvas的旋转及矩阵平移;drawTextOnpath使的文字跟随path绘制;SweepGradient实现圆弧的渐变色效果。

    欢迎评论及留言,不足之处,欢迎指正,谢谢!!!

    参考资料:https://www.aliyun.com/jiaocheng/32462.html

    相关文章

      网友评论

        本文标题:Android自定义View实现渐变色仪表盘效果

        本文链接:https://www.haomeiwen.com/subject/rcvxyftx.html