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

转自:http://www.loongwind.com/archives/96.html

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

roundmenu

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

下面直接上代码:


  
  
  1. package com.cm.widget;
  2.  
  3. import android.content.Context;
  4. import android.graphics.Bitmap;
  5. import android.graphics.Canvas;
  6. import android.graphics.Color;
  7. import android.graphics.Matrix;
  8. import android.graphics.Paint;
  9. import android.graphics.Paint.Style;
  10. import android.graphics.PaintFlagsDrawFilter;
  11. import android.graphics.Path;
  12. import android.graphics.Path.Direction;
  13. import android.graphics.Path.FillType;
  14. import android.graphics.PorterDuff.Mode;
  15. import android.graphics.PorterDuffXfermode;
  16. import android.graphics.Rect;
  17. import android.graphics.RectF;
  18. import android.graphics.Xfermode;
  19. import android.graphics.drawable.BitmapDrawable;
  20. import android.graphics.drawable.Drawable;
  21. import android.util.AttributeSet;
  22. import android.util.Log;
  23. import android.view.GestureDetector;
  24. import android.view.GestureDetector.OnGestureListener;
  25. import android.view.MotionEvent;
  26. import android.widget.ImageView;
  27.  
  28. public class RoundMenuView extends ImageView implements OnGestureListener {
  29. private static final int childMenuSize = 8;
  30. private static final float childAngle = 360f / childMenuSize;
  31. private float offsetAngle = 0;
  32. private Paint paint;
  33. private GestureDetector gestureDetector;
  34. private int selectId = -1;
  35.  
  36. public RoundMenuView(Context context, AttributeSet attrs) {
  37. super(context, attrs);
  38. // TODO Auto-generated constructor stub
  39. init();
  40. }
  41.  
  42. private void init() {
  43. paint = new Paint();
  44. paint.setColor(Color.BLACK);
  45. paint.setAntiAlias(true);
  46. paint.setFlags(Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);
  47.  
  48. gestureDetector = new GestureDetector(this);
  49. }
  50.  
  51.  
  52. /**
  53. * 画扇形
  54. * @param canvas
  55. * @param rectF
  56. */
  57. private void drawArc(Canvas canvas, RectF rectF) {
  58. for (int i = 0; i < childMenuSize; i++) {
  59. paint.setColor(Color.BLUE);
  60. if(i == selectId){ //如果是选中就将扇形画成实心的,否则画空心的扇形
  61. paint.setStyle(Style.FILL);
  62. canvas.drawArc(rectF, i * childAngle + offsetAngle, childAngle,
  63. true, paint);
  64. }else{
  65. paint.setStyle(Style.STROKE);
  66. canvas.drawArc(rectF, i * childAngle + offsetAngle, childAngle,
  67. true, paint);
  68. }
  69. //计算扇形中心点的坐标
  70. double x = 200 + getRoundX(200f/3*2, i, childMenuSize, offsetAngle+childAngle/2);
  71. double y = 200 + getRoundY(200f/3*2, i, childMenuSize, offsetAngle+childAngle/2);
  72. String str = "菜单"+i;
  73. //计算文字宽高
  74. Rect rect = new Rect();
  75. paint.getTextBounds(str, 0, str.length(), rect);
  76. int strW = rect.width();
  77. int strH = rect.height();
  78. paint.setColor(Color.WHITE);
  79. canvas.drawText(str, (float)x-strW/2, (float)y-strH/2, paint);
  80. }
  81. }
  82.  
  83. /**
  84. * 计算圆形等分扇形的点Y坐标
  85. * @param r 圆形直径
  86. * @param i 第几个等分扇形
  87. * @param n 等分扇形个数
  88. * @param offset_angle 与X轴偏移角度
  89. * @return Y坐标
  90. */
  91. private double getRoundY(float r,int i,int n,float offset_angle) {
  92. return r * Math.sin(i * 2 * Math.PI / n + Math.PI / 180
  93. * offset_angle);
  94. }
  95.  
  96. /**
  97. * 计算圆形等分扇形的点X坐标
  98. * @param r 圆形直径
  99. * @param i 第几个等分扇形
  100. * @param n 等分扇形个数
  101. * @param offset_angle 与X轴偏移角度
  102. * @return x坐标
  103. */
  104. private double getRoundX(float r,int i,int n,float offset_angle) {
  105. return r * Math.cos(i * 2 * Math.PI / n + Math.PI / 180
  106. * offset_angle);
  107. }
  108.  
  109.  
  110. @Override
  111. public boolean onTouchEvent(MotionEvent event) {
  112. // TODO Auto-generated method stub
  113. switch (event.getAction()) {
  114. case MotionEvent.ACTION_DOWN:
  115. selectId = whichSector(event.getX()-200, event.getY()-200, 200);
  116. invalidate();
  117. break;
  118. case MotionEvent.ACTION_MOVE:
  119. break;
  120. case MotionEvent.ACTION_UP:
  121. break;
  122. }
  123. gestureDetector.onTouchEvent(event);
  124. return true;
  125. }
  126.  
  127. @Override
  128. protected void onDraw(Canvas canvas) {
  129. RectF rectF = new RectF(0, 0, 400, 400);
  130. paint.setStyle(Paint.Style.FILL_AND_STROKE);
  131. paint.setColor(Color.LTGRAY);
  132. canvas.drawCircle(200, 200, 200, paint);
  133. paint.setColor(Color.GREEN);
  134. canvas.drawCircle(200, 200, 190, paint);
  135. paint.setColor(Color.GRAY);
  136. canvas.drawCircle(200, 200, 180, paint);
  137.  
  138. // 画空心扇形
  139. paint.setColor(Color.BLUE);
  140. paint.setStyle(Paint.Style.STROKE);
  141. drawArc(canvas, new RectF(20, 20, 380, 380));
  142.  
  143. // 画中心外圆
  144. paint.setColor(Color.WHITE);
  145. paint.setStyle(Style.FILL);
  146. canvas.drawCircle(200, 200, 25, paint);
  147.  
  148. // 画三角形
  149. Path path = new Path();
  150. path.moveTo(175, 200);// 此点为多边形的起点
  151. path.lineTo(225, 200);
  152. path.lineTo(200, 240);
  153. path.close(); // 使这些点构成封闭的多边形
  154. canvas.drawPath(path, paint);
  155. // 画中心内圆
  156. paint.setColor(Color.MAGENTA);
  157. canvas.drawCircle(200, 200, 20, paint);
  158. }
  159.  
  160. /**
  161. * 计算两个坐标点与圆点之间的夹角
  162. * @param px1 点1的x坐标
  163. * @param py1 点1的y坐标
  164. * @param px2 点2的x坐标
  165. * @param py2 点2的y坐标
  166. * @return 夹角度数
  167. */
  168. private double calculateScrollAngle(float px1, float py1, float px2,
  169. float py2) {
  170. double radian1 = Math.atan2(py1, px1);
  171. double radian2 = Math.atan2(py2, px2);
  172. double diff = radian2 - radian1;
  173. return Math.round(diff / Math.PI * 180);
  174. }
  175.  
  176. @Override
  177. public boolean onDown(MotionEvent e) {
  178. // TODO Auto-generated method stub
  179. return false;
  180. }
  181.  
  182. @Override
  183. public void onShowPress(MotionEvent e) {
  184. // TODO Auto-generated method stub
  185.  
  186. }
  187.  
  188. @Override
  189. public boolean onSingleTapUp(MotionEvent e) {
  190. // TODO Auto-generated method stub
  191. return false;
  192. }
  193.  
  194. @Override
  195. public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
  196. float distanceY) {
  197. float tpx = e2.getX() - 200;
  198. float tpy = e2.getY() - 200;
  199. float disx = (int) distanceX;
  200. float disy = (int) distanceY;
  201. double scrollAngle = calculateScrollAngle(tpx, tpy, tpx + disx, tpy
  202. + disy);
  203. offsetAngle -= scrollAngle;
  204. selectId = whichSector(0, 40, 200);//0,40是中心三角定点相对于圆点的坐标
  205. invalidate();
  206. Log.e("CM", "offsetAngle:" + offsetAngle);
  207. return true;
  208. }
  209.  
  210. @Override
  211. public void onLongPress(MotionEvent e) {
  212. // TODO Auto-generated method stub
  213.  
  214. }
  215.  
  216. @Override
  217. public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
  218. float velocityY) {
  219. // TODO Auto-generated method stub
  220. return false;
  221. }
  222.  
  223. /**
  224. * 计算点在那个扇形区域
  225. * @param X
  226. * @param Y
  227. * @param R 半径
  228. * @return
  229. */
  230. private int whichSector(double X, double Y, double R) {
  231. double mod;
  232. mod = Math.sqrt(X * X + Y * Y); //将点(X,Y)视为复平面上的点,与复数一一对应,现求复数的模。
  233. double offset_angle;
  234. double arg;
  235. arg = Math.round(Math.atan2(Y, X) / Math.PI * 180);//求复数的辐角。
  236. arg = arg < 0? arg+360:arg;
  237. if(offsetAngle%360 < 0){
  238. offset_angle = 360+offsetAngle%360;
  239. }else{
  240. offset_angle = offsetAngle%360;
  241. }
  242. if (mod > R) { //如果复数的模大于预设的半径,则返回0。
  243. return -2;
  244. } else { //根据复数的辐角来判别该点落在那个扇区。
  245. for(int i=0;i<childMenuSize;i++){
  246. if(isSelect(arg, i,offset_angle) || isSelect(360+arg, i,offset_angle)){
  247. return i;
  248. }
  249. }
  250. }
  251. return -1;
  252. }
  253. /**
  254. * 判读该区域是否被选中
  255. * @param arg 角度
  256. * @param i
  257. * @param offsetAngle 偏移角度
  258. * @return 是否被选中
  259. */
  260. private boolean isSelect(double arg, int i, double offsetAngle) {
  261. return arg>(i*childAngle+offsetAngle%360) && arg<((i+1)*childAngle+offsetAngle%360);
  262. }
  263. }

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


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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值