自定义view-仪表盘+半圆饼图表

作者: 拓荒者C | 来源:发表于2019-08-08 18:21 被阅读3524次

    效果图:


    image.png

    在实现这个功能是,可以先补充下求值弧的周边和角度根据周长来算出x,y轴的相关数学知识。
    根据这个效果图,可以拆分成6个主要点:画半圆刻度,根据半圆的相关位置绘制刻度的具体值和相应的文本值,绘制颜色渐变圆弧,绘制渐变色的透明和指针
    1、绘制半圆刻度:5个重要值:圆形的代表的最大值,最小刻度的值,大刻度的值,大刻度的数量,画笔,小刻度的半圆直径和大刻度的半圆直径。
    相关的值确定好后,就开始着手画图
    在onDraw方法里面进行绘制:
    不对,在绘制之前先写好小算法:
    根据圆心和半径的值算出x轴和y轴的坐标的算法A:(算法的具体我就不说了,不知道的话可以科普下数学知识)

     /**
         * 依圆心坐标,半径,扇形角度,计算出扇形终射线与圆弧交叉点的xy坐标
         */
        public float[] getCoordinatePoint(int radius, float cirAngle) {
            float[] point = new float[2];
            double arcAngle = Math.toRadians(cirAngle); //将角度转换为弧度
            if (cirAngle < 90) {
                point[0] = (float) (mCenterX + Math.cos(arcAngle) * radius);
                point[1] = (float) (mCenterY + Math.sin(arcAngle) * radius);
            } else if (cirAngle == 90) {
                point[0] = mCenterX;
                point[1] = mCenterY + radius;
            } else if (cirAngle > 90 && cirAngle < 180) {
                arcAngle = Math.PI * (180 - cirAngle) / 180.0;
                point[0] = (float) (mCenterX - Math.cos(arcAngle) * radius);
                point[1] = (float) (mCenterY + Math.sin(arcAngle) * radius);
            } else if (cirAngle == 180) {
                point[0] = mCenterX - radius;
                point[1] = mCenterY;
            } else if (cirAngle > 180 && cirAngle < 270) {
                arcAngle = Math.PI * (cirAngle - 180) / 180.0;
                point[0] = (float) (mCenterX - Math.cos(arcAngle) * radius);
                point[1] = (float) (mCenterY - Math.sin(arcAngle) * radius);
            } else if (cirAngle == 270) {
                point[0] = mCenterX;
                point[1] = mCenterY - radius;
            } else {
                arcAngle = Math.PI * (360 - cirAngle) / 180.0;
                point[0] = (float) (mCenterX + Math.cos(arcAngle) * radius);
                point[1] = (float) (mCenterY - Math.sin(arcAngle) * radius);
            }
    
            Log.e("getCoordinatePoint","radius="+radius+",cirAngle="+cirAngle+",point[0]="+point[0]+",point[1]="+point[1]);
            return point;
        }
    
     /**
         * 通过实时数值得到指针角度这个方法很重要
         */
        private float getAngleFromResult(float result) {
            if (result > mMaxValue)
                return 360.0f;
            return mSweepAngle * (result - mMinValue) / (mMaxValue - mMinValue) + mStartAngle;
        }
    

    根据大刻度的相关属性值,绘制大刻度和大刻度的具体值

    for (int i = 0; i <= mBigSliceCount; i++) {
                //绘制大刻度
                float angle = i * mBigScaleAngle + mStartAngle;
                float[] point1 = getCoordinatePoint(mBigScaleRadius, angle);
                float[] point2 = getCoordinatePoint(mSmallScaleRadius, angle);
                canvas.drawLine(point1[0], point1[1], point2[0], point2[1], mPaintScale);
                //绘制圆盘上的数字
                mPaintScaleText.setTextSize(mScaleTextSize);
                String number = mGraduations[i]+"%";
                mPaintScaleText.getTextBounds(number, 0, number.length(), mRectScaleText);
                if (angle % 360 > 135 && angle % 360 < 215) {
                    mPaintScaleText.setTextAlign(Paint.Align.LEFT);
                } else if ((angle % 360 >= 0 && angle % 360 < 45) || (angle % 360 > 325 && angle % 360 <= 360)) {
                    mPaintScaleText.setTextAlign(Paint.Align.RIGHT);
                } else {
                    mPaintScaleText.setTextAlign(Paint.Align.CENTER);
                }
                float[] numberPoint = getCoordinatePoint(mNumScaleRadius, angle);
                if (i == 0 ) {
                    //其实刻度和最大刻度不绘制
                    canvas.drawText(number, numberPoint[0], numberPoint[1] + (mRectScaleText.height() / 2), mPaintScaleText);
                } else if (i == mBigSliceCount){
                    canvas.drawText(number, numberPoint[0]+dpToPx(5), numberPoint[1]+ (mRectScaleText.height() / 2), mPaintScaleText);
                } else{
                    canvas.drawText(number, numberPoint[0], numberPoint[1] + mRectScaleText.height(), mPaintScaleText);
                }
            }
    

    然后根据小刻度的相关属性来绘制小刻度

    //绘制小的子刻度
            mPaintScale.setStrokeWidth(dpToPx(1));
            for (int i = 0; i < mSmallScaleCount; i++) {
                if (i % mScaleCountInOneBigScale != 0) {
                    float angle = i * mSmallScaleAngle + mStartAngle;
                    float[] point1 = getCoordinatePoint(mRadius, angle);
                    float[] point2 = getCoordinatePoint(mSmallScaleRadius, angle);
    
                    mPaintScale.setStrokeWidth(dpToPx(1));
                    canvas.drawLine(point1[0], point1[1], point2[0], point2[1], mPaintScale);
                }
            }
    

    完成这两步,你就可以得到


    image.png

    以上两步完成就好办了
    接下来就是重点,绘制圆角结合点的半圆饼表
    :根据不同的数据的占比,算出改数据所占的半圆的弧度的角度,然后根据所占的角度来绘制相应的半圆柱
    可以分两步走,
    1、先定义一个相应的画笔类,在画笔里面顺便和渐变的参数设置进去

    private void drawArcInAngle(Canvas canvas,int[] color,
                                    RectF rectF,float startAngle,
                                    float angleLength,float[] position) {
            Paint paint = new Paint();
            SweepGradient sweepGradient;
            if (color.length == 1){
                paint.setColor(color[0]);
            }else {
                sweepGradient = new SweepGradient(mCenterX, mCenterY,color,position);
                paint.setShader(sweepGradient);//设置渐变 从X轴正方向取color数组颜色开始渐变
            }
            /** 结合处为圆弧*/
            paint.setStrokeJoin(Paint.Join.ROUND);
            /** 设置画笔的样式 Paint.Cap.Round ,Cap.SQUARE等分别为圆形、方形*/
            paint.setStrokeCap(Paint.Cap.ROUND);
            /** 设置画笔的填充样式 Paint.Style.FILL :填充内部;Paint.Style.FILL_AND_STROKE :填充内部和描边; Paint.Style.STROKE :仅描边*/
            paint.setStyle(Paint.Style.STROKE);
            /**抗锯齿功能*/
            paint.setAntiAlias(true);
            /**设置画笔宽度*/
            paint.setStrokeWidth(borderWidth);
            /**绘制圆弧的方法
             * * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧,
             参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,
             参数二是起始角(度)在电弧的开始,圆弧起始角度,单位为度。
             参数三圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度。
             参数四是如果这是true(真)的话,在绘制圆弧时将圆心包括在内,通常用来绘制扇形;如果它是false(假)这将是一个弧线,
             参数五是Paint对象;
             */
            canvas.drawArc(rectF, startAngle, angleLength, false, paint);
        }
    

    2、算出矩阵参数

    RectF rectF = null;
            if (borderWidth > 0) {
                int r  = mSmallScaleRadius - borderWidth / 2 - dpToPx(5) ;
                rectF = new RectF(mCenterX - r, mCenterY - r, mCenterX + r, mCenterY + r);
            }
    

    再然后就是绘制圆饼图

    oldValue = bean.getX();
                if (bean.getX() == mMaxValue){
                    drawArcInAngle(canvas,bean.getColor(),rectF,mStartAngle+oldAngle,mSweepAngle-oldAngle,null);
                }else {
                    drawArcInAngle(canvas,bean.getColor(),rectF,mStartAngle+oldAngle,getAngleFromResult(bean.getX())-mStartAngle-oldAngle,null);
                }
    

    然后再在相应的数值中间绘制备注文本,一起的代码就是如下

    for (WarnPanelBean bean : warnPanelBeans){
    
                float angle = ((bean.getX()-oldValue)/2+oldValue) * (mSweepAngle/mMaxValue)+ mStartAngle;
                float[] numberPoint = getCoordinatePoint(mNumScaleRadius, angle);
                //绘制圆盘上的备注
                mPaintScaleText.setTextSize(mScaleTextSize+2);
                String number = bean.getRemark();
                mPaintScaleText.getTextBounds(number, 0, number.length(), mRectScaleText);
                if (angle % 360 > 135 && angle % 360 < 215) {
                    mPaintScaleText.setTextAlign(Paint.Align.LEFT);
                } else if ((angle % 360 >= 0 && angle % 360 < 45) || (angle % 360 > 325 && angle % 360 <= 360)) {
                    mPaintScaleText.setTextAlign(Paint.Align.RIGHT);
                } else {
                    numberPoint[1] += 10 ;
                    mPaintScaleText.setTextAlign(Paint.Align.CENTER);
                }
                canvas.drawText(number, numberPoint[0], numberPoint[1], mPaintScaleText);
                oldValue = bean.getX();
                if (bean.getX() == mMaxValue){
                    drawArcInAngle(canvas,bean.getColor(),rectF,mStartAngle+oldAngle,mSweepAngle-oldAngle,null);
                }else {
                    drawArcInAngle(canvas,bean.getColor(),rectF,mStartAngle+oldAngle,getAngleFromResult(bean.getX())-mStartAngle-oldAngle,null);
                }
                oldAngle = getAngleFromResult(bean.getX())-mStartAngle;
            }
    
    

    上面几步完成之后,下面的就是小问题了,

    //绘制中心点的圆
            mPaintCirclePointer.setStyle(Paint.Style.FILL);
            mPaintCirclePointer.setStrokeWidth(dpToPx(4));
            canvas.drawCircle(mCenterX, mCenterY, mCircleRadius/2, mPaintCirclePointer);
    
    //绘制三角形指针
            path.reset();
            mPaintCirclePointer.setStyle(Paint.Style.FILL);
            float[] point1 = getCoordinatePoint(mCircleRadius / 2 - yOffset, initAngle + 90);
            path.moveTo(point1[0], point1[1]);
            float[] point2 = getCoordinatePoint(mCircleRadius / 2 - yOffset, initAngle - 90);
            path.lineTo(point2[0], point2[1]);
            float[] point3 = getCoordinatePoint(mPointerRadius, initAngle);
            path.lineTo(point3[0], point3[1] );
            path.close();
            canvas.drawPath(path, mPaintCirclePointer);
    
    // 绘制三角形指针底部的圆弧效果
            canvas.drawCircle((point1[0] + point2[0]) / 2, (point1[1] + point2[1]) / 2-yOffset, mCircleRadius+dpToPx(2), mPaintCirclePointer);
    

    然后就是绘制具体值得值,就是图标的60%大字

    //绘制实时值
            canvas.drawText(trimFloat(mRealTimeValue)+" "+ mUnitText, mCenterX, mCenterY +realTimeTextYOffset, mPaintValue);
    

    对了,再有的就是根据具体值来绘制相应的扇形渐变蒙版

    mPaintRibbon = new Paint();
            mPaintRibbon.setAntiAlias(true);
            mPaintRibbon.setStrokeJoin(Paint.Join.ROUND);//结合处弧形
            mPaintRibbon.setStrokeWidth(mRibbonWidth);
            mPaintRibbon.setAlpha(120);
            mPaintRibbon.setStyle(Paint.Style.STROKE);
            mSweepGradient = new SweepGradient(mCenterX, mCenterY,colors,null);
            mPaintRibbon.setShader(mSweepGradient);//设置渐变 从X轴正方向取color数组颜色开始渐变
    
            if (mRibbonWidth > 0) {
                //int r  = mRadius - mRibbonWidth / 2 + dpToPx(1) ;
                int r  = mRadius - mRibbonWidth / 2 + dpToPx(1) ;
                mRectRibbon = new RectF(mCenterX - r, mCenterY - r, mCenterX + r, mCenterY + r);
            }
    // 绘制色带
            canvas.drawArc(mRectRibbon, mStartAngle, getAngleFromResult(mRealTimeValue)-mStartAngle, false, mPaintRibbon);
    

    然后就达到了图上的效果。因为是公司的相应项目,占不把全部的源码贴出来,需要的话下面留言

    相关文章

      网友评论

        本文标题:自定义view-仪表盘+半圆饼图表

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