先给大家展示下效果图:
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
|
package com.lixu.circlemenu; import android.app.activity; import android.os.bundle; import android.view.view; import android.widget.textview; import android.widget.toast; import com.lixu.circlemenu.view.circleimageview; import com.lixu.circlemenu.view.circlelayout; import com.lixu.circlemenu.view.circlelayout.onitemclicklistener; import com.lixu.circlemenu.view.circlelayout.onitemselectedlistener; import com.szugyi.circlemenu.r; public class mainactivity extends activity implements onitemselectedlistener, onitemclicklistener{ private textview selectedtextview; @override protected void oncreate(bundle savedinstancestate) { super .oncreate(savedinstancestate); setcontentview(r.layout.activity_main); circlelayout circlemenu = (circlelayout)findviewbyid(r.id.main_circle_layout); circlemenu.setonitemselectedlistener( this ); circlemenu.setonitemclicklistener( this ); //默认的最底部的那一条被选中,然后显示到该textview中。 selectedtextview = (textview)findviewbyid(r.id.main_selected_textview); selectedtextview.settext(((circleimageview)circlemenu.getselecteditem()).getname()); } //圆盘转动到底部,则认为该条目被选中 @override public void onitemselected(view view, int position, long id, string name) { selectedtextview.settext(name); } //选择了转盘中的某一条。 @override public void onitemclick(view view, int position, long id, string name) { toast.maketext(getapplicationcontext(), getresources().getstring(r.string.start_app) + " " + name, toast.length_short).show(); } } |
引用两个开源类:
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
|
package com.lixu.circlemenu.view; /* * copyright csaba szugyiczki * * licensed under the apache license, version . (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-. * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. */ import android.content.context; import android.content.res.typedarray; import android.util.attributeset; import android.widget.imageview; import com.szugyi.circlemenu.r; /** * * @author szugyi * custom imageview for the circlelayout class. * makes it possible for the image to have an angle, position and a name. * angle is used for the positioning in the circle menu. */ public class circleimageview extends imageview { private float angle = ; private int position = ; private string name; public float getangle() { return angle; } public void setangle(float angle) { this.angle = angle; } public int getposition() { return position; } public void setposition(int position) { this.position = position; } public string getname(){ return name; } public void setname(string name){ this.name = name; } /** * @param context */ public circleimageview(context context) { this(context, null); } /** * @param context * @param attrs */ public circleimageview(context context, attributeset attrs) { this(context, attrs, ); } /** * @param context * @param attrs * @param defstyle */ public circleimageview(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); if (attrs != null) { typedarray a = getcontext().obtainstyledattributes(attrs, r.styleable.circleimageview); name = a.getstring(r.styleable.circleimageview_name); } } } package com.lixu.circlemenu.view; import com.szugyi.circlemenu.r; /* * copyright csaba szugyiczki * * licensed under the apache license, version . (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-. * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. */ import android.content.context; import android.content.res.typedarray; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.graphics.canvas; import android.graphics.matrix; import android.util.attributeset; import android.view.gesturedetector; import android.view.gesturedetector.simpleongesturelistener; import android.view.motionevent; import android.view.view; import android.view.viewgroup; /** * * @author szugyi * creates a rotatable circle menu which can be parameterized by custom attributes. * handles touches and gestures to make the menu rotatable, and to make the * menu items selectable and clickable. * */ public class circlelayout extends viewgroup { // event listeners private onitemclicklistener monitemclicklistener = null; private onitemselectedlistener monitemselectedlistener = null; private oncenterclicklistener moncenterclicklistener = null; // background image private bitmap imageoriginal, imagescaled; private matrix matrix; private int mtappedviewspostition = -; private view mtappedview = null; private int selected = ; // child sizes private int mmaxchildwidth = ; private int mmaxchildheight = ; private int childwidth = ; private int childheight = ; // sizes of the viewgroup private int circlewidth, circleheight; private int radius = ; // touch detection private gesturedetector mgesturedetector; // needed for detecting the inversed rotations private boolean[] quadranttouched; // settings of the viewgroup private boolean allowrotating = true; private float angle = ; private float firstchildpos = ; private boolean rotatetocenter = true; private boolean isrotating = true; /** * @param context */ public circlelayout(context context) { this(context, null); } /** * @param context * @param attrs */ public circlelayout(context context, attributeset attrs) { this(context, attrs, ); } /** * @param context * @param attrs * @param defstyle */ public circlelayout(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); init(attrs); } /** * initializes the viewgroup and modifies it's default behavior by the passed attributes * @param attrs the attributes used to modify default settings */ protected void init(attributeset attrs) { mgesturedetector = new gesturedetector(getcontext(), new mygesturelistener()); quadranttouched = new boolean[] { false, false, false, false, false }; if (attrs != null) { typedarray a = getcontext().obtainstyledattributes(attrs, r.styleable.circle); // the angle where the first menu item will be drawn angle = a.getint(r.styleable.circle_firstchildposition, ); firstchildpos = angle; rotatetocenter = a.getboolean(r.styleable.circle_rotatetocenter, true); isrotating = a.getboolean(r.styleable.circle_isrotating, true); // if the menu is not rotating then it does not have to be centered // since it cannot be even moved if (!isrotating) { rotatetocenter = false; } if (imageoriginal == null) { int picid = a.getresourceid( r.styleable.circle_circlebackground, -); // if a background image was set as an attribute, // retrieve the image if (picid != -) { imageoriginal = bitmapfactory.decoderesource( getresources(), picid); } } a.recycle(); // initialize the matrix only once if (matrix == null) { matrix = new matrix(); } else { // not needed, you can also post the matrix immediately to // restore the old state matrix.reset(); } // needed for the viewgroup to be drawn setwillnotdraw(false); } } /** * returns the currently selected menu * @return the view which is currently the closest to the start position */ public view getselecteditem() { return (selected >= ) ? getchildat(selected) : null; } @override protected void ondraw(canvas canvas) { // the sizes of the viewgroup circleheight = getheight(); circlewidth = getwidth(); if (imageoriginal != null) { // scaling the size of the background image if (imagescaled == null) { matrix = new matrix(); float sx = (((radius + childwidth / ) * ) / (float) imageoriginal .getwidth()); float sy = (((radius + childwidth / ) * ) / (float) imageoriginal .getheight()); matrix.postscale(sx, sy); imagescaled = bitmap.createbitmap(imageoriginal, , , imageoriginal.getwidth(), imageoriginal.getheight(), matrix, false); } if (imagescaled != null) { // move the background to the center int cx = (circlewidth - imagescaled.getwidth()) / ; int cy = (circleheight - imagescaled.getheight()) / ; canvas g = canvas; canvas.rotate(, circlewidth / , circleheight / ); g.drawbitmap(imagescaled, cx, cy, null); } } } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { mmaxchildwidth = ; mmaxchildheight = ; // measure once to find the maximum child size. int childwidthmeasurespec = measurespec.makemeasurespec( measurespec.getsize(widthmeasurespec), measurespec.at_most); int childheightmeasurespec = measurespec.makemeasurespec( measurespec.getsize(widthmeasurespec), measurespec.at_most); final int count = getchildcount(); for (int i = ; i < count; i++) { final view child = getchildat(i); if (child.getvisibility() == gone) { continue; } child.measure(childwidthmeasurespec, childheightmeasurespec); mmaxchildwidth = math.max(mmaxchildwidth, child.getmeasuredwidth()); mmaxchildheight = math.max(mmaxchildheight, child.getmeasuredheight()); } // measure again for each child to be exactly the same size. childwidthmeasurespec = measurespec.makemeasurespec(mmaxchildwidth, measurespec.exactly); childheightmeasurespec = measurespec.makemeasurespec(mmaxchildheight, measurespec.exactly); for (int i = ; i < count; i++) { final view child = getchildat(i); if (child.getvisibility() == gone) { continue; } child.measure(childwidthmeasurespec, childheightmeasurespec); } setmeasureddimension(resolvesize(mmaxchildwidth, widthmeasurespec), resolvesize(mmaxchildheight, heightmeasurespec)); } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { int layoutwidth = r - l; int layoutheight = b - t; // laying out the child views final int childcount = getchildcount(); int left, top; radius = (layoutwidth <= layoutheight) ? layoutwidth / : layoutheight / ; childwidth = (int) (radius / .); childheight = (int) (radius / .); float angledelay = / getchildcount(); for (int i = ; i < childcount; i++) { final circleimageview child = (circleimageview) getchildat(i); if (child.getvisibility() == gone) { continue; } if (angle > ) { angle -= ; } else { if (angle < ) { angle += ; } } child.setangle(angle); child.setposition(i); left = math .round((float) (((layoutwidth / ) - childwidth / ) + radius * math.cos(math.toradians(angle)))); top = math .round((float) (((layoutheight / ) - childheight / ) + radius * math.sin(math.toradians(angle)))); child.layout(left, top, left + childwidth, top + childheight); angle += angledelay; } } /** * rotate the buttons. * * @param degrees the degrees, the menu items should get rotated. */ private void rotatebuttons(float degrees) { int left, top, childcount = getchildcount(); float angledelay = / childcount; angle += degrees; if (angle > ) { angle -= ; } else { if (angle < ) { angle += ; } } for (int i = ; i < childcount; i++) { if (angle > ) { angle -= ; } else { if (angle < ) { angle += ; } } final circleimageview child = (circleimageview) getchildat(i); if (child.getvisibility() == gone) { continue; } left = math .round((float) (((circlewidth / ) - childwidth / ) + radius * math.cos(math.toradians(angle)))); top = math .round((float) (((circleheight / ) - childheight / ) + radius * math.sin(math.toradians(angle)))); child.setangle(angle); if (math.abs(angle - firstchildpos) < (angledelay / ) && selected != child.getposition()) { selected = child.getposition(); if (monitemselectedlistener != null && rotatetocenter) { monitemselectedlistener.onitemselected(child, selected, child.getid(), child.getname()); } } child.layout(left, top, left + childwidth, top + childheight); angle += angledelay; } } /** * @return the angle of the unit circle with the image view's center */ private double getangle(double xtouch, double ytouch) { double x = xtouch - (circlewidth / d); double y = circleheight - ytouch - (circleheight / d); switch (getquadrant(x, y)) { case : return math.asin(y / math.hypot(x, y)) * / math.pi; case : case : return - (math.asin(y / math.hypot(x, y)) * / math.pi); case : return + math.asin(y / math.hypot(x, y)) * / math.pi; default: // ignore, does not happen return ; } } /** * @return the selected quadrant. */ private static int getquadrant(double x, double y) { if (x >= ) { return y >= ? : ; } else { return y >= ? : ; } } private double startangle; @override public boolean ontouchevent(motionevent event) { if (isenabled()) { if (isrotating) { switch (event.getaction()) { case motionevent.action_down: // reset the touched quadrants for (int i = ; i < quadranttouched.length; i++) { quadranttouched[i] = false; } allowrotating = false; startangle = getangle(event.getx(), event.gety()); break; case motionevent.action_move: double currentangle = getangle(event.getx(), event.gety()); rotatebuttons((float) (startangle - currentangle)); startangle = currentangle; break; case motionevent.action_up: allowrotating = true; rotateviewtocenter((circleimageview) getchildat(selected), false); break; } } // set the touched quadrant to true quadranttouched[getquadrant(event.getx() - (circlewidth / ), circleheight - event.gety() - (circleheight / ))] = true; mgesturedetector.ontouchevent(event); return true; } return false; } private class mygesturelistener extends simpleongesturelistener { @override public boolean onfling(motionevent e, motionevent e, float velocityx, float velocityy) { if (!isrotating) { return false; } // get the quadrant of the start and the end of the fling int q = getquadrant(e.getx() - (circlewidth / ), circleheight - e.gety() - (circleheight / )); int q = getquadrant(e.getx() - (circlewidth / ), circleheight - e.gety() - (circleheight / )); // the inversed rotations if ((q == && q == && math.abs(velocityx) < math .abs(velocityy)) || (q == && q == ) || (q == && q == ) || (q == && q == && math.abs(velocityx) > math .abs(velocityy)) || ((q == && q == ) || (q == && q == )) || ((q == && q == ) || (q == && q == )) || (q == && q == && quadranttouched[]) || (q == && q == && quadranttouched[])) { circlelayout.this.post(new flingrunnable(- * (velocityx + velocityy))); } else { // the normal rotation circlelayout.this .post(new flingrunnable(velocityx + velocityy)); } return true; } @override public boolean onsingletapup(motionevent e) { mtappedviewspostition = pointtoposition(e.getx(), e.gety()); if (mtappedviewspostition >= ) { mtappedview = getchildat(mtappedviewspostition); mtappedview.setpressed(true); } else { float centerx = circlewidth / ; float centery = circleheight / ; if (e.getx() < centerx + (childwidth / ) && e.getx() > centerx - childwidth / && e.gety() < centery + (childheight / ) && e.gety() > centery - (childheight / )) { if (moncenterclicklistener != null) { moncenterclicklistener.oncenterclick(); return true; } } } if (mtappedview != null) { circleimageview view = (circleimageview) (mtappedview); if (selected != mtappedviewspostition) { rotateviewtocenter(view, false); if (!rotatetocenter) { if (monitemselectedlistener != null) { monitemselectedlistener.onitemselected(mtappedview, mtappedviewspostition, mtappedview.getid(), view.getname()); } if (monitemclicklistener != null) { monitemclicklistener.onitemclick(mtappedview, mtappedviewspostition, mtappedview.getid(), view.getname()); } } } else { rotateviewtocenter(view, false); if (monitemclicklistener != null) { monitemclicklistener.onitemclick(mtappedview, mtappedviewspostition, mtappedview.getid(), view.getname()); } } return true; } return super.onsingletapup(e); } } /** * rotates the given view to the center of the menu. * @param view the view to be rotated to the center * @param fromrunnable if the method is called from the runnable which animates the rotation * then it should be true, otherwise false */ private void rotateviewtocenter(circleimageview view, boolean fromrunnable) { if (rotatetocenter) { float velocitytemp = ; float destangle = (float) (firstchildpos - view.getangle()); float startangle = ; int reverser = ; if (destangle < ) { destangle += ; } if (destangle > ) { reverser = -; destangle = - destangle; } while (startangle < destangle) { startangle += velocitytemp / ; velocitytemp *= .f; } circlelayout.this.post(new flingrunnable(reverser * velocitytemp, !fromrunnable)); } } /** * a {@link runnable} for animating the menu rotation. */ private class flingrunnable implements runnable { private float velocity; float angledelay; boolean isfirstforwarding = true ; public flingrunnable( float velocity) { this (velocity, true ); } public flingrunnable( float velocity, boolean isfirst) { this .velocity = velocity; this .angledelay = / getchildcount(); this .isfirstforwarding = isfirst; } public void run() { if (math.abs(velocity) > && allowrotating) { if (rotatetocenter) { if (!(math.abs(velocity) < && (math.abs(angle - firstchildpos) % angledelay < ))) { rotatebuttons(velocity / ); velocity /= .f; circlelayout. this .post( this ); } } else { rotatebuttons(velocity / ); velocity /= .f; circlelayout. this .post( this ); } } else { if (isfirstforwarding) { isfirstforwarding = false ; circlelayout. this .rotateviewtocenter( (circleimageview) getchildat(selected), true ); } } } } private int pointtoposition( float x, float y) { for ( int i = ; i < getchildcount(); i++) { view item = (view) getchildat(i); if (item.getleft() < x && item.getright() > x & item.gettop() < y && item.getbottom() > y) { return i; } } return -; } public void setonitemclicklistener(onitemclicklistener onitemclicklistener) { this .monitemclicklistener = onitemclicklistener; } public interface onitemclicklistener { void onitemclick(view view, int position, long id, string name); } public void setonitemselectedlistener( onitemselectedlistener onitemselectedlistener) { this .monitemselectedlistener = onitemselectedlistener; } public interface onitemselectedlistener { void onitemselected(view view, int position, long id, string name); } public interface oncenterclicklistener { void oncenterclick(); } public void setoncenterclicklistener( oncenterclicklistener oncenterclicklistener) { this .moncenterclicklistener = oncenterclicklistener; } } |
xml文件:
<relativelayout xmlns:android=""
xmlns:circle=""
xmlns:tools=""
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".mainactivity" >
<com.lixu.circlemenu.view.circlelayout
android:id="@+id/main_circle_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="@+id/main_selected_textview"
android:layout_gravity="center_horizontal"
circle:firstchildposition="south"
circle:rotatetocenter="true"
circle:isrotating="true" >
<!-- circle:circlebackground="@drawable/green" > -->
<com.lixu.circlemenu.view.circleimageview
android:id="@+id/main_facebook_image"
android:layout_width="dp"
android:layout_height="dp"
android:src="@drawable/icon_facebook"
circle:name="@string/facebook" />
<com.lixu.circlemenu.view.circleimageview
android:id="@+id/main_myspace_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_myspace"
circle:name="@string/myspace" />
<com.lixu.circlemenu.view.circleimageview
android:id="@+id/main_google_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_google"
circle:name="@string/google" />
<com.lixu.circlemenu.view.circleimageview
android:id="@+id/main_linkedin_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_linkedin"
circle:name="@string/linkedin" />
<com.lixu.circlemenu.view.circleimageview
android:id="@+id/main_twitter_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_twitter"
circle:name="@string/twitter" />
<com.lixu.circlemenu.view.circleimageview
android:id="@+id/main_wordpress_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_wordpress"
circle:name="@string/wordpress" />
</com.lixu.circlemenu.view.circlelayout>
<textview
android:id="@+id/main_selected_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignparentbottom="true"
android:layout_centerhorizontal="true"
android:layout_marginbottom="dp"
android:textappearance="?android:attr/textappearancelarge" />
</relativelayout>
基于android实现转盘按钮代码的全部内容就到此结束了,希望能够帮助到大家。