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

效果图:


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-仪表盘+半圆饼图表)