精华内容
下载资源
问答
  • android圆盘菜单效果

    千次阅读 2013-04-26 19:58:06
    /** 菜单背景 */ private Bitmap turnPlateBg; /** * point列表 */ private Point[] points; /** * 数目 */ private static final int PONIT_NUM = 3; /** * 圆心坐标 */ private int ...
    public class TurnplateView extends View implements OnTouchListener {
    
     private OnTurnplateListener onTurnplateListener;
    
     public void setOnTurnplateListener(OnTurnplateListener onTurnplateListener) {
      this.onTurnplateListener = onTurnplateListener;
     }
    
     /**
      * 画笔:点、线
      */
     private Paint mPaint = new Paint();
     /**
      * 画笔:圆
      */
     private Paint paintCircle = new Paint();
     /**
      * 图标列表
      */
     private Bitmap[] icons = new Bitmap[10];
     /** 菜单背景 */
     private Bitmap turnPlateBg;
     /**
      * point列表
      */
     private Point[] points;
     /**
      * 数目
      */
     private static final int PONIT_NUM = 3;
    
     /**
      * 圆心坐标
      */
     private int mPointX = 0, mPointY = 0;
     /**
      * 半径
      */
     private int mRadius = 0;
     /**
      * 每两个点间隔的角度
      */
     private int mDegreeDelta;
     /**
      * 每次转动的角度差
      */
     private int tempDegree = 0;
     /**
      * 选中的图标标识
      */
     private int curChooseIndex = -1;
     private Matrix mMatrix = new Matrix();
     // 每个菜单的宽和高
     private int itemWidth, itemHeight;
    
     public TurnplateView(Context context, int px, int py, int radius) {
      super(context);
      mPaint.setColor(Color.RED);
      mPaint.setStrokeWidth(2);
      paintCircle.setAntiAlias(true);
      paintCircle.setColor(Color.WHITE);
    
      mPointX = px;
      mPointY = py;
      mRadius = radius;
    
      itemWidth = radius * 2 / 3;
      itemHeight = radius * 2 / 3;
    
      loadIcons();
    
      initPoints();
      computeCoordinates();
     }
    
     public void loadBitmaps(int key, Drawable d) {
      Bitmap bitmap = Bitmap.createBitmap(itemWidth, itemHeight,
        Bitmap.Config.ARGB_8888);
      Canvas canvas = new Canvas(bitmap);
      d.setBounds(0, 0, itemWidth, itemHeight);
      d.draw(canvas);
      icons[key] = bitmap;
      turnPlateBg = BitmapUtil.getResizeBitmap(getContext(),
        R.drawable.turnplate_bg, mRadius * 2, mRadius * 2);
     }
    
     public void loadIcons() {
      Resources r = getResources();
      loadBitmaps(0, r.getDrawable(R.drawable.tools_ad_icon));
      loadBitmaps(1, r.getDrawable(R.drawable.tools_huangli_icon));
      loadBitmaps(2, r.getDrawable(R.drawable.tools_preview));
     }
    
     public void setPoints(Point[] points) {
      this.points = points;
     }
    
     /**
      * 
      * 方法名:initPoints 功能:初始化每个点
      */
    
     private void initPoints() {
      points = new Point[PONIT_NUM];
      Point point;
      int angle = 0;
      mDegreeDelta = 360 / PONIT_NUM;
    
      for (int index = 0; index < PONIT_NUM; index++) {
       point = new Point();
       point.angle = angle;
       angle += mDegreeDelta;
       point.bitmap = icons[index];
       point.flag = index;
       points[index] = point;
       switch (index) {
       case 0:
        points[index].intent = new Intent(getContext(),
          IndexActivity.class);
        break;
       case 1:
        points[index].intent = new Intent(getContext(),
          CalendarActivity.class);
        break;
       case 2:
        points[index].intent = new Intent(getContext(),
          AnimDemoActivity.class);
        break;
       }
    
      }
     }
    
     /**
      * 
      * 方法名:resetPointAngle 功能:重新计算每个点的角度 参数:
      * 
      * @param x
      * @param y
      */
     private void resetPointAngle(float x, float y) {
      int degree = computeMigrationAngle(x, y);
      for (int index = 0; index < PONIT_NUM; index++) {
       points[index].angle += degree;
       if (points[index].angle > 360) {
        points[index].angle -= 360;
       } else if (points[index].angle < 0) {
        points[index].angle += 360;
       }
    
      }
     }
    
     /**
      * 
      * 方法名:computeCoordinates 功能:计算每个点的坐标
      */
     private void computeCoordinates() {
      Point point;
      for (int index = 0; index < PONIT_NUM; index++) {
       point = points[index];
       point.x = mPointX
         + (float) (mRadius * Math.cos(point.angle * Math.PI / 180));
       point.y = mPointY
         + (float) (mRadius * Math.sin(point.angle * Math.PI / 180));
       point.x_c = mPointX + (point.x - mPointX) / 2;
       point.y_c = mPointY + (point.y - mPointY) / 2;
       // Log.e(TAG, point.angle+"");
      }
     }
    
     /**
      * 
      * 方法名:computeMigrationAngle 功能:计算偏移角度 参数:
      * 
      * @param x
      * @param y
      */
     private int computeMigrationAngle(float x, float y) {
      int a = 0;
      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;
      }
      if (tempDegree != 0) {
       a = degree - tempDegree;
      }
      tempDegree = degree;
      return a;
     }
    
     /**
      * 
      * 方法名:computeCurrentDistance 功能:计算触摸的位置与各个元点的距离 参数:
      * 
      * @param x
      * @param y
      */
     private void computeCurrentDistance(float x, float y) {
      for (Point point : points) {
       float distance = (float) Math
         .sqrt(((x - point.x) * (x - point.x) + (y - point.y)
           * (y - point.y)));
       if (distance < 31) {
        curChooseIndex = point.flag;
        break;
       } else {
        curChooseIndex = 999;
       }
      }
     }
    
     private void switchScreen(MotionEvent event) {
      computeCurrentDistance(event.getX(), event.getY());
      onTurnplateListener.onPointTouch(curChooseIndex);
    
     }
    
     @Override
     public boolean dispatchTouchEvent(MotionEvent event) {
      int action = event.getAction();
      switch (action) {
      case MotionEvent.ACTION_DOWN:
       break;
      case MotionEvent.ACTION_MOVE:
       resetPointAngle(event.getX(), event.getY());
       computeCoordinates();
       invalidate();
       break;
      case MotionEvent.ACTION_UP:
       switchScreen(event);
       tempDegree = 0;
       invalidate();
       break;
      case MotionEvent.ACTION_CANCEL:
       // 系统在运行到一定程度下无法继续响应你的后续动作时会产生此事件。
       // 一般仅在代码中将其视为异常分支情况处理
       break;
      }
      return true;
     }
    
     @Override
     public void onDraw(Canvas canvas) {
      // canvas.drawCircle(mPointX, mPointY, mRadius, paintCircle);
      canvas.drawBitmap(turnPlateBg, mPointX - mRadius, mPointY - mRadius,
        paintCircle);
      canvas.drawPoint(mPointX, mPointY, mPaint);
    
      for (int index = 0; index < PONIT_NUM; index++) {
       canvas.drawPoint(points[index].x_c, points[index].y_c, mPaint);
       drawInCenter(canvas, points[index].bitmap, points[index].x,
         points[index].y, points[index].flag);
      }
    
     }
    
     /**
      * 
      * 方法名:drawInCenter 功能:把点放到图片中心处 参数:
      * 
      * @param canvas
      * @param bitmap
      * @param left
      * @param top
      */
     void drawInCenter(Canvas canvas, Bitmap bitmap, float left, float top,
       int flag) {
      canvas.drawPoint(left, top, mPaint);
      if (curChooseIndex == flag) {
       mMatrix.setScale(70f / bitmap.getWidth(), 70f / bitmap.getHeight());
       mMatrix.postTranslate(left - 35, top - 35);
       canvas.drawBitmap(bitmap, mMatrix, null);
      } else {
       canvas.drawBitmap(bitmap, left - bitmap.getWidth() / 2, top
         - bitmap.getHeight() / 2, null);
      }
    
     }
    
     class Point {
    
      /**
       * 位置标识
       */
      int flag;
      /**
       * 图片
       */
      Bitmap bitmap;
    
      /**
       * 角度
       */
      int angle;
    
      /**
       * x坐标
       */
      float x;
    
      /**
       * y坐标
       */
      float y;
    
      /**
       * 点与圆心的中心x坐标
       */
      float x_c;
      /**
       * 点与圆心的中心y坐标
       */
      float y_c;
      // 保存的数据
      Intent intent;
     }
    
     public static interface OnTurnplateListener {
    
      public void onPointTouch(int flag);
    
     }
    
     @Override
     public boolean onTouch(View arg0, MotionEvent arg1) {
      return false;
     }
    
     public Intent getCurIntent(int flag) {
      if (flag >= 0 && flag < points.length) {
       Point curPoint = points[flag];
       return curPoint.intent;
      } else {
       return null;
      }
     }
    


    展开全文
  • 导语:最近项目中要用的圆盘菜单,在网上找了一圈发现现有的demo都不能拿过来直接用因为都是太基础了,所以自己在原有的基础上修改了下,增加了点击事件以及修正了滑动后总是从第一个开始绘图的bug。 ...

    导语:最近项目中要用的圆盘菜单,在网上找了一圈发现现有的demo都不能拿过来直接用因为都是太基础了,所以自己在原有的基础上修改了下,增加了点击事件以及修正了滑动后总是从第一个开始绘图的bug。


    package chroya.demo.roundspin;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.Toast;
    
    /**
     * 圆盘式的view
     * @author chroya
     *
     */
    public class RoundSpinView extends View {
    	private Paint mPaint = new Paint();
    
    	private BigStone[] mStones;
    	private static final int STONE_COUNT = 21;
    	private int mPointX=0, mPointY=0;
    	private int mRadius = 0;
    	private int mDegreeDelta;
    	private boolean isAllowed = false;
    	private boolean isMove = false;
    	private BigStone currentbstone = null;
    
    	public RoundSpinView(Context context, int px, int py, int radius) {
    		super(context);
    		mPaint.setColor(Color.RED);
    		mPaint.setStrokeWidth(2);
    		setBackgroundResource(R.drawable.menubkground);
    
    		mPointX = px;
    		mPointY = py;
    		mRadius = radius;
    
    		setupStones();
    		computeCoordinates();
    	}
    
    	/**
    	 * 初始化
    	 */
    	private void setupStones() {
    
    		mStones = new BigStone[STONE_COUNT];
    		BigStone stone;
    		int angle = 0;
    		mDegreeDelta = 360/STONE_COUNT;
    
    		for(int index=0; index<STONE_COUNT; index++) {
    			stone = new BigStone();
    			stone.index = index;
    			stone.content = index + "";
    			stone.angle = angle;
    			stone.bitmap = drawNumAtBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.icon),index);
    			//增加点击事件
    			stone.action = new ActionEvent(){
    				@Override
    				public void action(BigStone stone) {
    					Toast.makeText(RoundSpinView.this.getContext(), "你点击了我 ! " + stone.content, Toast.LENGTH_SHORT).show();
    				}
    			};
    			angle += mDegreeDelta;
    			mStones[index] = stone;
    		}
    	}
    
    	/**
    	 * 重新计算每一个元素的坐标位置
    	 * @param x
    	 * @param y
    	 */
    	private void resetStonesAngle(float x, float y) {
    		int angle = computeCurrentAngle(x, y);
    		for(int index=0; index<STONE_COUNT; index++) {
    			//从下标为0的开始
    			for(BigStone bs : mStones){
    				if(bs.index == index){
    					bs.angle = angle;
    					break;
    				}
    			}
    			angle += mDegreeDelta;
    		}
    	}
    
    	private void computeCoordinates() {
    		for(BigStone stone : mStones) {
    			stone.x = mPointX+ (float)(mRadius * Math.cos(stone.angle*Math.PI/180));
    			stone.y = mPointY+ (float)(mRadius * Math.sin(stone.angle*Math.PI/180));
    		}
    	}
    
    	/**
    	 * 计算手指划过的线性距离
    	 * @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;
    		}
    		return degree;
    	}
    
    	@Override
    	public boolean dispatchTouchEvent(MotionEvent event) {
    		if(event.getAction() == MotionEvent.ACTION_DOWN){
    			isMove = false;
    			int index = 0;
    			float min = 320;
    			//求出点击坐标X值与那么元素的坐标位置X值最为接近
    			for(BigStone bs : mStones){
    				if(bs.x >= 0 && bs.x <= 320 && min >= Math.max(bs.x, event.getX()) - Math.min(bs.x, event.getX())){
    					min = Math.max(bs.x, event.getX()) - Math.min(bs.x, event.getX());
    					index = bs.index;
    				}
    			}
    			//或者该元素内容
    			for(BigStone bs : mStones){
    				if(bs.index == index){
    					currentbstone = bs;
    					break;
    				}
    			}
    			//判断Y值是否也为最接近(可根据自己情况是否增加这段逻辑)
    			if(currentbstone.y >= 0 && currentbstone.y <= 430 && 80 >= Math.max(currentbstone.y, event.getY()) - Math.min(currentbstone.y, event.getY())){
    				isAllowed = true;
    				if(index > 0){
    					for(BigStone bs : mStones){
    						if(bs.index >= index){
    							bs.index = bs.index - index;
    						}else{
    							bs.index += STONE_COUNT - index;
    						}
    					}
    				}
    			}else{
    				isAllowed = false;
    			}
    		}
    		if(event.getAction() == MotionEvent.ACTION_MOVE && isAllowed){
    			isMove = true;
    			resetStonesAngle(event.getX(), event.getY());
    			computeCoordinates();
    			invalidate();
    		}
    		if(event.getAction() == MotionEvent.ACTION_UP && isAllowed){
    			if(!isMove)
    				currentbstone.action.action(currentbstone);
    		}
    		return true;
    	}
    
    	@Override
    	public void onDraw(Canvas canvas) {
    		canvas.drawPoint(mPointX, mPointY, mPaint);
    		for(int index=0; index<STONE_COUNT; index++) {
    			for(BigStone bs : mStones){
    				if(bs.index == index){
    					drawInCenter(canvas, bs.bitmap, bs.x, bs.y);
    					canvas.drawLine(mPointX, mPointY, bs.x, bs.y, mPaint);
    					break;
    				}
    			}
    		}
    	}
    
    	/**
    	 * 把中心点放到中心处
    	 * @param canvas
    	 * @param bitmap
    	 * @param left
    	 * @param top
    	 */
    	void drawInCenter(Canvas canvas, Bitmap bitmap, float left, float top) {
    		canvas.drawPoint(left, top, mPaint);
    		canvas.drawBitmap(bitmap, left-bitmap.getWidth()/2, top-bitmap.getHeight()/2, null);
    	}
    
    	interface ActionEvent{
    		public void action(BigStone stone);
    	}
    
    	class BigStone {
    
    		int index;
    		//图片
    		Bitmap bitmap;
    
    		//角度
    		int angle;
    
    		//x坐标
    		float x;
    
    		//y坐标
    		float y;
    
    		String content;
    
    		ActionEvent action;
    	}
    
    	public static Bitmap drawNumAtBitmap(Bitmap bitmap, int num) {
    		int x = bitmap.getWidth();
    		int y = bitmap.getHeight();
    		Bitmap newbit = Bitmap.createBitmap(x, y, Bitmap.Config.ARGB_8888);
    		Canvas canvas = new Canvas(newbit);
    		Paint paint = new Paint();
    		canvas.drawBitmap(bitmap, 0, 0, paint);
    		paint.setColor(Color.RED);
    		paint.setTextSize(20);
    		if (num >= 10) {
    			canvas.drawText(num + "", 9, 25, paint);
    		} else {
    			canvas.drawText(num + "", 15, 25, paint);
    		}
    		canvas.save(Canvas.ALL_SAVE_FLAG);
    		canvas.restore();
    		bitmap.recycle();
    		return newbit;
    	}
    }
    

    package chroya.demo.roundspin;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    public class Main extends Activity {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(new RoundSpinView(getApplicationContext(), 160, 430, 240));
        }
    }
    我还不会上传源码...研究下上传上来~!

    展开全文
  • Android圆盘旋转菜单实例,半圆形的组合菜单,会旋转,类似淘宝网客户端的圆形菜单,非常适合移动设备浏览的菜单,有兴趣可下载本Android菜单测试下。
  • 自定义组件,仿优酷圆盘菜单。动画类的编写,修正各种小BUG,提升用户体验。欢迎下载交流学习!
  • 摘要:Java源码,Android,旋转菜单,Android源码 Android高仿【优酷】圆盘旋转菜单的源码下载,Android的调试环境现还没有配置起来,暂时无法抓图,代码也没有测试,大家自己下载测试吧,当初是花了高分下载来的。
  • Android简单的圆盘菜单实现多个ImageView绕某一中心点旋转
  • android 右下角弹出1/4圆盘菜单的效果,默认只显示一个加号的图标,点击后动画弹出1/4圆形的两层菜单
  • 看了建行的圆盘菜单,效果还不错,于是也动手试试做一个,目标——高度定制化,数量、样式及动画,


    看了建行的圆盘菜单,效果还不错,于是也动手试试做一个,目标——高度定制化,数量、样式及动画。

    为什么要用适配器做成定制化?你知道的,UI那边总是动不动就改的,加点什么啊,删点什么啊,而且有多态机器需要适配,底色不一样就算了,数量和Item也不一样,怎么搞啊?我们总不能每一次都改一大串吧,改一大串和重复类似工作对我们来说简直就是折磨,所以,需要定制化。当然,如果第二次就直接淘汰圆盘了,那另当别论,

    工程代码:https://github.com/aknew123/CircleMenu  点击打开链接

    效果如下:

                                 


    程序架构的UML图,如下:



    一、圆盘菜单自定义控件的使用

    网上查看了一下,看一下他们的实现方式千篇一律,功能都写在一个文件里,阅读难度稍大,于是采用适配器模式做一个,把view和实现逻辑分离,就是简单的MVC,代码结构如下图:


    当然,这只是一个Library,把DefaultMenuAdapter删了,重新编译就可以直接用jar包了,测试模块写在另一个工程,使用示例如下:


    package com.example.circlemenutest;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import android.animation.ObjectAnimator;
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.ImageView;
    import android.widget.Toast;
    
    import com.pan.tanglang.circlemenu.model.CircleMenuStatus;
    import com.pan.tanglang.circlemenu.view.CircleMenu;
    import com.pan.tanglang.circlemenu.view.CircleMenu.OnMenuItemClickListener;
    import com.pan.tanglang.circlemenu.view.CircleMenu.OnMenuStatusChangedListener;
    
    public class MainActivity extends Activity {
    
    	public static final String TAG = "MainActivity";
    
    	private String[] mItemTexts = new String[] { "安全中心", "特殊服务", "投资理财", "转账汇款", "我的账户", "信用卡", "腾讯", "阿里", "百度" };
    	private int[] mItemImgs = new int[] { R.drawable.foreign01, R.drawable.foreign02, R.drawable.foreign03,
    			R.drawable.foreign04, R.drawable.foreign05, R.drawable.foreign06, R.drawable.foreign07,
    			R.drawable.foreign08, R.drawable.foreign09 };
    
    	private CircleMenu mCircleMenu;
    	private ImageView ivCenter;
    
    	private float startRotate;
    	private float startFling;
    
    	ObjectAnimator animRotate = null;
    	ObjectAnimator animFling = null;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		mCircleMenu = (CircleMenu) findViewById(R.id.cm_main);
    		ivCenter = (ImageView) findViewById(R.id.iv_center_main);
    		ivCenter.setOnClickListener(new OnClickListener() {
    
    			@Override
    			public void onClick(View v) {
    				Toast.makeText(MainActivity.this, "圆盘中心", Toast.LENGTH_SHORT).show();
    
    			}
    		});
    		mCircleMenu.setOnItemClickListener(new OnMenuItemClickListener() {
    
    			@Override
    			public void onClick(View view, int position) {
    				Toast.makeText(MainActivity.this, mItemTexts[position], Toast.LENGTH_SHORT).show();
    			}
    		});
    		mCircleMenu.setOnStatusChangedListener(new OnMenuStatusChangedListener() {
    
    			@Override
    			public void onStatusChanged(CircleMenuStatus status, double rotateAngle) {
    				// TODO 可在此处定制各种动画
    				odAnimation(status, (float)rotateAngle);
    			}
    
    		});
    		List<ItemInfo> data = new ArrayList<>();
    		ItemInfo item = null;
    		for (int i = 0; i < mItemTexts.length; i++) {
    			item = new ItemInfo(mItemImgs[i], mItemTexts[i]);
    			data.add(item);
    		}
    
    		mCircleMenu.setAdapter(new CircleMenuAdapter(data));
    
    	}
    
    	private void odAnimation(CircleMenuStatus status, float rotateAngle) {
    
    		switch (status) {
    		case IDLE:
    			Log.i(TAG, "--- -IDLE-----");
    			animRotate.cancel();
    			animRotate.cancel();
    			break;
    		case START_ROTATING:
    			Log.i(TAG, "--- -START_ROTATING-----");
    			break;
    		case ROTATING:
    			animRotate = ObjectAnimator.ofFloat(ivCenter, "rotation", startRotate, startRotate + rotateAngle);
    			animRotate.setDuration(200).start();
    			startRotate += rotateAngle;
    			// Log.i(TAG, "--- -ROTATING-----");
    			break;
    		case STOP_ROTATING:
    			Log.i(TAG, "--- -STOP_ROTATING-----");
    			break;
    		case START_FLING:
    			Log.i(TAG, "--- -START_FLING-----");
    			break;
    
    		case FLING:
    			// Log.i(TAG, "--- -FLING-----");
    			animFling = ObjectAnimator.ofFloat(ivCenter, "rotation", startFling, startFling + rotateAngle);
    			animFling.setDuration(200).start();
    			startFling += rotateAngle;
    			break;
    		case STOP_FLING:
    			Log.i(TAG, "--- -STOP_FLING-----");
    
    			break;
    
    		default:
    			break;
    		}
    
    	}
    }
    

    后面的是用户定制化动画实现(提供诸多状态,爱怎么折腾怎么折腾)。好了,我们来看看实现原理。

    二、实现原理

    1.先看需要做什么

    (1).圆盘菜单CircleMenu是一个转盘,装有各种Item,是一个容器,那理所当然是继承ViewGroup,为了方便实现飞转,所以监听用户手势OnGestureListener;

    (2).菜单项CircleItemView,虽是一个item,但为了可定制化,当然也是一个容器,也是为了方便实现飞转,重写onFling方法,所以这里继承LinearLayout,纯属为了方便布局,若有特殊需求,可在布局的时候,在外层添加一个FrameLayout容器,爱怎么搞怎么搞;

    (3).Adapter和Model,既然有Item那肯定也有Adapter和数据model,Adapter继承BaseAdapter即可,就跟ListView一样。

    好了,就这3个玩意儿,另外几个都是从上面这两个抽出来的。

    2.旋转原理

    来看一下转动分析图,


    图中圆心的坐标应为( mRadius, mRadius),则圆盘半径为mRadius,按照大多数人的习惯右手向下滑动,圆盘也就跟着顺时针转动(或者说滚动),当然不是圆盘在转,是菜单项在滚动,滚动了弧度为a,那途中Item的x、y坐标应为

    x = tmp *cos a;

    y = tmp*sin a;

    这是相对于圆心的坐标,再加上圆盘的半径,就是圆盘中的坐标了,子View只管在父容器中的坐标,父容器布局时会加上自身的left和top,这样逐层往上推就是在屏幕中的坐标了)。

    我们将Item强制为正方形,itemWidth为Item的宽度,当 tmp  = mRaiuds - itemWidth / Math.sqrt(2) 时,那Item的外直角就走在圆盘圆周上,再大则要出边界了,所以,Item的中心点位于mRaiuds/2和mRaiuds - itemWidth / Math.sqrt(2)之间最为合理,那么,item的x、y为

    		final int childCount = getChildCount();
    		int left, top, halfDiagonal;
    		// 限制Item的宽高
    		int itemWidth = (int) (mRadius * RADIO_DEFAULT_CHILD_DIMENSION);
    		float angleDelay = 360 / childCount;
    		for (int i = 0; i < childCount; i++) {
    			final View child = getChildAt(i);
    			if (child.getVisibility() == View.GONE) {
    				continue;
    			}
    			mStartAngle %= 360;
    			// 取Item对角线的一半为Item中心到圆盘圆周的距离
    			halfDiagonal = (int) (itemWidth / Math.sqrt(2));
    			float distanceFromCenter = mRadius - halfDiagonal - mPadding;
    			left = mRadius + (int) Math.round(distanceFromCenter * Math.cos(Math.toRadians(mStartAngle)) - 1 / 2f * itemWidth);
    			top = mRadius + (int) Math.round(distanceFromCenter * Math.sin(Math.toRadians(mStartAngle)) - 1 / 2f * itemWidth);
    			// 重新Layout
    			child.layout(left, top, left + itemWidth, top + itemWidth);
    			mStartAngle += angleDelay;
    		}



    left 为 x, top 为y,mStartAngle为间隔角度,一个for循环就可以把所有Item按角度Layout出来。要让他们滚动起来,只要在ACTION_MOVE的时候给一个角度就行。那转动原理就这样了。

    3.测量和布局

    Android View 的绘制过程,如下图


    过程为 measure() ---> layout () ---> draw(),measure() 中会掉用onMeasure()方法, layout () 中调用onLayout()方法,draw()我们就不管了,因为还是SDK继承而来,他可以自己绘制。

    onMeasure方法实现如下:

    	@Override
    	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    		// 测量自身
    		// measureMyself(widthMeasureSpec, heightMeasureSpec);
    		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    		// 测量子View
    		measureChildViews();
    
    	}
    
    	private void measureChildViews() {
    		if (mAdapter.getCount() <= 0) {
    			return;
    		}
    
    		// 获取半径,
    		mRadius = Math.max(getMeasuredWidth(), getMeasuredHeight()) / 2;
    		final int count = getChildCount();
    		// 取mRadius/2为Item宽度
    		int childSize = (int) (mRadius * RADIO_DEFAULT_CHILD_DIMENSION);
    		int childMode = MeasureSpec.EXACTLY;
    		int makeMeasureSpec = -1;
    		for (int i = 0; i < count; i++) {
    			final View child = getChildAt(i);
    			if (child.getVisibility() == View.GONE) {
    				continue;
    			}
    			makeMeasureSpec = MeasureSpec.makeMeasureSpec(childSize, childMode);
    			// 设置为正方形
    			child.measure(makeMeasureSpec, makeMeasureSpec);
    		}
    		// 取mRadius/10为默认内边距
    		if (mPadding == -1) {
    			mPadding = RADIO_PADDING_LAYOUT * mRadius;
    		}
    	}

    onLayout()在父类里是一个抽象方法,没有返回值,所以其中没有改变任何成员变量的话,是没有效果的。

    	@Override
    	protected void onLayout(boolean changed, int l, int t, int r, int b) {
    		if (mAdapter.getCount() <= 0) {
    			return;
    		}
    		final int childCount = getChildCount();
    		int left, top, halfDiagonal;
    		// 限制Item的宽高
    		int itemWidth = (int) (mRadius * RADIO_DEFAULT_CHILD_DIMENSION);
    		float angleDelay = 360 / childCount;
    		for (int i = 0; i < childCount; i++) {
    			final View child = getChildAt(i);
    			if (child.getVisibility() == View.GONE) {
    				continue;
    			}
    			mStartAngle %= 360;
    			// 取Item对角线的一半为Item中心到圆盘圆周的距离
    			halfDiagonal = (int) (itemWidth / Math.sqrt(2));
    			float distanceFromCenter = mRadius - halfDiagonal - mPadding;
    			left = mRadius
    					+ (int) Math.round(distanceFromCenter * Math.cos(Math.toRadians(mStartAngle)) - 1 / 2f * itemWidth);
    			top = mRadius
    					+ (int) Math.round(distanceFromCenter * Math.sin(Math.toRadians(mStartAngle)) - 1 / 2f * itemWidth);
    			// 重新Layout
    			child.layout(left, top, left + itemWidth, top + itemWidth);
    			mStartAngle += angleDelay;
    		}
    
    	}
    onLayout里就是把所有的Iitem逐个布局,这样就显示一个静态的圆盘了。


    4.圆盘滚动

    圆盘的滚动,可以在onTouchEvent(MotionEvent event)方法里实现,但这意味着你要同时在CircleMenu和CircleItemView的onTouchEvent方法里都要实现,当然两套代码可以提出来(但有可能子View和父View的x、y坐标不同,有麻烦),由于我们知道Android事件分发机制,Touch事件首先是父View的dispatchTouchEvent(MotionEvent event)先获得,然后往下分发,所以在dispatchTouchEvent方法实现Item转动一举两得,还有个原因是我们还要监听手势,需要重写onTouchEvent方法,so。。。。Item的滚动通过重写dispatchTouchEvent实现,代码如下:

    	// 因为父view的dispatchTouchEvent先获得事件,所以在这里可以连带子view的滚动事件一起处理
    	@Override
    	public boolean dispatchTouchEvent(MotionEvent event) {
    		if (mRotate != null) {
    			mRotate.onCircleMenuTouch(event, mRadius, this);
    		}
    		return super.dispatchTouchEvent(event);
    
    	}

    简单吧,呵呵,提出来了,提到了RotateEngine.java里实现,如下:

    package com.pan.tanglang.circlemenu.control;
    
    import android.util.Log;
    import android.view.MotionEvent;
    
    import com.pan.tanglang.circlemenu.model.CircleMenuStatus;
    import com.pan.tanglang.circlemenu.view.CircleMenu;
    import com.pan.tanglang.circlemenu.view.CircleMenu.RotateDirection;
    
    /**
     * 随手势旋转引擎
     */
    public class RotateEngine {
    
    	public static final String TAG = "RotateEngine";
    	private static RotateEngine instance = null;
    	private float startX;
    	private float startY;
    	/** 请求重新布局的起始角度 **/
    	private double mStartAngle;
    
    	private RotateEngine() {
    	}
    
    	public static RotateEngine getInstance() {
    		if (instance == null) {
    			synchronized (RotateEngine.class) {
    				if (instance == null) {
    					instance = new RotateEngine();
    				}
    			}
    		}
    		return instance;
    	}
    
    	// 通过dispatch实现
    	public void onCircleMenuTouch(MotionEvent ev, int radius, CircleMenu mCircleMenu) {
    		switch (ev.getAction()) {
    		case MotionEvent.ACTION_DOWN:
    			startX = ev.getX();
    			startY = ev.getY();
    			break;
    		case MotionEvent.ACTION_MOVE:
    			double start = getAngle(startX, startY, radius);
    			if (mCircleMenu.getStatus() == CircleMenuStatus.STOP_FLING
    					|| mCircleMenu.getStatus() == CircleMenuStatus.IDLE) {
    				// 开始滚动
    				mCircleMenu.startRotate();
    				// 拿到Fling停下(自然停或按停)之后的开始角度
    				mStartAngle = mCircleMenu.getmStartAngle();
    			}
    			float x = ev.getX();
    			float y = ev.getY();
    			toCircleMenuScroll(mCircleMenu, radius, start, ev);
    			startX = x;
    			startY = y;
    			break;
    		case MotionEvent.ACTION_UP:
    			CircleMenuStatus status = mCircleMenu.getStatus();
    			Log.i(TAG, "---ACTION_UP---status = " + status);
    			if (status == CircleMenuStatus.ROTATING || status == CircleMenuStatus.PAUSE_ROTATING) {
    				mCircleMenu.stopRotate();
    			}
    
    			break;
    
    		default:
    			break;
    		}
    	}
    
    	public void toCircleMenuScroll(CircleMenu mCircleMenu, int radius, double start, MotionEvent e2) {
    		float x = e2.getX();
    		float y = e2.getY();
    		// 一个连续的角度差值,用于判断滑动方向,得出顺时针、逆时针
    		float directionAngle = 0;
    		double end = getAngle(x, y, radius);
    		// 如果是一、四象限,则直接end-start,角度值都是正值
    		if (getQuadrant(x, y, radius) == 1 || getQuadrant(x, y, radius) == 4) {
    			mStartAngle += end - start;
    			directionAngle += end - start;
    		} else {
    			// 二、三象限,角度值是负值
    			mStartAngle += start - end;
    			directionAngle += start - end;
    		}
    
    		if (directionAngle > 0) {//得到旋转方向---顺时针,用于Fling飞转
    			mCircleMenu.setmDirection(RotateDirection.CLOCKWISE);
    		} else {
    			mCircleMenu.setmDirection(RotateDirection.ANTICLOCKWISE);
    		}
    		// 旋转角度, 请求重新布局
    		mCircleMenu.relayoutMenu(mStartAngle);
    		if (startX != x || startY != y) {
    			mCircleMenu.onRotating(directionAngle);
    		} else {
    			mCircleMenu.onPauseRotate();
    		}
    	}
    
    	public double getAngle(float xTouch, float yTouch, int radius) {
    		double x = xTouch - radius;
    		double y = yTouch - radius;
    		return (double) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
    	}
    
    	public int getQuadrant(float x, float y, int radius) {
    		int tmpX = (int) (x - radius);
    		int tmpY = (int) (y - radius);
    		if (tmpX >= 0) {
    			return tmpY >= 0 ? 4 : 1;
    		} else {
    			return tmpY >= 0 ? 3 : 2;
    		}
    
    	}
    
    }
    

    之所以代码这么多,是因为要处理诸多逻辑和诸多状态,主要思路就是得到x、y差值,计算角度差值,根据象限给加减角度,看ACTION_MOVE这段即可当然里面还牵扯到后面3个方法也差不多看完了。


    5.圆盘飞转
    飞转显然就是Fling,肯定是要在onFling()方法里面实现的,那么要实现OnGestureListener接口,同时,重写dispatchTouchEvent(MotionEvent event)方法,代码如下:

    	@Override
    	public boolean onTouchEvent(MotionEvent event) {
    		boolean result = mDetector.onTouchEvent(event);
    		if (event.getAction() == MotionEvent.ACTION_UP) {
    			if (mStatus != CircleMenuStatus.START_FLING && mStatus != CircleMenuStatus.FLING) {
    				idle();
    			}
    		}
    		return result;
    	}

    再来看onFling方法

    	@Override
    	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    		startFling(e1, e2, velocityX, velocityY);
    		return false;
    	}
    
    
    	/**
    	 * 开始飞转
    	 * @param e1
    	 * @param e2
    	 * @param velocityX
    	 * @param velocityY
    	 */
    	public void startFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    		if (mStatus == CircleMenuStatus.STOP_ROTATING) {
    			startMenuFling();
    			float velocity = Math.abs(velocityX) > Math.abs(velocityY) ? velocityX : velocityY;
    			double start = mRotate.getAngle(e2.getX(), e2.getY(), mRadius);
    			if (mFlingEngine == null) {
    				mFlingEngine = new FlingEngine();
    			}
    			mFlingEngine.start(this, velocity, start);
    		}
    	}

    也很简单,都提出来的,看FlingEngine.java。

    package com.pan.tanglang.circlemenu.control;
    
    import com.pan.tanglang.circlemenu.model.CircleMenuStatus;
    import com.pan.tanglang.circlemenu.view.CircleMenu;
    import com.pan.tanglang.circlemenu.view.CircleMenu.RotateDirection;
    
    
    /**
     * @description 描    述:飞转引擎
     */
    public class FlingEngine implements Runnable {
    
    	private static final String TAG = "FlingEngine";
    	private int mVelocity;
    	private int DELAY = 10;
    	private double startAngle;
    	private CircleMenu mCircleMenu;
    
    	public void start(CircleMenu circleMenu, float velocity, double start) {
    		mCircleMenu = circleMenu;
    		mVelocity = (int) Math.abs(velocity);
    		startAngle = start;
    		if (circleMenu.getStatus() == CircleMenuStatus.START_FLING) {
    			circleMenu.post(this);
    		}
    	}
    
    	@Override
    	public void run() {
    		// 如果小于20,则停止
    		if (mCircleMenu.getStatus() == CircleMenuStatus.STOP_FLING || mVelocity <= 0) {
    			//叫停或自动停止
    			mCircleMenu.idle();
    			return;
    		}
    		double preStartAngle = startAngle;
    		// 顺时针
    		if (mCircleMenu.getmDirection() == RotateDirection.CLOCKWISE) {
    
    			flingSlowDownByClockwise();
    		} else if (mCircleMenu.getmDirection() == RotateDirection.ANTICLOCKWISE) {
    			flingSlowDownByAnticlockwise();
    		}
    		mCircleMenu.relayoutMenu(startAngle);
    		mCircleMenu.onMenuFling(startAngle - preStartAngle);
    		mCircleMenu.postDelayed(this, DELAY);
    	}
    
    	// 逆时针减速
    	private void flingSlowDownByAnticlockwise() {
    
    		if (mVelocity > 10000) {
    			mVelocity -= 1000;
    			startAngle -= 10;
    		} else if (mVelocity > 5000) {
    			mVelocity -= 100;
    			startAngle -= 8;
    		} else if (mVelocity > 1000) {
    			mVelocity -= 50;
    			startAngle -= 6;
    		} else if (mVelocity > 500) {
    			mVelocity -= 10;
    			startAngle -= 4;
    		} else if (mVelocity > 100) {
    			mVelocity -= 5;
    			startAngle -= 2;
    		} else {
    			mVelocity--;
    			startAngle--;
    		}
    	}
    
    	// 顺时针减速
    	private void flingSlowDownByClockwise() {
    
    		if (mVelocity > 10000) {
    			mVelocity -= 1000;
    			startAngle += 10;
    		} else if (mVelocity > 5000) {
    			mVelocity -= 100;
    			startAngle += 8;
    		} else if (mVelocity > 1000) {
    			mVelocity -= 50;
    			startAngle += 6;
    		} else if (mVelocity > 500) {
    			mVelocity -= 10;
    			startAngle += 4;
    		} else if (mVelocity > 100) {
    			mVelocity -= 5;
    			startAngle += 2;
    		} else {
    			mVelocity--;
    			startAngle++;
    		}
    	}
    
    	public int getQuadrant(float angle, int radius) {
    		float x = Math.round(radius * Math.cos(Math.toRadians(angle)));
    		float y = Math.round(radius * Math.sin(Math.toRadians(angle)));
    		int tmpX = (int) (x - radius / 2);
    		int tmpY = (int) (y - radius / 2);
    		if (tmpX >= 0) {
    			return tmpY >= 0 ? 4 : 1;
    		} else {
    			return tmpY >= 0 ? 3 : 2;
    		}
    
    	}
    
    }
    

    之所以要提出startFling方法是因为Item也要用到,通过getParent强转之后调用,所以,CircleItemView.java的代码就相当简单了,如下:


    package com.pan.tanglang.circlemenu.view;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.GestureDetector;
    import android.view.View;
    import android.view.GestureDetector.OnGestureListener;
    import android.view.MotionEvent;
    import android.widget.LinearLayout;
    
    import com.pan.tanglang.circlemenu.model.CircleMenuStatus;
    import com.pan.tanglang.circlemenu.model.UserEvent;
    
    /**
     * @description 描 述:Item项自定义控件,主要是为了实现Item的onFling
     */
    public class CircleItemView extends LinearLayout implements OnGestureListener {
    
    	public static final String TAG = "CircleItemView";
    	private GestureDetector mDetector;
    	private CircleMenu mParent;
    
    	public CircleItemView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		setClickable(true);
    		mDetector = new GestureDetector(context, this);
    
    	}
    
    	@SuppressLint("ClickableViewAccessibility")
    	@Override
    	public boolean onTouchEvent(MotionEvent event) {
    		mDetector.onTouchEvent(event);
    		//将滑动状态从 STOP_ROTATING 置为 IDLE,多一个STOP_ROTATING,是ROTATING之后,TART_FLING之前的一个状态,目前意义不大,主要看用户怎么利用
    		if (event.getAction() == MotionEvent.ACTION_UP) {
    			CircleMenuStatus status = mParent.getStatus();
    			if (status != CircleMenuStatus.IDLE &&status != CircleMenuStatus.START_FLING && status != CircleMenuStatus.FLING) {
    				mParent.idle();
    			}
    		}
    		return super.onTouchEvent(event);// 防止点击事件丢失
    	}
    
    	@Override
    	public boolean onDown(MotionEvent e) {
    		if (mParent == null) {
    			mParent = (CircleMenu) getParent();
    		}
    		if (mParent != null) {
    			CircleMenuStatus status = mParent.getStatus();
    			if (status == CircleMenuStatus.FLING) {
    				mParent.stopFling();
    			}
    			if (status == CircleMenuStatus.ROTATING) {
    				mParent.stopRotate();
    			}
    		}
    		return true;
    	}
    
    	@Override
    	public void onShowPress(MotionEvent e) {
    	}
    
    	@Override
    	public boolean onSingleTapUp(MotionEvent e) {
    		return false;
    	}
    
    	@Override
    	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    		return false;
    	}
    
    	@Override
    	public void onLongPress(MotionEvent e) {
    	}
    
    	@Override
    	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    		if (mParent != null) {
    			mParent.startFling(e1, e2, velocityX, velocityY);
    		}
    		return false;
    	}
    	
    }
    

    CircleMenu.java的代码就不贴出来了,上https://github.com/aknew123/CircleMenu上downloade吧,里面有状态控制,其实代码也不到400行,只是贴进来显得文长(想想还是贴吧)。

    package com.pan.tanglang.circlemenu.view;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.GestureDetector;
    import android.view.GestureDetector.OnGestureListener;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ListAdapter;
    
    import com.pan.tanglang.circlemenu.control.FlingEngine;
    import com.pan.tanglang.circlemenu.control.RotateEngine;
    import com.pan.tanglang.circlemenu.model.CircleMenuStatus;
    import com.pan.tanglang.circlemenu.model.UserEvent;
    
    /**
     * @description 描 述:圆盘菜单容器,圆盘菜单功能的核心文件
     * @author 作 者:SergioPan
     */
    public class CircleMenu extends ViewGroup implements OnGestureListener {
    
    	public static final String TAG = "CircleMenu";
    	/** 圆盘半径,那么圆心为(mRadius, mRadius) **/
    	public int mRadius;
    	private static final float RADIO_DEFAULT_CHILD_DIMENSION = 1 / 2f;
    	private static final float RADIO_PADDING_LAYOUT = 1 / 20f;
    	/** 内边距,默认为mRadius/20 **/
    	public float mPadding = -1;
    	private double mStartAngle = 0;
    	private OnMenuItemClickListener mListener;
    	private ListAdapter mAdapter;
    	private CircleMenuStatus mStatus = CircleMenuStatus.IDLE;
    	private OnMenuStatusChangedListener mStatusListener;
    	private GestureDetector mDetector;
    	private RotateEngine mRotate;
    	private RotateDirection mDirection = RotateDirection.CLOCKWISE;
    	private FlingEngine mFlingEngine;
    	// 用户输入事件,默认为无用行为,主要用于解决Fling后的按停,此时屏蔽点击事件
    	private UserEvent mUserEvent = UserEvent.USELESS_ACTION;
    
    	public CircleMenu(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		setPadding(0, 0, 0, 0);
    		setClickable(true);
    		mDetector = new GestureDetector(context, this);
    		mRotate = RotateEngine.getInstance();
    
    	}
    
    	/** 转动方向 **/
    	public enum RotateDirection {
    		/** 顺时针 **/
    		CLOCKWISE,
    		/** 逆时针 **/
    		ANTICLOCKWISE;
    	}
    
    	//依附到窗口上
    	@Override
    	protected void onAttachedToWindow() {
    		if (mAdapter != null) {
    			buildMenuItems();
    		}
    		super.onAttachedToWindow();
    	}
    
    	/**
    	 * 菜单重新布局
    	 * 
    	 * @param startAngle
    	 */
    	public void relayoutMenu(double startAngle) {
    		mStartAngle = startAngle;
    		requestLayout();
    	}
    
    	// 构建菜单项
    	@SuppressLint("NewApi")
    	private void buildMenuItems() {
    		if (mAdapter.getCount() <= 0) {
    			return;
    		}
    		for (int i = 0; i < mAdapter.getCount(); i++) {
    
    			CircleItemView itemView = (CircleItemView) mAdapter.getView(i, null, this);
    			final int position = i;
    			itemView.setClickable(true);
    			itemView.setOnClickListener(new OnClickListener() {
    
    				@Override
    				public void onClick(View v) {
    					if (mUserEvent == UserEvent.FLING) {
    						// 正在飞转时,接收用户的点击事件,则视为停止飞转动作,而屏蔽点击事件
    						mUserEvent = UserEvent.USELESS_ACTION;
    						return;
    					}
    					// 非飞转时响应点击事件
    					if (mListener != null) {
    						mListener.onClick(v, position);
    					}
    
    				}
    			});
    			addView(itemView);
    		}
    	}
    
    	@Override
    	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    		// 测量自身
    		// measureMyself(widthMeasureSpec, heightMeasureSpec);
    		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    		// 测量子View
    		measureChildViews();
    
    	}
    
    	private void measureChildViews() {
    		if (mAdapter.getCount() <= 0) {
    			return;
    		}
    
    		// 获取半径,
    		mRadius = Math.max(getMeasuredWidth(), getMeasuredHeight()) / 2;
    		final int count = getChildCount();
    		// 取mRadius/2为Item宽度
    		int childSize = (int) (mRadius * RADIO_DEFAULT_CHILD_DIMENSION);
    		int childMode = MeasureSpec.EXACTLY;
    		int makeMeasureSpec = -1;
    		for (int i = 0; i < count; i++) {
    			final View child = getChildAt(i);
    			if (child.getVisibility() == View.GONE) {
    				continue;
    			}
    			makeMeasureSpec = MeasureSpec.makeMeasureSpec(childSize, childMode);
    			// 设置为正方形
    			child.measure(makeMeasureSpec, makeMeasureSpec);
    		}
    		// 取mRadius/10为默认内边距
    		if (mPadding == -1) {
    			mPadding = RADIO_PADDING_LAYOUT * mRadius;
    		}
    	}
    
    	@Override
    	protected void onLayout(boolean changed, int l, int t, int r, int b) {
    		if (mAdapter.getCount() <= 0) {
    			return;
    		}
    		final int childCount = getChildCount();
    		int left, top, halfDiagonal;
    		// 限制Item的宽高
    		int itemWidth = (int) (mRadius * RADIO_DEFAULT_CHILD_DIMENSION);
    		float angleDelay = 360 / childCount;
    		for (int i = 0; i < childCount; i++) {
    			final View child = getChildAt(i);
    			if (child.getVisibility() == View.GONE) {
    				continue;
    			}
    			mStartAngle %= 360;
    			// 取Item对角线的一半为Item中心到圆盘圆周的距离
    			halfDiagonal = (int) (itemWidth / Math.sqrt(2));
    			float distanceFromCenter = mRadius - halfDiagonal - mPadding;
    			left = mRadius
    					+ (int) Math.round(distanceFromCenter * Math.cos(Math.toRadians(mStartAngle)) - 1 / 2f * itemWidth);
    			top = mRadius
    					+ (int) Math.round(distanceFromCenter * Math.sin(Math.toRadians(mStartAngle)) - 1 / 2f * itemWidth);
    			// 重新Layout
    			child.layout(left, top, left + itemWidth, top + itemWidth);
    			mStartAngle += angleDelay;
    		}
    		
    	}
    
    	@SuppressLint("ClickableViewAccessibility")
    	@Override
    	public boolean onTouchEvent(MotionEvent event) {
    		boolean result = mDetector.onTouchEvent(event);
    		if (event.getAction() == MotionEvent.ACTION_UP) {
    			if (mStatus != CircleMenuStatus.START_FLING && mStatus != CircleMenuStatus.FLING) {
    				idle();
    			}
    		}
    		return result;
    	}
    
    	// 因为父view的dispatchTouchEvent先获得事件,所以在这里可以连带子view的滚动事件一起处理
    	@Override
    	public boolean dispatchTouchEvent(MotionEvent event) {
    		if (mRotate != null) {
    			mRotate.onCircleMenuTouch(event, mRadius, this);
    		}
    		return super.dispatchTouchEvent(event);
    
    	}
    
    	public void startMenuFling() {
    		mStatus = CircleMenuStatus.START_FLING;
    		if (mStatusListener != null) {
    			mStatusListener.onStatusChanged(mStatus, 0);
    		}
    	}
    
    	public void stopFling() {
    		mStatus = CircleMenuStatus.STOP_FLING;
    		if (mStatusListener != null) {
    			mStatusListener.onStatusChanged(mStatus, 0);
    		}
    	}
    
    	public void onMenuFling(double angle) {
    		mStatus = CircleMenuStatus.FLING;
    		if (mStatusListener != null) {
    			mStatusListener.onStatusChanged(mStatus, angle);
    			mUserEvent = UserEvent.FLING;
    		}
    	}
    
    	public void startRotate() {
    		mStatus = CircleMenuStatus.START_ROTATING;
    		if (mStatusListener != null) {
    			mStatusListener.onStatusChanged(mStatus, 0);
    		}
    	}
    
    	public void onRotating(float angle) {
    		mStatus = CircleMenuStatus.ROTATING;
    		if (mStatusListener != null) {
    			mStatusListener.onStatusChanged(mStatus, angle);
    		}
    	}
    
    	public void onPauseRotate() {
    		mStatus = CircleMenuStatus.PAUSE_ROTATING;
    		if (mStatusListener != null) {
    			mStatusListener.onStatusChanged(mStatus, 0);
    		}
    	}
    
    	public void stopRotate() {
    		mStatus = CircleMenuStatus.STOP_ROTATING;
    		if (mStatusListener != null) {
    			mStatusListener.onStatusChanged(mStatus, 0);
    		}
    	}
    
    	public void idle() {
    		mStatus = CircleMenuStatus.IDLE;
    		if (mStatusListener != null) {
    			mStatusListener.onStatusChanged(mStatus, 0);
    		}
    	}
    
    	@Override
    	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    		return true;
    	}
    
    	@Override
    	public boolean onDown(MotionEvent e) {
    		if (mStatus == CircleMenuStatus.FLING) {
    			stopFling();
    		}
    		return true;
    	}
    
    	@Override
    	public void onShowPress(MotionEvent e) {
    	}
    
    	@Override
    	public boolean onSingleTapUp(MotionEvent e) {
    		return true;
    	}
    
    	@Override
    	public void onLongPress(MotionEvent e) {
    	}
    
    	@Override
    	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    		startFling(e1, e2, velocityX, velocityY);
    		return false;
    	}
    
    	/**
    	 * 开始飞转
    	 * @param e1
    	 * @param e2
    	 * @param velocityX
    	 * @param velocityY
    	 */
    	public void startFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    		if (mStatus == CircleMenuStatus.STOP_ROTATING) {
    			startMenuFling();
    			float velocity = Math.abs(velocityX) > Math.abs(velocityY) ? velocityX : velocityY;
    			double start = mRotate.getAngle(e2.getX(), e2.getY(), mRadius);
    			if (mFlingEngine == null) {
    				mFlingEngine = new FlingEngine();
    			}
    			mFlingEngine.start(this, velocity, start);
    		}
    	}
    
    	public RotateDirection getmDirection() {
    		return mDirection;
    	}
    
    	public void setmDirection(RotateDirection mDirection) {
    		this.mDirection = mDirection;
    	}
    
    	public void setStatus(CircleMenuStatus status) {
    		mStatus = status;
    	}
    
    	public CircleMenuStatus getStatus() {
    		return mStatus;
    	}
    
    	public void setAdapter(ListAdapter adapter) {
    		mAdapter = adapter;
    	}
    
    	public ListAdapter getAdapter() {
    		return mAdapter;
    	}
    
    	public void setOnItemClickListener(OnMenuItemClickListener listener) {
    		this.mListener = listener;
    	}
    
    	public void setOnStatusChangedListener(OnMenuStatusChangedListener statusListener) {
    		this.mStatusListener = statusListener;
    	}
    
    	public UserEvent getmUserEvent() {
    		return mUserEvent;
    	}
    
    	public void setmUserEvent(UserEvent mUserEvent) {
    		this.mUserEvent = mUserEvent;
    	}
    
    	public double getmStartAngle() {
    		return mStartAngle;
    	}
    
    	public float getmPadding() {
    		return mPadding;
    	}
    
    	public void setmPadding(float mPadding) {
    		this.mPadding = mPadding;
    	}
    
    	/**
    	 * 圆盘菜单状态改变监听器,用于定制外围或圆心动画
    	 * 
    	 * @author SergioPan
    	 */
    	public interface OnMenuStatusChangedListener {
    
    		/**
    		 * @param status
    		 *            状态
    		 * @param rotateAngle
    		 *            旋转量(角度)
    		 */
    		public void onStatusChanged(CircleMenuStatus status, double rotateAngle);
    
    	}
    
    	/**
    	 * Item点击事件监听器
    	 * 
    	 * @author SergioPan
    	 */
    	public interface OnMenuItemClickListener {
    
    		public void onClick(View view, int position);
    	}
    
    }
    



    下面说说适配器模式,按开发的角度,应该一开始就该说了,要不怎么调试啊,好!马上来!


    6.适配器模式

    想要定制化就要用适配器模式,这样才人性化,要不只能写一些只能用一次的逻辑代码而已。适配器模式也是一个小的MVC模式,

    Modle ----- DefaultItem

    View ---- CircleItemView

    Controller ---- Adapter

    用起来,其实挺简单,当然这是简单的MVC,父容器CircleMenu要实现2个方法,如下:

    	public void setAdapter(ListAdapter adapter) {
    		mAdapter = adapter;
    	}
    
    
    	//依附到窗口上
    	@Override
    	protected void onAttachedToWindow() {
    		if (mAdapter != null) {
    			buildMenuItems();
    		}
    		super.onAttachedToWindow();
    	}

    	// 构建菜单项
    	@SuppressLint("NewApi")
    	private void buildMenuItems() {
    		if (mAdapter.getCount() <= 0) {
    			return;
    		}
    		for (int i = 0; i < mAdapter.getCount(); i++) {
    
    			CircleItemView itemView = (CircleItemView) mAdapter.getView(i, null, this);
    			final int position = i;
    			itemView.setClickable(true);
    			itemView.setOnClickListener(new OnClickListener() {
    
    				@Override
    				public void onClick(View v) {
    					if (mUserEvent == UserEvent.FLING) {
    						// 正在飞转时,接收用户的点击事件,则视为停止飞转动作,而屏蔽点击事件
    						mUserEvent = UserEvent.USELESS_ACTION;
    						return;
    					}
    					// 非飞转时响应点击事件
    					if (mListener != null) {
    						mListener.onClick(v, position);
    					}
    
    				}
    			});
    			addView(itemView);
    		}
    	}

    其实就是装饰(装饰模式)了一下Adapter,然后调用Adapter的getView方法,即

    mAdapter.getView(i, null, this);

    然后,Adapter就更简单了,跟ListView一样,继承BaseAdapter,再重写这4个方法,DefaultMenuAdapter.java代码如下

    package com.pan.tanglang.circlemenu.adapter;
    
    import java.util.List;
    
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.BaseAdapter;
    import android.widget.ImageView;
    import android.widget.TextView;
    
    import com.pan.tanglang.circlemenu.R;
    import com.pan.tanglang.circlemenu.model.DefaultItem;
    
    
    /**
     * @description 描    述:圆盘菜单默认适配器,如有其他需求,用户自行定义适配器,继承BaseAdapter就可以,自行编写xml文件
     */
    public class DefaultMenuAdapter extends BaseAdapter {
    
    	private List<DefaultItem> items;
    
    	public DefaultMenuAdapter(List<DefaultItem> data) {
    		items = data;
    	}
    
    	@Override
    	public int getCount() {
    		if (items == null) {
    			return 0;
    		}
    		return items.size();
    	}
    
    	@Override
    	public Object getItem(int position) {
    
    		return items.get(position);
    	}
    
    	@Override
    	public long getItemId(int position) {
    		return position;
    	}
    
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		ViewHolder holder = null;
    		if (convertView == null) {
    			convertView = View.inflate(parent.getContext(), R.layout.item_default, null);
    			holder = new ViewHolder();
    			holder.iv = (ImageView) convertView.findViewById(R.id.iv_default_circle_menu_item);
    			holder.tv = (TextView) convertView.findViewById(R.id.tv_default_circle_menu_item);
    			convertView.setTag(holder);
    		}
    		holder = (ViewHolder) convertView.getTag();
    		DefaultItem item = items.get(position);
    		if (item != null) {
    			holder.iv.setImageResource(item.getImgId());
    			holder.tv.setText(item.getText());
    		}
    		return convertView;
    	}
    
    	class ViewHolder {
    		ImageView iv;
    		TextView tv;
    	}
    
    }
    

    这样就做到,高度定制化。

    7.状态模式定制动画

    可根据圆盘状态,定制与之相匹配的动画,想要多炫有多炫,只要监听setOnStatusChangedListener方法即可实现。

    public enum CircleMenuStatus {
    	/** 静止 **/
    	IDLE,
    	/** 开始旋转 **/
    	START_ROTATING,
    	/** 正在旋转 **/
    	ROTATING,
    	/** 暂停旋转 **/
    	PAUSE_ROTATING,
    	/** 叫停旋转,是ROTATING之后,START_FLING之前的一个状态,本项目中意义不大,看用户怎么利用 **/
    	STOP_ROTATING,
    	/** 开始飞转 **/
    	START_FLING,
    	/**正在飞转 **/
    	FLING,
    	/** 叫停飞转 **/
    	STOP_FLING;
    }


    		mCircleMenu.setOnStatusChangedListener(new OnMenuStatusChangedListener() {
    
    			@Override
    			public void onStatusChanged(CircleMenuStatus status, double rotateAngle) {
    				// TODO 可在此处定制各种动画
    				odAnimation(status, (float)rotateAngle);
    			}
    
    		});
    
    	private void odAnimation(CircleMenuStatus status, float rotateAngle) {
    
    		switch (status) {
    		case IDLE:
    			Log.i(TAG, "--- -IDLE-----");
    			animRotate.cancel();
    			animRotate.cancel();
    			break;
    		case START_ROTATING:
    			Log.i(TAG, "--- -START_ROTATING-----");
    			break;
    		case ROTATING:
    			animRotate = ObjectAnimator.ofFloat(ivCenter, "rotation", startRotate, startRotate + rotateAngle);
    			animRotate.setDuration(200).start();
    			startRotate += rotateAngle;
    			// Log.i(TAG, "--- -ROTATING-----");
    			break;
    		case STOP_ROTATING:
    			Log.i(TAG, "--- -STOP_ROTATING-----");
    			break;
    		case START_FLING:
    			Log.i(TAG, "--- -START_FLING-----");
    			break;
    
    		case FLING:
    			// Log.i(TAG, "--- -FLING-----");
    			animFling = ObjectAnimator.ofFloat(ivCenter, "rotation", startFling, startFling + rotateAngle);
    			animFling.setDuration(200).start();
    			startFling += rotateAngle;
    			break;
    		case STOP_FLING:
    			Log.i(TAG, "--- -STOP_FLING-----");
    
    			break;
    
    		default:
    			break;
    		}
    
    	}
    

    是不是很炫啊大笑

    完!



















    展开全文
  • Android 高仿【优酷】圆盘旋转菜单 的实现

    千次下载 热门讨论 2012-08-22 15:54:19
    Android 高仿【优酷】圆盘旋转菜单 的实现
  • Android 高仿优酷圆盘旋转菜单【源代码】
  • 最近学习的时候,看见一份资料上教怎么写自定义控件,上面的示例用的是优酷早期版本的客户端,该客户端的菜单就是一个自定义的组件(现在的版本就不清楚有没有了,没下载过了),好吧,废话不多说,先上优酷的原型图...

    最近学习的时候,看见一份资料上教怎么写自定义控件,上面的示例用的是优酷早期版本的客户端,该客户端的菜单就是一个自定义的组件(现在的版本就不清楚有没有了,没下载过了),好吧,废话不多说,先上优酷的原型图。


    这个自定义组件感官上看是,里外三层设计,每一层上有布置不同的菜单按钮,每一层又设置了进入和退出的动画,来增强用户的体验效果。这种设计非常好,简洁美观,以下是我仿照优酷菜单自定义的一个组件,写的马马虎虎,仅作为参考。

    1,首先,里外三层看似错乱,其实无外乎就是UI布局,并不高深。以下是布局文件:

    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:layout_width="match_parent"  
    4.     android:layout_height="match_parent"  
    5.     tools:context=".MainActivity" >  
    6.   
    7.     <RelativeLayout  
    8.         android:id="@+id/rl_level1"  
    9.         android:layout_width="100dip"  
    10.         android:layout_height="50dip"  
    11.         android:layout_alignParentBottom="true"  
    12.         android:layout_centerHorizontal="true"  
    13.         android:background="@drawable/level1" >  
    14.   
    15.         <ImageButton  
    16.             android:id="@+id/ib_home"  
    17.             android:layout_width="wrap_content"  
    18.             android:layout_height="wrap_content"  
    19.             android:layout_centerInParent="true"  
    20.             android:background="@drawable/icon_home" />  
    21.     </RelativeLayout>  
    22.   
    23.     <RelativeLayout  
    24.         android:id="@+id/rl_level2"  
    25.         android:layout_width="200dip"  
    26.         android:layout_height="100dip"  
    27.         android:layout_alignParentBottom="true"  
    28.         android:layout_centerHorizontal="true"  
    29.         android:background="@drawable/level2" >  
    30.   
    31.         <ImageButton  
    32.             android:layout_width="wrap_content"  
    33.             android:layout_height="wrap_content"  
    34.             android:layout_alignParentBottom="true"  
    35.             android:layout_marginBottom="5dip"  
    36.             android:layout_marginLeft="10dip"  
    37.             android:background="@drawable/icon_search" />  
    38.   
    39.         <ImageButton  
    40.             android:id="@+id/ib_menu"  
    41.             android:layout_width="wrap_content"  
    42.             android:layout_height="wrap_content"  
    43.             android:layout_centerHorizontal="true"  
    44.             android:layout_marginTop="10dip"  
    45.             android:background="@drawable/icon_menu" />  
    46.   
    47.         <ImageButton  
    48.             android:layout_width="wrap_content"  
    49.             android:layout_height="wrap_content"  
    50.             android:layout_alignParentBottom="true"  
    51.             android:layout_alignParentRight="true"  
    52.             android:layout_marginBottom="5dip"  
    53.             android:layout_marginRight="10dip"  
    54.             android:background="@drawable/icon_myyouku" />  
    55.     </RelativeLayout>  
    56.   
    57.     <RelativeLayout  
    58.         android:id="@+id/rl_level3"  
    59.         android:layout_width="320dip"  
    60.         android:layout_height="160dip"  
    61.         android:layout_alignParentBottom="true"  
    62.         android:layout_centerHorizontal="true"  
    63.         android:background="@drawable/level3" >  
    64.   
    65.         <ImageButton  
    66.             android:id="@+id/ib_channel1"  
    67.             android:layout_width="wrap_content"  
    68.             android:layout_height="wrap_content"  
    69.             android:layout_alignParentBottom="true"  
    70.             android:layout_marginBottom="10dip"  
    71.             android:layout_marginLeft="15dip"  
    72.             android:background="@drawable/channel1" />  
    73.   
    74.         <ImageButton  
    75.             android:id="@+id/ib_channel2"  
    76.             android:layout_width="wrap_content"  
    77.             android:layout_height="wrap_content"  
    78.             android:layout_above="@id/ib_channel1"  
    79.             android:layout_marginBottom="20dip"  
    80.             android:layout_marginLeft="40dip"  
    81.             android:background="@drawable/channel2" />  
    82.   
    83.         <ImageButton  
    84.             android:layout_width="wrap_content"  
    85.             android:layout_height="wrap_content"  
    86.             android:layout_above="@id/ib_channel2"  
    87.             android:layout_marginBottom="15dip"  
    88.             android:layout_marginLeft="10dip"  
    89.             android:layout_toRightOf="@id/ib_channel2"  
    90.             android:background="@drawable/channel3" />  
    91.   
    92.         <ImageButton  
    93.             android:layout_width="wrap_content"  
    94.             android:layout_height="wrap_content"  
    95.             android:layout_centerHorizontal="true"  
    96.             android:layout_marginTop="10dip"  
    97.             android:background="@drawable/channel4" />  
    98.   
    99.         <ImageButton  
    100.             android:id="@+id/ib_channel7"  
    101.             android:layout_width="wrap_content"  
    102.             android:layout_height="wrap_content"  
    103.             android:layout_alignParentBottom="true"  
    104.             android:layout_alignParentRight="true"  
    105.             android:layout_marginBottom="10dip"  
    106.             android:layout_marginRight="15dip"  
    107.             android:background="@drawable/channel7" />  
    108.   
    109.         <ImageButton  
    110.             android:id="@+id/ib_channel6"  
    111.             android:layout_width="wrap_content"  
    112.             android:layout_height="wrap_content"  
    113.             android:layout_above="@id/ib_channel7"  
    114.             android:layout_alignParentRight="true"  
    115.             android:layout_marginBottom="20dip"  
    116.             android:layout_marginRight="40dip"  
    117.             android:background="@drawable/channel6" />  
    118.   
    119.         <ImageButton  
    120.             android:layout_width="wrap_content"  
    121.             android:layout_height="wrap_content"  
    122.             android:layout_above="@id/ib_channel6"  
    123.             android:layout_marginBottom="15dip"  
    124.             android:layout_marginRight="10dip"  
    125.             android:layout_toLeftOf="@id/ib_channel6"  
    126.             android:background="@drawable/channel5" />  
    127.     </RelativeLayout>  
    128.   
    129. </RelativeLayout>  

    布局后的UI效果见图:


    如上图所示的样子,布局完成了,接下来就是添加一些动态的效果了,效果描述是这样的:该菜单由内而外分别叫做“1级菜单”,“2级菜单”和“3级菜单”,1级菜单和2级菜单的中心位置的ImageButton用来控制整个菜单的动态效果。点击1级菜单时,若2级或者3级菜单处于显示状态,则隐藏2级和3级菜单,如果没有显示,则只显示出2级菜单。点击2级菜单的时候,只控制3级菜单的显示和隐藏。

    这里所有的动态效果都是由自定义的旋转动画来实现,下面我们先完成这个自定义的旋转动画:

    1. package com.example.youkumenu;  
    2.   
    3. import android.view.animation.Animation;  
    4. import android.view.animation.RotateAnimation;  
    5. import android.widget.RelativeLayout;  
    6.   
    7. public class AnimationUtils {  
    8.   
    9.     public static boolean isRunningAnimation = false// 记录动画是否在执行  
    10.   
    11.     /** 
    12.      * 旋转出去的动画 
    13.      *  
    14.      * @param layout 
    15.      *            执行动画的对象 
    16.      * @param startOffset 
    17.      *            延迟时间 
    18.      */  
    19.     public static void outRotateAnimation(RelativeLayout layout, long startOffset) {  
    20.         // 防止父控件中的子控件抢焦点能力强,而将子控件设置为不可用  
    21.         for (int i = 0; i < layout.getChildCount(); i++) {  
    22.             layout.getChildAt(i).setEnabled(false);  
    23.         }  
    24.   
    25.         RotateAnimation ra = new RotateAnimation( //  
    26.                 0.0f, // 旋转开始的角度  
    27.                 -180.0f, // 旋转结束的角度  
    28.                 RotateAnimation.RELATIVE_TO_SELF, // 旋转坐标X轴的参照物  
    29.                 0.5f, // 相对于参照物X轴的百分比  
    30.                 RotateAnimation.RELATIVE_TO_SELF, // 旋转坐标Y轴的参照物  
    31.                 1.0f // 相对于参照物Y轴的百分比  
    32.         );  
    33.         ra.setDuration(500);  
    34.         ra.setStartOffset(startOffset); // 动画延迟时间  
    35.         ra.setFillAfter(true);  
    36.         ra.setAnimationListener(new MyAnimationListener());  
    37.         layout.startAnimation(ra);  
    38.     }  
    39.   
    40.     /** 
    41.      * 旋转进来的动画 
    42.      *  
    43.      * @param layout 
    44.      *            执行动画的对象 
    45.      */  
    46.     public static void inRotateAnimation(RelativeLayout layout) {  
    47.         // 进来的时候,将所有的子控件设置为可用  
    48.         for (int i = 0; i < layout.getChildCount(); i++) {  
    49.             layout.getChildAt(i).setEnabled(false);  
    50.         }  
    51.   
    52.         RotateAnimation ra = new RotateAnimation( //  
    53.                 -180.0f, // 旋转开始的角度  
    54.                 0.0f, // 旋转结束的角度  
    55.                 RotateAnimation.RELATIVE_TO_SELF, // 旋转坐标X轴的参照物  
    56.                 0.5f, // 相对于参照物X轴的百分比  
    57.                 RotateAnimation.RELATIVE_TO_SELF, // 旋转坐标Y轴的参照物  
    58.                 1.0f // 相对于参照物Y轴的百分比  
    59.         );  
    60.         ra.setDuration(500);  
    61.         ra.setFillAfter(true);  
    62.         layout.startAnimation(ra);  
    63.     }  
    64.   
    65.     static class MyAnimationListener implements Animation.AnimationListener {  
    66.   
    67.         /** 
    68.          * 动画开始的时候执行 
    69.          */  
    70.         @Override  
    71.         public void onAnimationStart(Animation animation) {  
    72.             // TODO Auto-generated method stub  
    73.             isRunningAnimation = true;  
    74.         }  
    75.   
    76.         /** 
    77.          * 动画结束的时候执行 
    78.          */  
    79.         @Override  
    80.         public void onAnimationEnd(Animation animation) {  
    81.             // TODO Auto-generated method stub  
    82.             isRunningAnimation = false;  
    83.         }  
    84.   
    85.         /** 
    86.          * 动画重复执行的时候 
    87.          */  
    88.         @Override  
    89.         public void onAnimationRepeat(Animation animation) {  
    90.             // TODO Auto-generated method stub  
    91.   
    92.         }  
    93.   
    94.     }  
    95.   
    96. }  


    下面是MainActivity的主要代码,这里控制菜单的动态变化:

    1. package com.example.youkumenu;  
    2.   
    3. import android.os.Bundle;  
    4. import android.app.Activity;  
    5. import android.view.KeyEvent;  
    6. import android.view.View;  
    7. import android.view.View.OnClickListener;  
    8. import android.widget.RelativeLayout;  
    9.   
    10. public class MainActivity extends Activity implements OnClickListener {  
    11.   
    12.     private RelativeLayout rlLevel1;  
    13.     private RelativeLayout rlLevel2;  
    14.     private RelativeLayout rlLevel3;  
    15.     /** 记录3级菜单是否展示 */  
    16.     private boolean isDisplayLevel3 = true;  
    17.     /** 记录2级菜单是否展示 */  
    18.     private boolean isDisplayLevel2 = true;  
    19.     /** 记录1级菜单是否展示 */  
    20.     private boolean isDisplayLevel1 = true;  
    21.   
    22.     @Override  
    23.     protected void onCreate(Bundle savedInstanceState) {  
    24.         super.onCreate(savedInstanceState);  
    25.         setContentView(R.layout.activity_main);  
    26.         rlLevel1 = (RelativeLayout) findViewById(R.id.rl_level1);  
    27.         rlLevel2 = (RelativeLayout) findViewById(R.id.rl_level2);  
    28.         rlLevel3 = (RelativeLayout) findViewById(R.id.rl_level3);  
    29.   
    30.         findViewById(R.id.ib_home).setOnClickListener(this);  
    31.         findViewById(R.id.ib_menu).setOnClickListener(this);  
    32.   
    33.     }  
    34.   
    35.     @Override  
    36.     public void onClick(View v) {  
    37.         switch (v.getId()) {  
    38.         case R.id.ib_home:  
    39.             if (AnimationUtils.isRunningAnimation) // 当前动画正在执行的时候,不执行动画  
    40.                 return;  
    41.             if (isDisplayLevel2) {  
    42.                 // 2级菜单正在展示  
    43.                 long startOffset = 0// 旋转延时时间  
    44.                 if (isDisplayLevel3) {  
    45.                     // 3级菜单也在展示,先旋转出去3级菜单,再旋转出去2级菜单  
    46.                     AnimationUtils.outRotateAnimation(rlLevel3, startOffset);  
    47.                     startOffset += 200;  
    48.                     isDisplayLevel3 = !isDisplayLevel3;  
    49.                 }  
    50.                 AnimationUtils.outRotateAnimation(rlLevel2, startOffset);  
    51.             } else {  
    52.                 // 2级菜单没有展示,需要旋转进来  
    53.                 AnimationUtils.inRotateAnimation(rlLevel2);  
    54.             }  
    55.             isDisplayLevel2 = !isDisplayLevel2;  
    56.             break;  
    57.         case R.id.ib_menu:  
    58.             if (AnimationUtils.isRunningAnimation)  
    59.                 return;  
    60.             if (isDisplayLevel3) {  
    61.                 // 3级菜单正在展示,需要旋转出去  
    62.                 AnimationUtils.outRotateAnimation(rlLevel3, 0);  
    63.             } else {  
    64.                 // 3级菜单没有展示,需要旋转进来  
    65.                 AnimationUtils.inRotateAnimation(rlLevel3);  
    66.             }  
    67.             isDisplayLevel3 = !isDisplayLevel3;  
    68.             break;  
    69.         default:  
    70.             break;  
    71.         }  
    72.     }  
    73.   
    74.     /** 
    75.      * 菜单按钮的处理 
    76.      */  
    77.     @Override  
    78.     public boolean onKeyDown(int keyCode, KeyEvent event) {  
    79.         // TODO Auto-generated method stub  
    80.         if (keyCode == KeyEvent.KEYCODE_MENU) {  
    81.             if (AnimationUtils.isRunningAnimation)  
    82.                 return super.onKeyDown(keyCode, event);  
    83.             if (isDisplayLevel1) {  
    84.                 // 1级菜单旋转出去  
    85.                 long startOffset = 0// 记录延迟时间  
    86.                 if (isDisplayLevel2) {  
    87.                     // 2级菜单旋转出去  
    88.                     if (isDisplayLevel3) {  
    89.                         // 3级菜单先旋转出去  
    90.                         AnimationUtils.outRotateAnimation(rlLevel3, startOffset);  
    91.                         startOffset += 200;  
    92.                         isDisplayLevel3 = !isDisplayLevel3;  
    93.                     }  
    94.                     AnimationUtils.outRotateAnimation(rlLevel2, startOffset);  
    95.                     startOffset += 200// 延迟200ms  
    96.                     isDisplayLevel2 = !isDisplayLevel2;  
    97.                 }  
    98.                 AnimationUtils.outRotateAnimation(rlLevel1, startOffset);  
    99.             } else {  
    100.                 // 1级菜单旋转进来  
    101.                 AnimationUtils.inRotateAnimation(rlLevel1);  
    102.             }  
    103.             isDisplayLevel1 = !isDisplayLevel1;  
    104.         }  
    105.         return super.onKeyDown(keyCode, event);  
    106.     }  
    107. }  

    以上是整个自定义组件的全部源代码,值得注意的是:

    1,关于控件的焦点问题。

    在布局中可以看到,我在这个相对布局RelativeLayout中使用的控件都是ImageButton,ImageButton有个显著地特点就是它抢焦点的能力特别强。所以为了各层次菜单在动画执行的过程中,用户点击了ImageButton按钮,就会发生事件响应。这种用户体验是极不可取的,那么该怎么解决这个问题。

    诚然通过API查找发现,RelativeLayout继承于ViewGroup类

    ViewGroup提供了遍历子元素的方法getChildAt(int index),所以,我们在Animation的动画旋转出去的方法中,遍历菜单上的所有子控件,并设置其为不可用;而在动画旋转进来的方法,遍历菜单上的所有子控件,并设置其可用,这样的矛盾就解决了。


    2,关于快速点击菜单2次的问题,当快速点击菜单2次的时候,即第一次点击菜单,菜单还没有完全旋转出去或者旋转进来的时候,伴随着第二次点击菜单,这里执行相反的动画效果,即菜单还未完全出去或者进来的时候,中途被迫停止操作,取之而来的是相反的动作,即旋转进来或者旋转出去。那么,这个问题怎么解决?

    通过动画的API,可以发现Android给我们提供的一个接口:

    1. public static interface AnimationListener {  
    2.         /** 
    3.          * <p>Notifies the start of the animation.</p> 
    4.          * 
    5.          * @param animation The started animation. 
    6.          */  
    7.         void onAnimationStart(Animation animation);  
    8.   
    9.         /** 
    10.          * <p>Notifies the end of the animation. This callback is not invoked 
    11.          * for animations with repeat count set to INFINITE.</p> 
    12.          * 
    13.          * @param animation The animation which reached its end. 
    14.          */  
    15.         void onAnimationEnd(Animation animation);  
    16.   
    17.         /** 
    18.          * <p>Notifies the repetition of the animation.</p> 
    19.          * 
    20.          * @param animation The animation which was repeated. 
    21.          */  
    22.         void onAnimationRepeat(Animation animation);  
    23.     }  
    通过这个接口,一目了然这些方法的使用,只要自定义一个实现类,实现接口的三个方法后,设置一个静态变量来记录当前动画是否在运行状态,给动画设置这个监听。

    在MainActivity里,只要调用这个控制菜单动画的方法前,判断一下动画的运行状态,再做操作,这样问题就可以解决了。

    以上是个人爱好,源码已经上传,欢迎大家一起交流学习。


    源码请在这里下载


    展开全文
  • 目前,用户对安卓应用程序的UI设计要求越来越高,因此,掌握... 其中优酷中圆盘旋转菜单的实现就比较好。 该菜单共分里外三层导航菜单.可以依次从外向里关闭三层菜单,也可以反向打开,并且伴有圆盘旋转的动画效果。
  • 先上效果图:   布局文件: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" ... android:layout_width="match_parent... android
  • 高仿优酷圆盘旋转菜单的实现.zip
  • 目前,用户对安卓应用程序的UI设计要求越来越高,因此,... 其中优酷中圆盘旋转菜单的实现就比较好。 该菜单共分里外三层导航菜单.可以依次从外向里关闭三层菜单,也可以反向打开,并且伴有圆盘旋转的动画效果。  
  • 本文实例讲述了Android编程实现仿优酷圆盘旋转菜单效果的方法。分享给大家供大家参考,具体如下: 目前,用户对安卓应用程序的UI设计要求越来越高,因此,掌握一些新颖的设计很有必要. 比如菜单,传统的菜单已经不能...
  • 目前,用户对安卓应用程序的UI设计要求越来越高,因此,掌握一些新颖的设计很有必要. ... 其中优酷中圆盘旋转菜单的实现就比较优秀,这里我提供下我的思路及实现,仅供参考. 该菜单共分里外三层导航菜单.可

空空如也

空空如也

1 2 3 4
收藏数 75
精华内容 30
关键字:

android圆盘菜单