Android雷达图(蜘蛛网图)

简介

最近因为项目的需要,需要实现雷达图来展示各科目的对题率。

雷达图的绘制不算复杂,只要按照一定流程来绘制就可以了,其中使用的最多的是path路径类,使用这个类便于我们绘制出多边形等效果。

效果图如下:

使用方式

使用方式很简单,直接在布局文件里面使用这个控件,记得设置一个合适的大小就可以。

当然也有开放一些public方法,可以进行数据、文本颜色等设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * 设置数据
 * @param points
 */
public void setData(ArrayList<LastPoint> points){}
 
/**
 * 设置文本
 * @param titles
 */
public void setTitles(String[] titles){}
 
/**
 * 设置圈数
 * @param count
 */
public void setCount(int count){}
 
/**
 * 设置网格线颜色
 * @param color
 */
public void setLineColor(int color){}
 
/**
 * 设置填充区域颜色
 * @param color
 */
public void setValueColor(int color){}
 
/**
 * 设置文本颜色
 * @param color
 */
public void setTextColor(int color){}

具体实现

一般自定义控件的流程有以下几个步骤(个人观点):

* 1、构造函数(初始化)
* 2、onMeasure(测量大小)
* 3、onSizeChanged(确定大小)
* 4、onLayout(子view的位置,如果包含子view的话)
* 5、onDraw(绘制内容)
* 6、暴露给外部的接口

在该控件中,2、4都不用考虑,只要确定了大小(onSizeChanged),就能够计算出整个布局的中心,雷达图是以这个中心开始绘制的。

1
2
3
4
5
6
7
8
9
10
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = w;
    mHeight = h;
    mCenterX = mWidth/2;
    mCenterY = mHeight/2;
    mRadius = (Math.min(mWidth,mHeight)/2 * 0.9f);
    postInvalidate();
}

绘制蜘蛛网图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * 画网格
 * @param canvas
 */
private void drawLine(Canvas canvas){
    Path path = new Path();
    //网格线之间的间距
    float distance =  mRadius / (mCount-1);
    for (int i = 0; i < mCount; i++){//外面的网格图形
        float currentRadius = i * distance;//当前半径
        if (i == mCount -1){
            //存储最后一圈网格的点的坐标
            mLastPoints.add(new LastPoint(currentRadius,0));
            mLastPoints.add(new LastPoint(currentRadius/2,-currentRadius));
            mLastPoints.add(new LastPoint(-currentRadius/2,-currentRadius));
            mLastPoints.add(new LastPoint(-currentRadius,0));
            mLastPoints.add(new LastPoint(-currentRadius/2,currentRadius));
            mLastPoints.add(new LastPoint(currentRadius/2,currentRadius));
        }
        //6个点坐标组成一个网格图形
        path.lineTo(currentRadius,0);
        //设置上一次操作的坐标点
        path.moveTo(currentRadius,0);
        path.lineTo(currentRadius/2,-currentRadius);
        path.lineTo(-currentRadius/2,-currentRadius);
        path.lineTo(-currentRadius,0);
        path.lineTo(-currentRadius/2,currentRadius);
        path.lineTo(currentRadius/2,currentRadius);
        path.close();
        canvas.drawPath(path,mLinePaint);
    }
}

绘制从中心到末端的直线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 画网格对角线
 * @param canvas
 */
private void drawGridLine(Canvas canvas){
    Path path = new Path();
    for (int i = 0; i < mLastPoints.size(); i++){
        path.reset();
        LastPoint point = mLastPoints.get(i);
        float x = point.x;
        float y = point.y;
        path.lineTo(x, y);
        canvas.drawPath(path, mLinePaint);
    }
}

绘制末端文本

由于文本与末端有一定的距离,所以需要加上一定的偏移量;当文本在网格左边显示的时候,会与网格有重叠,所以需要先计算文本长度,然后再向左边偏移对应的距离,这样就可以解决重叠问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * 画文本
 * @param canvas
 */
private void drawText(Canvas canvas){
    for (int i = 0; i < mLastPoints.size(); i++){
        //文本长度
        float dis = mTextPaint.measureText(mTitles[i]);
 
        LastPoint point = mLastPoints.get(i);
        float x = point.x;
        float y = point.y;
        if (i == 2 || i == 3 || i == 4){
            //左边绘制文本:文本显示在坐标左边
            x = x - dis;
        }
        if (y > 0){
            y+=18;
        }
        canvas.drawText(mTitles[i],x,y,mTextPaint);
    }
}

绘制填充区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * 画数据线:填充区域
 * @param canvas
 */
private void drawDataLine(Canvas canvas){
    if (mDataPoints == null || mDataPoints.size() == 0)
        return;
    Path path = new Path();
    for (int i = 0; i < mDataPoints.size(); i++){
        LastPoint point = mDataPoints.get(i);
        float x = point.x;
        float y = point.y;
        path.lineTo(x, y);
        if (i == 0){//将上一次操作点移到第一个点坐标,保证最后调用close,形成一个封闭的形状
            path.moveTo(x,y);
        }
        mValuePaint.setAlpha(255);
        //画小圆点
        canvas.drawCircle(x,y,8,mValuePaint);
    }
    path.close();
    mValuePaint.setAlpha(127);
    canvas.drawPath(path, mValuePaint);
 
}

最后,贴上完整的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package com.ha.cjy.myproject.view.widget;
 
 
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
 
import java.util.ArrayList;
 
/**
 * 自定义雷达图
 * 1、构造函数(初始化)
 * 2、onMeasure(测量大小)
 * 3、onSizeChanged(确定大小)
 * 4、onLayout(子view的位置,如果包含子view的话)
 * 5、onDraw(绘制内容)
 * 6、暴露给外部的接口
 * Created by cjy on 17/8/15.
 */
 
public class CustomRadarView extends View {
    //宽度
    private int mWidth;
    //高度
    private int mHeight;
    //原点坐标
    private int mCenterX;
    private int mCenterY;
    //网格半径
    private float mRadius;
    //网格圈数
    private int mCount = 10;
    //Paint
    private Paint mLinePaint;
    private Paint mValuePaint;
    private Paint mTextPaint;
    //颜色值
    private int mLineColor = Color.GRAY;
    private int mValueColor = Color.BLUE;
    private int mTextColor = Color.BLACK;
 
    //最后一圈网格坐标点集合
    private ArrayList<LastPoint> mLastPoints = new ArrayList<LastPoint>();
    //数据坐标点集合
    private ArrayList<LastPoint> mDataPoints = new ArrayList<LastPoint>();
    //文本集合
    private String[] mTitles = new String[]{"科目A","科目A1","科目1","科目D","科目E","科目F"};
 
    public CustomRadarView(Context context) {
        super(context);
        init(context);
    }
 
    public CustomRadarView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }
 
    public CustomRadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }
 
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        //中心坐标
        mCenterX = mWidth/2;
        mCenterY = mHeight/2;
        mRadius = (Math.min(mWidth,mHeight)/2 * 0.9f);
        postInvalidate();
    }
 
    private void init(Context context){
        mLinePaint = new Paint();
        mLinePaint.setStyle(Paint.Style.STROKE);
        mLinePaint.setStrokeWidth(2);
        mLinePaint.setColor(mLineColor);
 
        mValuePaint = new Paint();
        mValuePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mValuePaint.setStrokeWidth(2);
        mValuePaint.setColor(mValueColor);
 
        mTextPaint = new Paint();
        mTextPaint.setStyle(Paint.Style.STROKE);
        mTextPaint.setStrokeWidth(2);
        mTextPaint.setColor(mTextColor);
        mTextPaint.setTextSize(28);
        mTextPaint.setAntiAlias(true);
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
 
        //移动原点坐标
        canvas.translate(mCenterX,mCenterY);
        //画网格线
        drawLine(canvas);
        //画网格对角线
        drawGridLine(canvas);
        //画文本
        drawText(canvas);
        //画数据线
        drawDataLine(canvas);
 
    }
 
    /**
     * 画网格
     * @param canvas
     */
    private void drawLine(Canvas canvas){
        Path path = new Path();
        //网格线之间的间距
        float distance =  mRadius / (mCount-1);
        for (int i = 0; i < mCount; i++){//外面的网格图形
            float currentRadius = i * distance;//当前半径
            if (i == mCount -1){
                //存储最后一圈网格的点的坐标
                mLastPoints.add(new LastPoint(currentRadius,0));
                mLastPoints.add(new LastPoint(currentRadius/2,-currentRadius));
                mLastPoints.add(new LastPoint(-currentRadius/2,-currentRadius));
                mLastPoints.add(new LastPoint(-currentRadius,0));
                mLastPoints.add(new LastPoint(-currentRadius/2,currentRadius));
                mLastPoints.add(new LastPoint(currentRadius/2,currentRadius));
            }
            //6个点坐标组成一个网格图形
            path.lineTo(currentRadius,0);
            //设置上一次操作的坐标点
            path.moveTo(currentRadius,0);
            path.lineTo(currentRadius/2,-currentRadius);
            path.lineTo(-currentRadius/2,-currentRadius);
            path.lineTo(-currentRadius,0);
            path.lineTo(-currentRadius/2,currentRadius);
            path.lineTo(currentRadius/2,currentRadius);
            path.close();
            canvas.drawPath(path,mLinePaint);
        }
    }
 
    /**
     * 画网格对角线
     * @param canvas
     */
    private void drawGridLine(Canvas canvas){
        Path path = new Path();
        for (int i = 0; i < mLastPoints.size(); i++){
            path.reset();
            LastPoint point = mLastPoints.get(i);
            float x = point.x;
            float y = point.y;
            path.lineTo(x, y);
            canvas.drawPath(path, mLinePaint);
        }
    }
 
    /**
     * 画文本
     * @param canvas
     */
    private void drawText(Canvas canvas){
        for (int i = 0; i < mLastPoints.size(); i++){
            //文本长度
            float dis = mTextPaint.measureText(mTitles[i]);
 
            LastPoint point = mLastPoints.get(i);
            float x = point.x;
            float y = point.y;
            if (i == 2 || i == 3 || i == 4){
                //左边绘制文本:文本显示在坐标左边
                x = x - dis;
            }
            if (y > 0){
                y+=18;
            }
            canvas.drawText(mTitles[i],x,y,mTextPaint);
        }
    }
 
    /**
     * 画数据线:填充区域
     * @param canvas
     */
    private void drawDataLine(Canvas canvas){
        if (mDataPoints == null || mDataPoints.size() == 0)
            return;
        Path path = new Path();
        for (int i = 0; i < mDataPoints.size(); i++){
            LastPoint point = mDataPoints.get(i);
            float x = point.x;
            float y = point.y;
            path.lineTo(x, y);
            if (i == 0){//将上一次操作点移到第一个点坐标,保证最后调用close,形成一个封闭的形状
                path.moveTo(x,y);
            }
            mValuePaint.setAlpha(255);
            //画小圆点
            canvas.drawCircle(x,y,8,mValuePaint);
        }
        path.close();
        mValuePaint.setAlpha(127);
        canvas.drawPath(path, mValuePaint);
 
    }
 
    /**
     * 设置数据
     * @param points
     */
    public void setData(ArrayList<LastPoint> points){
        mDataPoints = points;
        invalidate();
    }
 
    /**
     * 设置文本
     * @param titles
     */
    public void setTitles(String[] titles){
        mTitles = titles;
        invalidate();
    }
 
    /**
     * 设置圈数
     * @param count
     */
    public void setCount(int count){
        mCount = count;
        invalidate();
    }
 
    /**
     * 设置网格线颜色
     * @param color
     */
    public void setLineColor(int color){
        mLineColor = color;
        mLinePaint.setColor(mLineColor);
        invalidate();
    }
 
    /**
     * 设置填充区域颜色
     * @param color
     */
    public void setValueColor(int color){
        mValueColor = color;
        mValuePaint.setColor(mValueColor);
        invalidate();
    }
 
    /**
     * 设置文本颜色
     * @param color
     */
    public void setTextColor(int color){
        mTextColor = color;
        mTextPaint.setColor(mTextColor);
        invalidate();
    }
 
 
    /**
     * 坐标点
     */
    public static class LastPoint {
        private float x;
        private float y;
 
        public LastPoint(float x, float y) {
            this.x = x;
            this.y = y;
        }
    }
}

  

 

posted @   ha_cjy  阅读(2655)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示