Android View之蜘蛛网

(一)基本思路

1、绘制多个正多边形。

2、绘制连接顶点并过原点的直线。

3、绘制顶点上的文本。

4、绘制各维度的辐射区域。

(二)SpiderView的实例

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
/**
* 自定义View -- 蜘蛛网(正多边形)
*
* @author [*昨日重现*] lhy_ycu@163.com
* @since version 1.0
*/
public class SpiderView extends View {
// 顶点文本
private static final String mPeakTitles = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 每条线上(过原点)的各维度值(可由参数传入)
private double[] mDimenData = {90.0, 80.5, 100.0, 50.9, 20.3, 50.2, 30.7, 70.8};
// 维度最大阀值
private double mMaxThreshold;

private Paint mPaint;
private Path mPath;

// 蜘蛛网区域的宽高
private int mWidth;
private int mHeight;

// 正多边形最大层外接圆半径(多边形的最大半径)
private float mRadius;
// 多边形边数
private int mEdgeCount = 8;
// 多边形个数
private int mPolygonCount = 6;
// 每条边对应的圆心角(正数时为逆时针)
private float mCentralAngle;

private Context mContext;

public SpiderView(Context context) {
this(context, null);
}

// 1、构造(初始化)
public SpiderView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}

private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPath = new Path();

mCentralAngle = (float) ((Math.PI * 2) / mEdgeCount);
mMaxThreshold = getMaxThreshold();
}

private double getMaxThreshold() {
double result = 0.0D;
for (double value : mDimenData) {
result = Math.max(value, result);
}
return result;
}

// 2、测量(控制)View的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureDimension(DensityUtils.dip2px(mContext, 250), widthMeasureSpec);
int height = measureDimension(DensityUtils.dip2px(mContext, 250), heightMeasureSpec);
setMeasuredDimension(width, height);// 保存最终测量的宽高
}

// 注意:这里defaultSize参数可由自定义属性传入构造方法拿到,这里暂时写死。
private int measureDimension(int defaultSize, int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;// 指定大小
} else {
result = defaultSize;// 默认大小
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}

// 3、获取控件最终大小(在onLayout中获取可能更精确)
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.mWidth = w;
this.mHeight = h;
this.mRadius = (w / 2) * 0.9f;
}

// 4、绘制控件
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 将坐标系移动到画布中央
canvas.translate(mWidth / 2, mHeight / 2);
// 设置画布背景色
canvas.drawColor(Color.CYAN);
// 绘制多个多边形
drawPolygon(canvas);
// 绘制多条直线
drawLines(canvas);
// 绘制文本
drawText(canvas);
// 绘制辐射区域
drawRegion(canvas);
}

private void drawPolygon(Canvas canvas) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(3F);
mPaint.setColor(Color.argb(127, 0, 0, 0));
// 绘制多个多边形
for (int i = 1; i <= mPolygonCount; i++) {
float currentR = (mRadius / mPolygonCount) * i;// 当前多边形半径
mPath.reset();
mPath.moveTo(currentR, 0);
// 绘制一个多边形(连接多条边)
for (int j = 1; j <= mEdgeCount; j++) {
float px = (float) (currentR * Math.cos(mCentralAngle * j));
float py = (float) (currentR * Math.sin(mCentralAngle * j));
mPath.lineTo(px, py);
}
canvas.drawPath(mPath, mPaint);
}
}

private void drawLines(Canvas canvas) {
for (int i = 0; i < mEdgeCount; i++) {
mPath.reset();
mPath.moveTo(0, 0);// 起点为原点(0,0)
float px = (float) (mRadius * Math.cos(mCentralAngle * i));
float py = (float) (mRadius * Math.sin(mCentralAngle * i));
mPath.lineTo(px, py);
canvas.drawPath(mPath, mPaint);
}
}

private void drawText(Canvas canvas) {
mPaint.setColor(Color.argb(255, 255, 0, 0));
mPaint.setTextSize(DensityUtils.sp2px(mContext, 10F));
char[] titles = mPeakTitles.toCharArray();

// 计算画笔高度
Paint.FontMetrics fm = mPaint.getFontMetrics();
float fontH = (float) Math.ceil(fm.descent - fm.ascent);
for (int i = 0; i < mEdgeCount; i++) {
float angle = mCentralAngle * i;
// 将半径延长一些再描点
float x = (float) ((mRadius + fontH / 2) * Math.cos(angle));
float y = (float) ((mRadius + fontH / 2) * Math.sin(angle));
float fontW = mPaint.measureText(String.valueOf(titles[i])); // 文本长度
canvas.drawText(titles, i, 1, x - fontW / 2, y + fontH / 2, mPaint);// A、B

// if (angle >= 0 && angle <= Math.PI / 2) {// 第四象限 A、B
// } else if (angle > Math.PI / 2 && angle <= Math.PI) {// 三 C、D
// } else if (angle > Math.PI && angle <= 3 * Math.PI / 2) {// 二 E、F
// } else if (angle > 3 * Math.PI / 2 && angle <= 2 * Math.PI) {// 一 G、H
// }
}
}

private void drawRegion(Canvas canvas) {
mPaint.setColor(Color.argb(200, 74, 132, 251));
mPaint.setStyle(Paint.Style.FILL);
mPath.reset();
float circleRadius = DensityUtils.dip2px(mContext, 5);
for (int i = 0; i < mEdgeCount; i++) {
// 过原点的线长
double lineLen = (mDimenData[i] / mMaxThreshold) * mRadius;
// 确保在直线上
float x = (float) (lineLen * Math.cos(mCentralAngle * i));
float y = (float) (lineLen * Math.sin(mCentralAngle * i));
// 描点
canvas.drawCircle(x, y, circleRadius, mPaint);
// 连线
if (i == 0) {
mPath.moveTo(x, y);
} else {
mPath.lineTo(x, y);
}
}
canvas.drawPath(mPath, mPaint);
}

// ********************************************** //
// ******* 下面可以暴露一些数据接口、监听 ******** //
// ********************************************** //

/**
* 设置各维度值
*
* @param dimenData 维度数组
*/
public void setDimenData(double[] dimenData) {
this.mDimenData = dimenData;
this.mMaxThreshold = getMaxThreshold();
invalidate();
}

}
Hawky wechat
欢迎订阅我的微信公众号
坚持原创技术分享,您的支持将鼓励我继续创作!

分享