先给大家展示下效果图:
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实现转盘按钮代码的全部内容就到此结束了,希望能够帮助到大家。