Android Path 最佳实践之绘制雷达图

第一步:绘制蜘蛛网络

private void init() {
    mainPaint=new Paint();
    mainPaint.setColor(Color.BLACK);
    mainPaint.setAntiAlias(true);
    mainPaint.setStrokeWidth(1);
    mainPaint.setStyle(Paint.Style.STROKE);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    radius=Math.min(w,h)/2*0.9f;
    centerX=w/2;
    centerY=h/2;
    //一旦size发生改变,重新绘制
    postInvalidate();
    super.onSizeChanged(w, h, oldw, oldh);
}

@Override
protected void onDraw(Canvas canvas) {
    drawPolygon(canvas);
}

/**
 * 绘制多边形
 * @param canvas
 */
private void drawPolygon(Canvas canvas){
    Path path=new Path();
    //1度=1*PI/180   360度=2*PI   那么我们每旋转一次的角度为2*PI/内角个数
    //中心与相邻两个内角相连的夹角角度
    angle= (float) (2*Math.PI/count);
    //每个蛛丝之间的间距
    float r= radius/(count-1);
    for (int i = 0; i < count; i++) {
        //当前半径
        float curR=r*i;
        path.reset();
        for (int j = 0; j < count; j++) {
            if(j==0){
                path.moveTo(centerX+curR,centerY);
            }else {
                //对于直角三角形sin(x)是对边比斜边,cos(x)是底边比斜边,tan(x)是对边比底边
                //因此可以推导出:底边(x坐标)=斜边(半径)*cos(夹角角度)
                //               对边(y坐标)=斜边(半径)*sin(夹角角度)
                float x = (float) (centerX+curR*Math.cos(angle*j));
                float y = (float) (centerY+curR*Math.sin(angle*j));
                path.lineTo(x,y);
            }
        }
        path.close();
        canvas.drawPath(path,mainPaint);
    }

绘制蜘蛛网络其实就是绘制指定边数的正多边形,这一步比较简单,比较难的可能就是每个顶点的算法,相关注释我都写了,还有一张来自互联网的图以助于思考,如下:

多边形夹角示意图

绘制出的多边形成品如下:

多边形效果.gif

动画效果只是写了 set 方法,用 handler 实现,代码如下:

//设置数值种类
public void setCount(int count) {
    this.count = count;
    postInvalidate();
}

//设置蜘蛛网颜色
public void setMainPaint(Paint mainPaint) {
    this.mainPaint = mainPaint;
    postInvalidate();
}

调用方法:

mainPaint=new Paint();
mainPaint.setAntiAlias(true);
mainPaint.setStrokeWidth(1);
mainPaint.setStyle(Paint.Style.STROKE);
Handler handler=new Handler();
for (int i = 3; i < 20; i++) {
final int finalI = i;
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        mRdv.setCount(finalI);
        mainPaint.setStrokeWidth(finalI);
        mRdv.setMainPaint(mainPaint);
    }
},i*300);
}

第二步:绘制对角线

/**
 * 绘制直线
 */
private void drawLines(Canvas canvas){
    Path path=new Path();
    for (int i = 0; i < count; i++) {
        path.reset();
        path.moveTo(centerX,centerY);
        float x = (float) (centerX+radius*Math.cos(angle*i));
        float y = (float) (centerY+radius*Math.sin(angle*i));
        path.lineTo(x,y);
        canvas.drawPath(path,mainPaint);
    }
}

这一步比较简单,就是将中心点和各个顶点连接起来,效果如下:

多边形效果.gif

第三步:绘制标题文字

/**
 * 绘制标题文字
 *
 * @param canvas
 */
private void drawTitle(Canvas canvas) {
    if (count != titles.size()) {
        return;
    }
    //相关知识点:http://mikewang.blog.51cto.com/3826268/871765/
    Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
    float fontHeight = fontMetrics.descent - fontMetrics.ascent;
    //绘制文字时不让文字和雷达图形交叉,加大绘制半径
    float textRadius = radius + fontHeight;
    double pi = Math.PI;
    for (int i = 0; i < count; i++) {
        float x = (float) (centerX + textRadius * Math.cos(angle * i));
        float y = (float) (centerY + textRadius * Math.sin(angle * i));
        //当前绘制标题所在顶点角度
        float degrees = angle * i;
        //从右下角开始顺时针画起,与真实坐标系相反
        if (degrees >= 0 && degrees < pi / 2) {//第四象限
            float dis=textPaint.measureText(titles.get(i))/(titles.get(i).length()-1);
            canvas.drawText(titles.get(i), x+dis, y, textPaint);
        } else if (degrees >= pi / 2 && degrees < pi) {//第三象限
            float dis=textPaint.measureText(titles.get(i))/(titles.get(i).length()-1);
            canvas.drawText(titles.get(i), x-dis, y, textPaint);
        } else if (degrees >= pi && degrees < 3 * pi / 2) {//第二象限
            float dis=textPaint.measureText(titles.get(i))/(titles.get(i).length());
            canvas.drawText(titles.get(i), x-dis, y, textPaint);
        } else if (degrees >= 3 * pi / 2 && degrees <= 2 * pi) {//第一象限
            canvas.drawText(titles.get(i), x, y, textPaint);
        }

    }

}

效果如下:

image.png

第四步:绘制覆盖区域

要绘制覆盖区域,首先要指定最大值和每个分类的具体数值,有了这些数值之后,就可以绘制了。
代码如下:

/**
 * 绘制覆盖区域
 */
private void drawRegion(Canvas canvas){
    valuePaint.setAlpha(255);
    Path path=new Path();
    for (int i = 0; i < count; i++) {
        //计算该数值与最大值比例
        Double perCenter = data.get(i)/maxValue;
        //小圆点所在位置距离圆心的距离
        double perRadius=perCenter*radius;
        float x = (float) (centerX + perRadius * Math.cos(angle * i));
        float y = (float) (centerY + perRadius * Math.sin(angle * i));
        if(i==0){
            path.moveTo(x,y);
        }else {
            path.lineTo(x,y);
        }
        //绘制小圆点
        canvas.drawCircle(x,y,10,valuePaint);
    }
    //闭合覆盖区域
    path.close();
    valuePaint.setStyle(Paint.Style.STROKE);
    //绘制覆盖区域外的连线
    canvas.drawPath(path, valuePaint);
    //填充覆盖区域
    valuePaint.setAlpha(128);
    valuePaint.setStyle(Paint.Style.FILL);
    canvas.drawPath(path,valuePaint);
}

看一下效果:

image.png

再来看一下动态的效果吧:

多边形效果.gif

总结

终于完成了,全部代码在下面:

Android雷达图全部代码

主要是参考 crazy__chen 大神的博客,链接贴在下面,做了一遍其实还蛮简单的,这个控件还有很多不完善的,如果实际使用需要改善的地方还有很多,如果有不足希望大家可以告诉我,谢谢!!

参考资料

Android雷达图(蜘蛛网图)绘制

Path之基本操作

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

推荐阅读更多精彩内容