基于Android实现转盘按钮代码
先给大家展示下效果图:
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仅仅作为演示转盘按钮以何为默认的选中项, //默认的最底部的那一条被选中,然后显示到该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(); } }
引用两个开源类:
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实现转盘按钮代码的全部内容就到此结束了,希望能够帮助到大家。