【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);
    }
}
时间: 2024-10-24 10:44:29

【Android】自定义环形菜单View的相关文章

Android自定义上下文菜单

今天自定义了一个简单的Android菜单控件.实现方式是:PopupWindow和ListView. 现在来给大家分享一下源码: SHContextMenu.java 核心代码部分:主要是对PopupWindow和ListView的初始化,为ListView设置数据源,以及封装了菜单的显示和隐藏的方法.还有提供了菜单的点击回调. import android.app.Activity; import android.content.Context; import android.graphics

Android自定义View4——统计图View

1.介绍 周末在逛慕课网的时候,看到了一张学习计划报告图,详细记录了自己一周的学习情况,天天都是0节课啊!正好在学习Android自定义View,于是就想着自己去写了一个,这里先给出一张慕课网的图,和自己的效果图. yissan的博客,未经允许严禁转载 http://blog.csdn.net/yissan 2.实现分析 我们要实现这样一个折线统计图,必要的信息主要有下面几个 先看纵轴,纵轴需要的信息有最大值,还有用来确定每个间距代表的单位,比如最大值是100,我们还要有一个将值分为几份的数据.

android自定义手势解锁View

有时候为了程序的安全性,我们经常要采取一些安全措施,就像我们常用的支付宝那样,隔一定的时间再回到应用程序时会让用户利用手势去解锁应用程序,最近由于项目需求,也要求做这样一个功能,当用户切出本应用程序15分钟后回来,让用户手势解锁,整个需求的难点就在如何实现这个手势锁,开始一点头绪也没有,没有一点思路去实现这个手势解锁功能,在google了一番后看了一篇非常好的博客后,按照博主的思路的确是可以实现一个十分不错的手势锁View,也参考了下那位大神的代码,下面是我根据他的思路和代码片段实现的一个自定义

android自定义LinearLayout和View

自定义线性布局经常用到: 第一种是在扩展的LinearLayout构造函数中使用Inflater加载一个布局,并从中提取出相关的UI组件进行封装,形成一个独立的控件.在使用该控件时,由于它所有的子元素都是在运行时通过代码动态创建的,所以该控件只能以一个独立控件的形式在Layout文件中声明,例如: public class CustomLayout extends LinearLayout{ public CustomLayout(Context context){ LayoutInflater

Android自定义Viewgroup切换View带有吸附效果

1.概述 先上效果图 大概就是这个效果,先说说实现思路 1.首先我们要拿到图片的url(网络)或id.路径(本地),将View与数据进行绑定,写我们自己的Adapter 2.自定义Viewgroup将要显示的view进行布局,以及处理触摸事件进行逻辑处理 3.写切换回调 2.实现 1)自定义Adapter 这里我下载的网络图片,同样可以将图片放到res下设置ImageView的内容 public class DragPageViewAdapter { private static final S

Android 自定义View修炼-打造完美的自定义侧滑菜单/侧滑View控件(转)

一.概述 在App中,经常会出现侧滑菜单,侧滑滑出View等效果,虽然说Android有很多第三方开源库,但是实际上 咱们可以自己也写一个自定义的侧滑View控件,其实不难,主要涉及到以下几个要点: 1.对Android中Window类中的DecorView有所了解 2.对Scroller类实现平滑移动效果 3.自定义ViewGroup的实现 首先来看看效果图吧:    下面现在就来说说这里咱们实现侧滑View的基本思路吧,这里我采用的是自定义一个继承于RelativeLayout的控件叫做XC

Android 自定义View合集

自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/Mr-XiaoLiang 自定义控件三部曲 http://blog.csdn.net/harvic880925?viewmode=contents Android 从0开始自定义控件之View基础知识与概念 http://blog.csdn.net/airsaid/article/details/5

【Android自定义View实战】之仿百度加载动画,一种优雅的Loading方式

转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53470872 本文出自[DylanAndroid的博客] Android自定义View实战之仿百度加载动画一种优雅的Loading方式 第一个仿百度加载动画用ObjectAnimator属性动画操作ImageView的属性方法实现 第二个仿百度加载动画第二种实现方式用ValueAnimator原生的ondraw方法实现 第三个扔球动画-水平旋转动画 第四个扔球动画-垂直旋转动

Android自定义TabActivity(实现仿新浪微博底部菜单更新UI)

如今Android上很多应用都采用底部菜单控制更新的UI这种框架,例如新浪微博 点击底部菜单的选项可以更新界面.底部菜单可以使用TabHost来实现,不过用过TabHost的人都知道自定义TabHost究竟是有多麻烦的,原生TabHost的风格是不依附屏幕的底部的,要依附底部就要重写布局. TabHost设置的Container可以管理UI的显示,UI可以用LayoutInflater动态生成,也可以是Activity,但不好管理Activity的生命周期.然后用TabHost控制显示UI的显示