前言android
自定义view可以作出不少不一样寻常的效果,圆形菜单交互效果不错,目前网上有两个版本,虽然比较庞大,但很是值得研究与学习。git
radial-menu-widget: https://code.google.com/p/radial-menu-widget/github
Radial-Menu-Widget-Android:https://github.com/strider2023/Radial-Menu-Widget-Androidcanvas
这两个版本呢实际上第一个是最原始的做者Jason Valestin,后来被Arindam Nath修改后出现了后面的版本。在分析过程当中能够逐个击破,关键在于理解要点,本文讲自定义一个圆形的view做为自定义圆形菜单的一个入门基础,暂且命名为CircleView,点击后变化颜色。ide
源码连接: https://github.com/avenwu/RadialDemo/tree/draw_circle学习
实现this
若是要自定义一个圆形的菜单,那么现有的LinearLayout或者RelativeLayout等都已经没法知足,咱们须要从View直接继承,因为须要处理触摸事件,因此须要重载onTouchEvent(),来根据当前触摸的坐标额动做状态调整viewgoogle
,除此以外须要判断点击的位置出于菜单的但是区域内,在Android中全部的view本质上是矩形区域的,因此须要经过数学计算来判断当前是否点中了菜单,如今咱们对这两个关键作相似的实现。spa
1.初始化画笔菜单位置及大小3d
在合适的地方初始化资源是很重要的,对画笔和菜单半径等咱们能够在构造方法内赋值,
绘制一个圆还须要中心坐标,这个值咱们在view的大小肯定时初始化,不要尝试直接获取view的高宽,这两个值必须在view绘制后才拿的到,这里咱们在onMeasure和onSizeChanged均可以获得view的大小。
2.经过画布绘制菜单
绘制一个圆形比较简单,直接调用canvas的drawCircle方法
3.处理触摸事件
重载onTouchEvent方法咱们监听到view上面的触摸动做,通常来讲ACTION_DOWN, ACTION_UP, ACTION_MOVE都是比较重要的
咱们在点击view的时候更改画笔的颜色为粉红色,当手离开时改回默认的红色,因此能够在ACTION_DOWN时设置color为Color.MAGENTA, 在ACTION_UP时设置为Color.RED
另外在咱们按住屏幕后移动位置,这是有可能会移除菜单区域,因此在ACTION_MOVE时咱们也须要设置
4.计算触摸位置是否在菜单内
这一步将直接影响咱们的触摸效果,这里咱们的区域是圆形,因此比较好处理,只需计算触摸点到圆心的距离就能够知道相对位置。
后面当单一圆形菜单分割为多个菜单项时,位置计算会复杂一些。
完整代码:
package com.avenwu.radialdemo; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; /** * @author chaobin * @date 1/10/14. */ public class CircleView extends View { private float mCenterX; private float mCenterY; private float mRadius; private Paint mPaint; public CircleView(Context context) { this(context, null); } public CircleView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CircleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mRadius = getContext().getResources().getDisplayMetrics().density * 100; mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.RED); mPaint.setStrokeWidth(getContext().getResources().getDisplayMetrics().density * 5); } @Override protected void onDraw(Canvas canvas) { canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec) / 2; int height = MeasureSpec.getSize(heightMeasureSpec) / 2; Log.d("CircleView", "onMeasure, height=" + height + ", width=" + width); updateCenter(width, height); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); Log.d("CircleView", "onSizeChanged: w=" + w + ", h=" + h); updateCenter(w, h); } void updateCenter(int x, int y) { mCenterX = x / 2; mCenterY = y / 2; } boolean isInside(float x, float y) { return Math.pow(x - mCenterX, 2) + Math.pow(y - mCenterY, 2) <= Math.pow(mCenterX - getPaddingLeft(), 2); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (isInside(event.getX(), event.getY())) { mPaint.setColor(Color.MAGENTA); } break; case MotionEvent.ACTION_UP: mPaint.setColor(Color.RED); break; case MotionEvent.ACTION_MOVE: if (!isInside(event.getX(), event.getY())) { mPaint.setColor(Color.RED); } Log.d("CircleView", "position, x=" + event.getX() + ", y=" + event.getY()); break; } invalidate(); return true; } }