前言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; } }