android圆形旋转菜单,并支持移动换位功能

LZ最近接手公司一个项目,需要写一个圆形的旋转菜单,并且支持菜单之间的移动换位,本来以为这种demo应该网上是很多的,想不到度娘也是帮不了我,空有旋转功能但是却不能换位置,所以LZ就只能靠自己摸索了。

最终LZ参考了网上的部分代码,重写了一个自定义的view终于实现了这个看似很吊,却没有实际意义的功能。在此贡献出来给广大码农们共享。

话不多说,先上代码:

自定义view类:

public class RoundSpinView extends View {
	private Paint mPaint = new Paint();
	private PaintFlagsDrawFilter pfd;

	private int startMenu;   //菜单的第一张图片的资源id

	// stone列表
	private BigStone[] mStones;
	// 数目
	private static final int STONE_COUNT = 3;

	// 圆心坐标
	private int mPointX = 0, mPointY = 0;
	// 半径
	private int mRadius = 0;
	// 每两个点间隔的角度
	private int mDegreeDelta;

	private int menuRadius; // 菜单的半径

	private int mCur = -1; // 正在被移动的menu;

	private boolean[] quadrantTouched;   //对每个象限触摸情况的记录

	// Touch detection
	private GestureDetector mGestureDetector;

	private onRoundSpinViewListener mListener;  //自定义事件监听器

	private final static int TO_ROTATE_BUTTON = 0;  //旋转按钮;

	private Handler handler = new Handler(){
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case TO_ROTATE_BUTTON:
				float velocity = Float.parseFloat(msg.obj.toString());
				rotateButtons(velocity/75);
				velocity /= 1.0666F;
				new Thread(new FlingRunnable(velocity)).start();
				break;

			default:
				break;
			}
		};
	};

	public interface onRoundSpinViewListener{
		public void onSingleTapUp(int position);  //监听每个菜单的单击事件
	}

	public RoundSpinView(Context context,AttributeSet attrs) {
		super(context,attrs);
		if(attrs!=null){
			TypedArray a = getContext().obtainStyledAttributes(attrs,
					R.styleable.RoundSpinView);
			startMenu = a.getResourceId(R.styleable.RoundSpinView_menuStart, 0);
		}
		pfd = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);
		mPaint.setColor(Color.WHITE);
		mPaint.setStrokeWidth(2);
		mPaint.setAntiAlias(true); //消除锯齿
		mPaint.setStyle(Paint.Style.STROKE); //绘制空心圆
		PathEffect effects = new DashPathEffect(new float[]{5,5,5,5},1);
		mPaint.setPathEffect(effects);

		quadrantTouched = new boolean[] { false, false, false, false, false };
		mGestureDetector = new GestureDetector(getContext(),
				new MyGestureListener());

		setupStones();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		mPointX = this.getMeasuredWidth()/2;
		mPointY = this.getMeasuredHeight()/2;

		//初始化半径和菜单半径
		mRadius = mPointX-mPointX/5;
		menuRadius = (int)(mPointX/5.5);

		computeCoordinates();
	}

	/**
	 * 初始化每个点
	 */
	private void setupStones() {
		mStones = new BigStone[STONE_COUNT];
		BigStone stone;
		int angle = 270;
		mDegreeDelta = 360 / STONE_COUNT;

		for (int index = 0; index < STONE_COUNT; index++) {
			stone = new BigStone();
			if (angle >= 360) {
				angle -= 360;
			}else if(angle < 0){
				angle += 360;
			}
			stone.angle = angle;
			stone.bitmap = BitmapFactory.decodeResource(getResources(),
					startMenu + index);
			angle += mDegreeDelta;

			mStones[index] = stone;
		}
	}

	/**
	 * 重新计算每个点的角度
	 */
	private void resetStonesAngle(float x, float y) {
		int angle = computeCurrentAngle(x, y);
		Log.d("RoundSpinView", "angle:" + angle);
		for (int index = 0; index < STONE_COUNT; index++) {
			mStones[index].angle = angle;
			angle += mDegreeDelta;
		}
	}

	/**
	 * 计算每个点的坐标
	 */
	private void computeCoordinates() {
		BigStone stone;
		for (int index = 0; index < STONE_COUNT; index++) {
			stone = mStones[index];
			stone.x = mPointX
					+ (float) (mRadius * Math.cos(Math.toRadians(stone.angle)));
			stone.y = mPointY
					+ (float) (mRadius * Math.sin(Math.toRadians(stone.angle)));
		}
	}

	/**
	 * 计算某点的角度
	 *
	 * @param x
	 * @param y
	 * @return
	 */
	private int computeCurrentAngle(float x, float y) {
		float distance = (float) Math
				.sqrt(((x - mPointX) * (x - mPointX) + (y - mPointY)
						* (y - mPointY)));
		int degree = (int) (Math.acos((x - mPointX) / distance) * 180 / Math.PI);
		if (y < mPointY) {
			degree = -degree;
		}

		Log.d("RoundSpinView", "x:" + x + ",y:" + y + ",degree:" + degree);
		return degree;
	}

	private double startAngle;

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		// resetStonesAngle(event.getX(), event.getY());
		// computeCoordinates();
		// invalidate();

		int x, y;
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			x = (int) event.getX();
			y = (int) event.getY();
			mCur = getInCircle(x, y);
			if (mCur == -1) {
				startAngle = computeCurrentAngle(x, y);
			}
		} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
			x = (int) event.getX();
			y = (int) event.getY();
			if (mCur != -1) {
				mStones[mCur].x = x;
				mStones[mCur].y = y;
				invalidate();
			} else {
				double currentAngle = computeCurrentAngle(x, y);
				rotateButtons(startAngle - currentAngle);
				startAngle = currentAngle;
			}
		} else if (event.getAction() == MotionEvent.ACTION_UP) {
			x = (int) event.getX();
			y = (int) event.getY();
			if (mCur != -1) {
				computeCoordinates();
				int cur = getInCircle(x, y);
				if (cur != mCur && cur != -1) {
					int angle = mStones[mCur].angle;
					mStones[mCur].angle = mStones[cur].angle;
					mStones[cur].angle = angle;
				}
				computeCoordinates();
				invalidate();
				mCur = -1;
			}
		}

		// set the touched quadrant to true
		quadrantTouched[getQuadrant(event.getX() - mPointX,
				mPointY - event.getY())] = true;
		mGestureDetector.onTouchEvent(event);
		return true;
	}

	private class MyGestureListener extends SimpleOnGestureListener {
		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY) {
			// get the quadrant of the start and the end of the fling
			int q1 = getQuadrant(e1.getX() - mPointX, mPointY - e1.getY());
			int q2 = getQuadrant(e2.getX() - mPointX, mPointY - e2.getY());

			// the inversed rotations
			if ((q1 == 2 && q2 == 2 && Math.abs(velocityX) < Math
					.abs(velocityY))
					|| (q1 == 3 && q2 == 3)
					|| (q1 == 1 && q2 == 3)
					|| (q1 == 4 && q2 == 4 && Math.abs(velocityX) > Math
							.abs(velocityY))
					|| ((q1 == 2 && q2 == 3) || (q1 == 3 && q2 == 2))
					|| ((q1 == 3 && q2 == 4) || (q1 == 4 && q2 == 3))
					|| (q1 == 2 && q2 == 4 && quadrantTouched[3])
					|| (q1 == 4 && q2 == 2 && quadrantTouched[3])) {

				// CircleLayout.this.post(new FlingRunnable(-1
				// * (velocityX + velocityY)));
				new Thread(new FlingRunnable(velocityX+velocityY)).start();
			} else {
				// the normal rotation
				// CircleLayout.this
				// .post(new FlingRunnable(velocityX + velocityY));
				new Thread(new FlingRunnable(-(velocityX+velocityY))).start();
			}

			return true;

		}

		@Override
		public boolean onSingleTapUp(MotionEvent e) {

			int cur = getInCircle((int)e.getX(),(int)e.getY());
			if(cur!=-1){
				if(mListener!=null){
					mListener.onSingleTapUp(cur);
				}
//				Toast.makeText(getContext(), "position:"+cur, 0).show();
				return true;
			}
			return false;
		}

	}

	private class FlingRunnable implements Runnable{

		private float velocity;

		public FlingRunnable(float velocity){
			this.velocity = velocity;
		}

		@Override
		public void run() {
			// TODO Auto-generated method stub
			if(Math.abs(velocity)>=200){
				Message message = Message.obtain();
				message.what = TO_ROTATE_BUTTON;
				message.obj = velocity;
				handler.sendMessage(message);
			}
		}

	}

	/**
	 * @return The selected quadrant.
	 */
	private static int getQuadrant(double x, double y) {
		if (x >= 0) {
			return y >= 0 ? 1 : 4;
		} else {
		}
		return y >= 0 ? 2 : 3;
	}

	/*
	 * 旋转菜单按钮
	 */
	private void rotateButtons(double degree) {
		for (int i = 0; i < STONE_COUNT; i++) {
			mStones[i].angle -= degree;
			if (mStones[i].angle < 0) {
				mStones[i].angle += 360;
			}else if(mStones[i].angle >=360){
				mStones[i].angle -= 360;
			}
		}

		computeCoordinates();
		invalidate();
	}

	@Override
	public void onDraw(Canvas canvas) {
		//画一个白色的圆环
		canvas.drawCircle(mPointX, mPointY, mRadius, mPaint);

		//将每个菜单画出来
		for (int index = 0; index < STONE_COUNT; index++) {
			if (!mStones[index].isVisible)
				continue;
			drawInCenter(canvas, mStones[index].bitmap, mStones[index].x,
					mStones[index].y);
		}
	}

	/**
	 * 把中心点放到中心处
	 *
	 * @param canvas
	 * @param bitmap
	 * @param left
	 * @param top
	 */
	private void drawInCenter(Canvas canvas, Bitmap bitmap, float left,
			float top) {
		Rect dst = new Rect();
		dst.left = (int) (left - menuRadius);
		dst.right = (int) (left + menuRadius);
		dst.top = (int) (top - menuRadius);
		dst.bottom = (int) (top + menuRadius);
		canvas.setDrawFilter(pfd);
		canvas.drawBitmap(bitmap, null, dst, mPaint);
	}

	private int getInCircle(int x, int y) {
		for (int i = 0; i < STONE_COUNT; i++) {
			BigStone stone = mStones[i];
			int mx = (int) stone.x;
			int my = (int) stone.y;
			if (((x - mx) * (x - mx) + (y - my) * (y - my)) < menuRadius
					* menuRadius) {
				return i;
			}
		}
		return -1;
	}

	public void setOnRoundSpinViewListener(onRoundSpinViewListener listener){
		this.mListener = listener;
	}

	class BigStone {

		// 图片
		Bitmap bitmap;

		// 角度
		int angle;

		// x坐标
		float x;

		// y坐标
		float y;

		// 是否可见
		boolean isVisible = true;
	}
}

layout文件代码:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
     >

    <com.example.roundspinviewdemo.view.RoundSpinView
        android:id="@+id/rsv_test"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:background="@drawable/menubkground"
        app:menuStart="@drawable/menu1" />

</LinearLayout>

注意:必须加上这一条 :

xmlns:app="http://schemas.android.com/apk/res-auto"

此外必须添加attr文件设置对应的自定义属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 设置旋转菜单对应的第一张图片 -->
    <declare-styleable name="RoundSpinView">
        <attr name="menuStart" format="reference" />
    </declare-styleable>
</resources>

接下来就是activity中的应用了:

public class MainActivity extends Activity implements onRoundSpinViewListener {

	private RoundSpinView rsv_test;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
	}

	private void initView(){
		rsv_test = (RoundSpinView)this.findViewById(R.id.rsv_test);
		rsv_test.setOnRoundSpinViewListener(this);
	}

	@Override
	public void onSingleTapUp(int position) {
		// TODO Auto-generated method stub
		switch (position) {
		case 0:
            Toast.makeText(MainActivity.this, "place:0", 0).show();
			break;
		case 1:
			Toast.makeText(MainActivity.this, "place:1", 0).show();
			break;
		case 2:
			Toast.makeText(MainActivity.this, "place:2", 0).show();
			break;
		default:
			break;
		}
	}

}

注意:

rsv_test.setOnRoundSpinViewListener(this);

对自定义view的自定义监听器进行赋值

至此,你的项目就可以拥有看上去很高大上的旋转换位菜单功能

这里附上此demo对应的资源链接:点击打开链接

android圆形旋转菜单,并支持移动换位功能,布布扣,bubuko.com

时间: 2024-10-19 01:33:19

android圆形旋转菜单,并支持移动换位功能的相关文章

android圆形旋转菜单,而对于移动转换功能支持

LZ该公司最近接手一个项目,需要写一个圆形旋转菜单,和菜单之间的移动换位支持,我本来以为这样的demo如若互联网是非常.想想你妈妈也帮不了我,空旋转,但它不能改变位置,所以LZ我们只能靠自己摸索. 最后LZ参考代码的在线部分.了一个自己定义的view最终实现了这个看似非常吊.却没有实际意义的功能. 在此贡献出来给广大码农们共享. 话不多说,先上代码: 自己定义view类: public class RoundSpinView extends View { private Paint mPaint

Android圆形旋转菜单

[点击下载] 小生做程序也有些许日子,从一个青涩的小白,慢慢的成长为了小有成就的程序猿,从不知名的码农,到二三百人圈里还有点小名气的码霸. 要说辛苦,可能每个程序心中都有各自的理解,大学学的管理,毕业了工作不好找,后来机缘巧合接触了移动开发.自我感觉大学高数,线性,概率论学的还可以,于是培训学校学习了三个月的android,可是那些学科貌似也没帮上什么大忙.后来接触了算法,逻辑等等. 小生肤浅,认识的不够,其实这篇文章的目的也不算分享吧,算是交流,不知道各位大神的心得体会啊.让我学习学习~~ a

Android 圆形滚动菜单

dispatchTouchEvent检测旋转的角度,调用requestLayout()不停地重绘界面 public class CircleMenuLayout extends ViewGroup { private int mRadius; /** * 该容器内child item的默认尺寸 */ private static final float RADIO_DEFAULT_CHILD_DIMENSION = 1 / 4f; /** * 菜单的中心child的默认尺寸 */ private

Android实例-Delphi在运行时更改Android屏幕旋转(IOS也支持,但还没有写,下午我回来加上。不过我可没有苹果机,测试不了)

相关资料: https://www.it1352.com/624177.html 1 unit Unit2; 2 3 interface 4 5 uses 6 System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 7 FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMXTee.Engine, 8 FMXTee.P

Android之——史上最简单旋转菜单实现效果

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/48048323 由于身体原因,前几天没有给大家更新博客,那么,今天我们就来一起实现一个非常酷炫的旋转菜单效果吧.在很多APP中,不难发现,人家把菜单效果设计的那叫一个酷炫啊,其中一种设计就是将菜单设计成旋转的效果.好了,那么这么酷炫的菜单效果是如何实现的呢?下面,就让我们一起来实现这个酷炫的菜单效果吧. 一.原理 老规矩,还是先唠叨下原理级别的东西. 这个示例的实现原理很简单,利用

Android立体旋转动画实现与封装(支持以X、Y、Z三个轴为轴心旋转)

本文主要介绍Android立体旋转动画,或者3D旋转,下图是我自己实现的一个界面 立体旋转分为以下三种: 1. 以X轴为轴心旋转 2. 以Y轴为轴心旋转 3. 以Z轴为轴心旋转--这种等价于android默认自带的旋转动画RotateAnimation 实现立体旋转核心步骤: 1. 继承系统Animation重写applyTransformation方法 通过applyTransformation方法的回调参数 float interpolatedTime, Transformation t 来

下角动画旋转菜单、圆心弹出菜单ArcMenu 源码解析

支持类似Path的左下角动画旋转菜单及横向划出菜单.圆心弹出菜单 项目地址:https://github.com/daCapricorn/ArcMenu 一.关注3个效果 点击中心控制点 的时候,展开效果: 中心控制点旋转45度的动画 周围children 弹出动画 2.点击中心控制点的时候,收缩动画: 中心控制点旋转45度 周围children 自旋转并收缩 3.展开时候,点击child 被点击的child放大, 其他chidren 消失 二.3个java文件 ArcMenu.java  自定

Android 3D滑动菜单完全解析,实现推拉门式的立体特效

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/10471245 在上一篇文章中,我们学习了Camera的基本用法,并借助它们编写了一个例子,实现了类似于API Demos里的图片中轴旋转功能.不过那个例子的核心代码是来自于API Demos中带有的Rotate3dAnimation这个类,是它帮助我们完成了所有的三维旋转操作,所有Matrix和Camera相关的代码也是封装在这个类中. 这样说来的话,大家心里会不会痒痒的呢?虽然

Android中轴旋转特效实现,制作别样的图片浏览器

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/10766017 Android API Demos中有很多非常Nice的例子,这些例子的代码都写的很出色,如果大家把API Demos中的每个例子研究透了,那么恭喜你已经成为一个真正的Android高手了.这也算是给一些比较迷茫的Android开发者一个指出了一个提升自我能力的方向吧.API Demos中的例子众多,今天我们就来模仿其中一个3D变换的特效,来实现一种别样的图片浏览器