基于Android实现转盘按钮代码

内容摘要
先给大家展示下效果图:



package com.lixu.circlemenu;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.
文章正文

先给大家展示下效果图:

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="http://schemas.android.com/apk/res/android"
      xmlns:circle="http://schemas.android.com/apk/res/com.szugyi.circlemenu"
      xmlns:tools="http://schemas.android.com/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实现转盘按钮代码的全部内容就到此结束了,希望能够帮助到大家。


代码注释

作者:喵哥笔记

IDC笔记

学的不仅是技术,更是梦想!