自定义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);

然后就达到了图上的效果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353