android圆形等分扇形菜单的实现

最近闲着没事写了一个圆形等分扇形菜单的功能,废话不多说先上图:

roundmenu

如图,实现了旋转功能、点击选中指定菜单等功能。

下面直接上代码:

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
package com.cm.widget;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.Path.FillType;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.widget.ImageView;

public class RoundMenuView extends ImageView implements OnGestureListener {
private static final int childMenuSize = 8;
private static final float childAngle = 360f / childMenuSize;
private float offsetAngle = 0;
private Paint paint;
private GestureDetector gestureDetector;
private int selectId = -1;

public RoundMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init();
}

private void init() {
paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
paint.setFlags(Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);

gestureDetector = new GestureDetector(this);
}

/**
* 画扇形
* @param canvas
* @param rectF
*/
private void drawArc(Canvas canvas, RectF rectF) {

for (int i = 0; i < childMenuSize; i++) {
paint.setColor(Color.BLUE);

if(i == selectId){ //如果是选中就将扇形画成实心的,否则画空心的扇形
paint.setStyle(Style.FILL);
canvas.drawArc(rectF, i * childAngle + offsetAngle, childAngle,
true, paint);
}else{
paint.setStyle(Style.STROKE);
canvas.drawArc(rectF, i * childAngle + offsetAngle, childAngle,
true, paint);
}

//计算扇形中心点的坐标
double x = 200 + getRoundX(200f/3*2, i, childMenuSize, offsetAngle+childAngle/2);
double y = 200 + getRoundY(200f/3*2, i, childMenuSize, offsetAngle+childAngle/2);

String str = "菜单"+i;
//计算文字宽高
Rect rect = new Rect();
paint.getTextBounds(str, 0, str.length(), rect);
int strW = rect.width();
int strH = rect.height();
paint.setColor(Color.WHITE);
canvas.drawText(str, (float)x-strW/2, (float)y-strH/2, paint);

}
}

/**
* 计算圆形等分扇形的点Y坐标
* @param r 圆形直径
* @param i 第几个等分扇形
* @param n 等分扇形个数
* @param offset_angle 与X轴偏移角度
* @return Y坐标
*/
private double getRoundY(float r,int i,int n,float offset_angle) {
return r * Math.sin(i * 2 * Math.PI / n + Math.PI / 180
* offset_angle);
}

/**
* 计算圆形等分扇形的点X坐标
* @param r 圆形直径
* @param i 第几个等分扇形
* @param n 等分扇形个数
* @param offset_angle 与X轴偏移角度
* @return x坐标
*/
private double getRoundX(float r,int i,int n,float offset_angle) {
return r * Math.cos(i * 2 * Math.PI / n + Math.PI / 180
* offset_angle);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
selectId = whichSector(event.getX()-200, event.getY()-200, 200);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
gestureDetector.onTouchEvent(event);
return true;
}

@Override
protected void onDraw(Canvas canvas) {

RectF rectF = new RectF(0, 0, 400, 400);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setColor(Color.LTGRAY);
canvas.drawCircle(200, 200, 200, paint);
paint.setColor(Color.GREEN);
canvas.drawCircle(200, 200, 190, paint);
paint.setColor(Color.GRAY);
canvas.drawCircle(200, 200, 180, paint);

// 画空心扇形
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);
drawArc(canvas, new RectF(20, 20, 380, 380));

// 画中心外圆
paint.setColor(Color.WHITE);
paint.setStyle(Style.FILL);
canvas.drawCircle(200, 200, 25, paint);

// 画三角形
Path path = new Path();
path.moveTo(175, 200);// 此点为多边形的起点
path.lineTo(225, 200);
path.lineTo(200, 240);
path.close(); // 使这些点构成封闭的多边形
canvas.drawPath(path, paint);

// 画中心内圆
paint.setColor(Color.MAGENTA);
canvas.drawCircle(200, 200, 20, paint);

}

/**
* 计算两个坐标点与圆点之间的夹角
* @param px1 点1的x坐标
* @param py1 点1的y坐标
* @param px2 点2的x坐标
* @param py2 点2的y坐标
* @return 夹角度数
*/
private double calculateScrollAngle(float px1, float py1, float px2,
float py2) {
double radian1 = Math.atan2(py1, px1);
double radian2 = Math.atan2(py2, px2);
double diff = radian2 - radian1;
return Math.round(diff / Math.PI * 180);
}

@Override
public boolean onDown(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}

@Override
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub

}

@Override
public boolean onSingleTapUp(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
float tpx = e2.getX() - 200;
float tpy = e2.getY() - 200;
float disx = (int) distanceX;
float disy = (int) distanceY;
double scrollAngle = calculateScrollAngle(tpx, tpy, tpx + disx, tpy
+ disy);
offsetAngle -= scrollAngle;
selectId = whichSector(0, 40, 200);//0,40是中心三角定点相对于圆点的坐标
invalidate();
Log.e("CM", "offsetAngle:" + offsetAngle);
return true;
}

@Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub

}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// TODO Auto-generated method stub
return false;
}

/**
* 计算点在那个扇形区域
* @param X
* @param Y
* @param R 半径
* @return
*/
private int whichSector(double X, double Y, double R) {

double mod;
mod = Math.sqrt(X * X + Y * Y); //将点(X,Y)视为复平面上的点,与复数一一对应,现求复数的模。
double offset_angle;
double arg;
arg = Math.round(Math.atan2(Y, X) / Math.PI * 180);//求复数的辐角。
arg = arg < 0? arg+360:arg;
if(offsetAngle%360 < 0){
offset_angle = 360+offsetAngle%360;
}else{
offset_angle = offsetAngle%360;
}
if (mod > R) { //如果复数的模大于预设的半径,则返回0。
return -2;
} else { //根据复数的辐角来判别该点落在那个扇区。
for(int i=0;i<childMenuSize;i++){
if(isSelect(arg, i,offset_angle) || isSelect(360+arg, i,offset_angle)){
return i;
}
}
}
return -1;
}
/**
* 判读该区域是否被选中
* @param arg 角度
* @param i
* @param offsetAngle 偏移角度
* @return 是否被选中
*/
private boolean isSelect(double arg, int i, double offsetAngle) {
return arg>(i*childAngle+offsetAngle%360) &amp;&amp; arg<((i+1)*childAngle+offsetAngle%360);
}

}

需要特别说明的是代码里很多用到坐标的地方都进行了加200或者减200的操作,这是因为这里我是以圆的中心为坐标圆点计算的而不是以view的左上角为圆点。
如图所示:
xy)
这里的坐标系与常规的坐标系有些不同,象限是相反的,所以计算坐标的时候需要注意。
最后贴上在布局中使用的方法:

1
2
3
4
<com.com.widget.RoundMenuView
android:id="@+id/myRoundMenu"
android:layout_width="400dp"
android:layout_height="400dp"/>

这里是将控件大小固定死的,可以根据需求修改,当然代码里相应的也要做修改

坚持原创技术分享,您的支持将鼓励我继续创作!