【Android】自定义环形菜单View
实现的效果图:六个小图片可以跟随手指滑动绕中心点旋转
代码:
package com.example.test_canvas; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Point; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffXfermode; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; public class MyView extends SurfaceView { private final String TAG = "MyView"; private final int WHAT_MOVE = 0X013; private final int WHAT_CLICK = 0X014; private final int TIME_CLICK_INTERVAL = 200;// 按下抬起的间隔时间 // private Handler mThreadHandler = null; MyDrawableThread mThread = null; private MyViewClickListener mListener = null; private int Radius = 0;// 半径 Bitmap[] mItems = null; private int viewWidth = 0; private int viewHeight = 0; private int centerX = 0; private int centerY = 0; private List<Point> mPoints = new ArrayList<Point>(); private Handler mMainHandler = null; public MyView(Context context, AttributeSet attrs) { super(context, attrs); mMainHandler = new Handler(context.getMainLooper()); SurfaceHolder holder = this.getHolder(); holder.addCallback(mCallback); mThread = new MyDrawableThread(holder); mThread.start(); } /** * 设置图片 * * @param items */ public void setItem(Bitmap[] items, MyViewClickListener listener) { Log.d(TAG, "--->setItem"); if (items != null) { mItems = items; } mListener = listener; } float x = 0; float y = 0; private float mBuildDegree = 0; private long time_start = 0; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "ACTION_DOWN"); x = event.getX(); y = event.getY(); time_start = System.currentTimeMillis(); break; case MotionEvent.ACTION_UP: { Log.d(TAG, "ACTION_UP"); float dstx = event.getX(); float dsty = event.getY(); if ((System.currentTimeMillis() - time_start) <= TIME_CLICK_INTERVAL) { Log.d(TAG, "--->TIME_CLICK_INTERVAL"); mThreadHandler.sendMessage(mThreadHandler.obtainMessage( WHAT_CLICK, (int) dstx, (int) dsty)); } time_start = 0; x = 0; y = 0; break; } case MotionEvent.ACTION_MOVE: Log.d(TAG, "ACTION_MOVE"); float dstx = event.getX(); float dsty = event.getY(); Log.d(TAG, "src x: " + x + "y: " + y); Log.d(TAG, "dst x: " + dstx + "y: " + dsty); float degree = 0; degree = (float) getActionDegrees(centerX, centerY, dstx, dsty, x, y); Log.d(TAG, "degree: " + degree); x = dstx; y = dsty; mBuildDegree -= degree; mThreadHandler.sendMessage(mThreadHandler.obtainMessage(WHAT_MOVE, mBuildDegree)); break; default: break; } return true; } private class MyDrawableThread extends Thread { private SurfaceHolder mHolder; public MyDrawableThread(SurfaceHolder holder) { mHolder = holder; } @Override public void run() { Log.d(TAG, "--->MyThread run"); Looper.prepare(); mThreadHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case WHAT_MOVE: float degree = (Float) msg.obj; drawImgByRoate(degree); break; case WHAT_CLICK: if (mListener != null && mItems != null && mItems.length > 0 && mItems[0] != null) { int dstx = msg.arg1; int dsty = msg.arg2; Bitmap firstBitmap = mItems[0]; int interval = (firstBitmap.getHeight() > firstBitmap .getWidth()) ? firstBitmap.getWidth() / 2 : firstBitmap.getHeight() / 2; Log.d(TAG, "--->TIME_CLICK_INTERVAL tempdistance: " + interval); if (mPoints != null) { for (int i = 0; i < mPoints.size(); ++i) { Point point = mPoints.get(i); double tempx = Math.abs(point.x - dstx); double tempy = Math.abs(point.y - dsty); double distance = Math.sqrt(tempx * tempx + tempy * tempy); Log.d(TAG, "--->TIME_CLICK_INTERVAL for: " + i + " " + distance); if (distance <= interval) { final int index = i; mMainHandler.post(new Runnable() { @Override public void run() { Log.d(TAG, "--->TIME_CLICK_INTERVAL onItemClick: " + index); mListener.onItemClick(index); } }); break; } } } } break; default: super.handleMessage(msg); } } }; Looper.loop(); } private void drawImgByRoate(float degree) { Log.d(TAG, "mdegree>> " + degree); Canvas canvas = null; Paint paint = new Paint(); int count = mItems.length; try { synchronized (mHolder) { canvas = mHolder.lockCanvas(); clear(canvas); mPoints.clear(); for (int i = 0; i < count; i++) { Bitmap bitmap = mItems[i]; // 计算x,y float reaote = degree + (360 / count) * i; Log.d(TAG, i + " >> " + reaote); float x = (float) (Radius * Math.cos(Math.toRadians(reaote)) + centerX); float y = (float) (Radius * Math.sin(Math.toRadians(reaote)) + centerY); Log.d(TAG, "x>> " + x); Log.d(TAG, "y>> " + y); // draw int tempX = (int) (x - bitmap.getWidth() / 2); int tempY = (int) (y - bitmap.getHeight() / 2); mPoints.add(new Point((int) x, (int) y)); canvas.drawBitmap(bitmap, tempX, tempY, paint); } } } catch (Exception e) { e.printStackTrace(); } finally { if (canvas != null) { Log.d(TAG, "--->unlockCanvasAndPost"); mHolder.unlockCanvasAndPost(canvas);// 结束锁定画图,并提交改变。 } } } private void clear(Canvas canvas) { Paint paint = new Paint(); paint.setXfermode(new PorterDuffXfermode(Mode.CLEAR)); canvas.drawPaint(paint); paint.setXfermode(new PorterDuffXfermode(Mode.SRC)); } }; SurfaceHolder.Callback mCallback = new Callback() { @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub Log.d(TAG, "--->surfaceDestroyed"); } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub Log.d(TAG, "--->surfaceCreated"); viewWidth = getWidth(); viewHeight = getHeight(); Radius = (viewWidth / 2) - 100; Log.d(TAG, "viewWidth: " + viewWidth); Log.d(TAG, "viewHeight: " + viewHeight); centerX = viewWidth / 2; centerY = viewHeight / 2; mThreadHandler.sendMessage(mThreadHandler.obtainMessage(WHAT_MOVE, 0f)); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.d(TAG, "--->surfaceChanged"); } }; public void release() { if (mItems != null) { for (Bitmap bitmap : mItems) { bitmap = null; } } mItems = null; mListener = null; } /** * 获取两点到第三点的夹角。 * * @param centerX * @param centerY * @param nowX * @param nowY * @param oldX * @param oldY * @return */ private double getActionDegrees(float centerX, float centerY, float nowX, float nowY, float oldX, float oldY) { double a = Math.sqrt((nowX - oldX) * (nowX - oldX) + (nowY - oldY) * (nowY - oldY)); double b = Math.sqrt((centerX - oldX) * (centerX - oldX) + (centerY - oldY) * (centerY - oldY)); double c = Math.sqrt((nowX - centerX) * (nowX - centerX) + (nowY - centerY) * (nowY - centerY)); // 余弦定理 double cosA = (b * b + c * c - a * a) / (2 * b * c); // 返回余弦值为指定数字的角度,Math函数为我们提供的方法 double arcA = Math.acos(cosA); double degree = arcA * 180 / Math.PI; // 接下来我们要讨论正负值的关系了,也就是求出是顺时针还是逆时针。 // 第1、2象限 if (nowY < centerY && oldY < centerY) { if (nowX < centerX && oldX > centerX) {// 由2象限向1象限滑动 return degree; } // 由1象限向2象限滑动 else if (nowX >= centerX && oldX <= centerX) { return -degree; } } // 第3、4象限 if (nowY > centerY && oldY > centerY) { // 由3象限向4象限滑动 if (nowX < centerX && oldX > centerX) { return -degree; } // 由4象限向3象限滑动 else if (nowX > centerX && oldX < centerX) { return degree; } } // 第2、3象限 if (nowX < centerX && oldX < centerX) { // 由2象限向3象限滑动 if (nowY < centerY && oldY > centerY) { return -degree; } // 由3象限向2象限滑动 else if (nowY > centerY && oldY < centerY) { return degree; } } // 第1、4象限 if (nowX > centerX && oldX > centerX) { // 由4向1滑动 if (nowY > centerY && oldY < centerY) { return -degree; } // 由1向4滑动 else if (nowY < centerY && oldY > centerY) { return degree; } } // 在特定的象限内 float tanB = (nowY - centerY) / (nowX - centerX); float tanC = (oldY - centerY) / (oldX - centerX); if ((nowX > centerX && nowY > centerY && oldX > centerX && oldY > centerY && tanB > tanC)// 第一象限 || (nowX > centerX && nowY < centerY && oldX > centerX && oldY < centerY && tanB > tanC)// 第四象限 || (nowX < centerX && nowY < centerY && oldX < centerX && oldY < centerY && tanB > tanC)// 第三象限 || (nowX < centerX && nowY > centerY && oldX < centerX && oldY > centerY && tanB > tanC))// 第二象限 return -degree; return degree; } public interface MyViewClickListener { public void onItemClick(int itemIndex); } }