博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
精灵菜单
阅读量:6576 次
发布时间:2019-06-24

本文共 13708 字,大约阅读时间需要 45 分钟。

偶尔看到就知道了,几乎 文化都比較好,有归属感,多写写博客。希望能被豌豆荚发现大笑

一、功能介绍

1、简述

这是一个自己定义菜单View,包括菜单button和子菜单列表。

仅仅是基础呈现

2、功能描写叙述

a、支持与该菜单的各种交互。包含:单机、双击、上下左右滑动、长按等;
b、单次点击,可隐藏或显示子菜单列表;
c、长按,可托拽到任何位置;
d、拖动过程中。子菜单列表始终围绕在菜单button周围;
e、支持随意个子菜单。支持动态添加、降低子菜单数量;
f、菜单buttonView可任意更改。

3、动画演示

       
        

二、技术难点

1、自己定义View;
2、子菜单位置的计算;
3、交互事件,使用OnGestureListener、GestureDetector类;
4、托拽过程中,子菜单排列位置的实时变化。

三、设计思路

1、自己定义View

注意onMeasure、onDraw方法和自己定义的notifyLocationSetChanged方法。

2、子菜单位置计算

子菜单排列有九种方式:
屏幕中间CENTER,顺时针排列。
在屏幕左边界上BOUNDARY_LEFT,顺时针排列;
在屏幕右边界上BOUNDARY_RIGHT。逆时针排列。
在屏幕顶部边界上BOUNDARY_TOP,逆时针排列。
在屏幕底部边界上BOUNDARY_BOTTOM,顺时针排列;
在屏幕左上角CORNER_LEFT_TOP。顺时针排列;
在屏幕左下角CORNER_LEFT_BOTTOM,顺时针排列;
在屏幕右上角CORNER_RIGHT_TOP。逆时针排列;
在屏幕右下角CORNER_RIGHT_BOTTOM,顺时针排列。
注:在本文中约定,在以菜单中心为原点二维坐标系中。x轴正方向上为起始0弧度。则Y轴正方向上为-PI/2弧度。x轴负方向上为起始-PI弧度。

这里推理方式都差点儿相同,仅以 屏幕中间CENTER 情况为例分析:
此时,子菜单能够环绕菜单button一周,即2*PI,那么子菜单之间的夹角为unitRadian = 2*PI/(count - 1)  (count为子菜单的数量),我们能够得到,第一个子菜单位置在Y轴负方向上,距离为radius。第二个子菜单在在顺时针方向。与X轴正方向的夹角为-PI+unitRadian,以此类推。就能够得到全部子菜单位置,从而得到各子菜单距离菜单中心点的位置。到此就得到了Location。
从上面推理,能够得到子菜单的几点重要属性:起始位置startRadian、单位夹角unitRadian、方向direction、半径radius。

3、交互事件

当然会想到GestureDetector类处理。

4、托拽功能

监听长按事件和Touch事件的Move事件,依据矢量位移和图片在屏幕中的位置。在move过程中使用void android.view.ViewGroup.layout(int l, int t, int r, int b)方法。可重绘layout的位置。

四、源代码展示

源代码包含五部分:
菜单接口ChildsMenu
自己定义的菜单视图ChildsMenuLayout
主菜单位置信息Location
交互精灵 GestureSprite
ChildsMenu.java:
public interface ChildsMenu {	public Point DEFAULT_POSITION = new Point(0, 0);	public void showChilds(View[] view);	public void notifyLocationSetChanged();}
ChildsMenuLayout.java:
public class ChildsMenuLayout extends RelativeLayout implements ChildsMenu {	public double radius = 140d;	private Point position = DEFAULT_POSITION;	private List
points; private Context mContext; private View[] views; private LOCATION location; private boolean isShow = false; public ChildsMenuLayout(Context context) { super(context); // TODO Auto-generated constructor stub mContext = context; setWillNotDraw(false); } public ChildsMenuLayout(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub mContext = context; setWillNotDraw(false); } public ChildsMenuLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub mContext = context; setWillNotDraw(false); } @Override public void showChilds(View[] views) { // TODO Auto-generated method stub isShow = true; this.views = views; points = getDataPoints(views); this.invalidate(); } public void hideChilds() { isShow = false; this.invalidate(); } private List
getDataPoints(View[] views) { if (views == null) { isShow = false; return null; } Location locationOnScreen = new Location(mContext , this); location = locationOnScreen.getLocation(); List
points = new ArrayList
(); int count = views.length; double unitRadian = 0; double startRadian = 0; double endRadian = 0; final int Clockwise = 1;//顺时针 final int Eastern = -1;//逆时针 int direction = Clockwise; double temp; switch (location) { case BOUNDARY_BOTTOM: temp =Math.PI/2 - Math.asin((locationOnScreen.bottom + radius)/radius); unitRadian = 2*(Math.PI -temp)/ (count - 1); direction = Clockwise; startRadian = -Math.PI*3/2+temp; break; case BOUNDARY_LEFT: temp =Math.PI/2 - Math.asin((locationOnScreen.left + radius)/radius); unitRadian = 2*(Math.PI - temp) / (count - 1); direction = Clockwise; startRadian = -Math.PI + temp; break; case BOUNDARY_RIGHT: temp =Math.PI/2 - Math.asin((locationOnScreen.right + radius)/radius); unitRadian = 2*(Math.PI - temp) / (count - 1); direction = Eastern; startRadian = - temp; break; case BOUNDARY_TOP: temp =Math.PI/2 - Math.asin((locationOnScreen.top + radius)/radius); unitRadian = 2*(Math.PI -temp) / (count - 1); direction = Eastern; startRadian = -Math.PI/2 - temp; break; case CENTER: unitRadian = Math.PI * 2 / count; direction = Clockwise; startRadian = -Math.PI; break; case CORNER_LEFT_BOTTOM: startRadian = Math.asin((radius+locationOnScreen.left)/radius); endRadian = Math.asin((radius+locationOnScreen.bottom)/radius); unitRadian = (Math.PI / 2+startRadian+endRadian) / (count - 1); direction = Clockwise; startRadian = -Math.PI / 2 -startRadian; break; case CORNER_LEFT_TOP: startRadian = Math.asin((radius+locationOnScreen.top)/radius); endRadian = Math.asin((radius+locationOnScreen.left)/radius); unitRadian = (Math.PI / 2+startRadian+endRadian) / (count - 1); direction = Clockwise; startRadian = -startRadian; break; case CORNER_RIGHT_BOTTOM: startRadian = Math.asin((radius+locationOnScreen.right)/radius); endRadian = Math.asin((radius+locationOnScreen.bottom)/radius); unitRadian = (Math.PI / 2+startRadian+endRadian) / (count - 1); direction = Eastern; startRadian = -Math.PI / 2+startRadian; break; case CORNER_RIGHT_TOP: startRadian = Math.asin((radius+locationOnScreen.top)/radius); endRadian = Math.asin((radius+locationOnScreen.right)/radius); unitRadian = (Math.PI / 2+startRadian+endRadian) / (count - 1); direction = Eastern; startRadian = -Math.PI + startRadian; break; default: break; } for (int i = 0; i < count; i++) { PointF pt = new PointF(); if (direction == Eastern) { float offsetX = (float) (position.x + radius * Math.cos(-i * unitRadian + startRadian)); float offsetY = (float) (position.y + radius * Math.sin(-i * unitRadian + startRadian)); pt.set(offsetX, offsetY); } else if (direction == Clockwise) { float offsetX = (float) (position.x + radius * Math.cos(i * unitRadian + startRadian)); float offsetY = (float) (position.y + radius * Math.sin(i * unitRadian + startRadian)); pt.set(offsetX, offsetY); } points.add(pt); } return points; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); radius = this.getWidth() / 2 - 20; position = new Point(this.getWidth() / 2, this.getHeight() / 2); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); Paint paint = new Paint(); paint.setARGB(255, 207, 0, 112); paint.setTextSize(30); paint.setAntiAlias(true); if (points != null && isShow) { for (int i = 0; i < points.size(); i++) { PointF pt = points.get(i); canvas.drawText("" + i, pt.x, pt.y, paint); } } } @Override public void notifyLocationSetChanged() { // TODO Auto-generated method stub Location locationOnScreen = new Location(mContext , this); LOCATION temp = locationOnScreen.getLocation(); if(location== LOCATION.CENTER && temp == location){ return; } location = temp; points = getDataPoints(views); this.invalidate(); }}
GestureSprite.java
public class GestureSprite implements OnTouchListener, OnGestureListener {	private TextView tv2;	private ChildsMenuLayout mLayout;	private Context mContext;	private GestureDetector detector = new GestureDetector(this);    private final int MODE_DRAG = 0x1000;    private final int MODE_ZOOM = MODE_DRAG + 1;    private final int MODE_DEFAULT = -1;    private int MODE = MODE_DEFAULT;    private PointF oldPosition;    private PointF delta;        private View[] childViews;    private LOCATION location;        //************onTouch  start***********    private float x = 0, y = 0;      private int dx, dy;    private int left = 0, top = 0;    //************onTouch  end***********        private boolean isShowMenu = false;    @SuppressLint("NewApi")	public GestureSprite(Context context , ChildsMenuLayout layout,TextView tv2){    	mLayout = layout;    	mContext = context;    	this.tv2 = tv2;    }    	// 用户轻触触摸屏。由1个MotionEvent ACTION_DOWN触发	@Override	public boolean onDown(MotionEvent e) {		// TODO Auto-generated method stub		System.out.println("onDown");		tv2.setText("轻触触摸屏  按下");		return false;	}		// 用户轻触触摸屏,尚未松开或拖动。由一个1个MotionEvent ACTION_DOWN触发	// 注意和onDown()的差别,强调的是没有松开或者拖动的状态	@Override	public void onShowPress(MotionEvent e) {		// TODO Auto-generated method stub		System.out.println("onShowPress");		tv2.setText("轻触触摸屏,尚未松开或拖动");	}	// 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发	@Override	public boolean onSingleTapUp(MotionEvent e) {		// TODO Auto-generated method stub		System.out.println("onSingleTapUp");		toggleMenu();		tv2.setText("轻触触摸屏后松开");		return false;	}	// 用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发	@Override	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,			float distanceY) {		// TODO Auto-generated method stub		System.out.println("onScroll");		tv2.setText("按下触摸屏,并拖动");		return false;	}	// 用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发	@Override	public void onLongPress(MotionEvent e) {		// TODO Auto-generated method stub		System.out.println("onLongPress");		tv2.setText("长按触摸屏");		MODE = MODE_DRAG;	}	// 用户按下触摸屏、高速移动后松开。由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发	@Override	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,			float velocityY) {		// TODO Auto-generated method stub		float dx = e2.getX() - e1.getX();          float dy = e2.getY() - e1.getY();          if(dx > 0 && Math.abs(dx) > Math.abs(dy)){        	System.out.println("onFling right");        	tv2.setText("右滑");        }else if(dx < 0 && Math.abs(dx) > Math.abs(dy)){        	System.out.println("onFling left");        	tv2.setText("左滑");        }else if(dy > 0 && Math.abs(dy) > Math.abs(dx)){        	System.out.println("onFling down");        	tv2.setText("下滑");        }else if(dy < 0 && Math.abs(dy) > Math.abs(dx)){        	tv2.setText("上滑");        	System.out.println("onFling up");        }		return false;	}	@Override	public boolean onTouch(View v, MotionEvent event) {		// TODO Auto-generated method stub		detector.onTouchEvent(event);		switch (event.getAction() & MotionEvent.ACTION_MASK) {		case MotionEvent.ACTION_DOWN: // 指点杆按下			// 将当前的坐标保存为起始点			 x = event.getRawX();               y = event.getRawY();               left = mLayout.getLeft();             top = mLayout.getTop();			break;		case MotionEvent.ACTION_MOVE: // 指点杆保持按下。而且进行位移			if (MODE == MODE_DRAG) {				dx =  (int) ((event.getRawX() -x) + left);                  dy =  (int) ((event.getRawY() -y) + top);                  mLayout.layout(dx, dy, dx + mLayout.getWidth(), dy + mLayout.getHeight());                mLayout.notifyLocationSetChanged();			}			break;		case MotionEvent.ACTION_UP: // 指点杆离开屏幕			MODE = MODE_DEFAULT;			break;		case MotionEvent.ACTION_POINTER_UP: // 有手指头离开屏幕,但还有没离开的			break;		case MotionEvent.ACTION_POINTER_DOWN: // 假设已经有手指压在屏幕上,又有一个手指压在了屏幕上			break;		}		return true;	}		private void toggleMenu(){		isShowMenu = isShowMenu?closeMenu():openMenu();	}		private boolean openMenu(){		childViews = new View[6];		mLayout.showChilds(childViews);		return true;	}		private boolean closeMenu(){//		int count = mLayout.getChildCount();//		if(count > 1){//			mLayout.removeViews(1, count);//			mLayout.invalidate();//		}		mLayout.hideChilds();		return false;	}		private void setLocation(){		View menuBtn = mLayout.getChildAt(0);			}
Location.java:
public class Location {	private int screenWidth;	private int screenHeight;	private int viewWidth;	private int viewHeight;	public double left;	public double top;	public double bottom;	public double right;	private int code;	public enum LOCATION{		CENTER,BOUNDARY_LEFT,BOUNDARY_RIGHT,BOUNDARY_TOP,BOUNDARY_BOTTOM,CORNER_LEFT_TOP,CORNER_LEFT_BOTTOM		,CORNER_RIGHT_TOP,CORNER_RIGHT_BOTTOM	}		public Location(Context context,View view){		screenWidth  = ((Activity)context).getWindowManager().getDefaultDisplay().getWidth(); 		screenHeight = ((Activity)context).getWindowManager().getDefaultDisplay().getHeight(); 		viewWidth = view.getWidth();		viewHeight = view.getHeight();		int[] array = new int[2];		view.getLocationOnScreen(array);		left = array[0];		top = array[1];		right = screenWidth - (left + viewWidth);		bottom = screenHeight - (top + viewHeight);	}			private void onLeft(){		code = left < 0 ?

0x0001 : 0x0; } private void onRight(){ code = right < 0 ? code + 0x0010 : code; } private void onTop(){ code = top < 0 ? code + 0x0100 : code; } private void onBottom(){ code = bottom < 0 ? code + 0x1000 : code; } // private void onCornerLeftTop(){ // code = onLeft()&&onTop() ?

code + 1 : code; // } // // private void onCornerLeftBottom(){ // code = onLeft()&&onBottom(); // } // // private void onCornerRightTop(){ // code = onRight()&&onTop(); // } // // private void onCornerRightBottom(){ // code = onRight()&&onBottom(); // } public LOCATION getLocation(){ LOCATION location = null; onLeft(); onRight(); onTop(); onBottom(); switch (code) { case 0x0000: location = LOCATION.CENTER; break; case 0x0001: location = LOCATION.BOUNDARY_LEFT; break; case 0x0010: location = LOCATION.BOUNDARY_RIGHT; break; case 0x0100: location = LOCATION.BOUNDARY_TOP; break; case 0x1000: location = LOCATION.BOUNDARY_BOTTOM; break; case 0x1001: location = LOCATION.CORNER_LEFT_BOTTOM; break; case 0x0101: location = LOCATION.CORNER_LEFT_TOP; break; case 0x1010: location = LOCATION.CORNER_RIGHT_BOTTOM; break; case 0x0110: location = LOCATION.CORNER_RIGHT_TOP; break; default: break; } return location; } }

xml:
调用时,仅仅需一行代码:
menuBtn = (ImageView) findViewById(R.id.btn_bb_menu);menuBtn.setOnTouchListener(new GestureSprite(this, menuLayout,tv2));
请标明转载自:http://blog.csdn.net/toyuexinshangwan/article/details/37594329
源代码下载地址:http://download.csdn.net/detail/toyuexinshangwan/7613097

版权声明:本文博主原创文章,博客,未经同意不得转载。

你可能感兴趣的文章
二、服务器优化(2)版本更新与负载预估
查看>>
svn 代码管理工具
查看>>
【Go语言】【7】GO语言的切片
查看>>
【斗医】【14】Web应用开发20天
查看>>
Python回顾与整理11:面向对象编程
查看>>
DVWA系列之16 文件包含漏洞挖掘与防御
查看>>
mysql忧化参数
查看>>
MariaDB 10.0.X中,动态列支持 JSON 格式来获取数据。
查看>>
【习惯的力量】之五让拖延见鬼去吧
查看>>
SFB 项目经验-24-为持久聊天室-查询或者增加成员
查看>>
配套自测连载(一)
查看>>
Oracle高水位High Water Mark(HWM)简介
查看>>
“神秘”的加密指纹
查看>>
统一沟通-技巧-11-Lync-联盟-1-MSN
查看>>
职业、管理和招聘——近期的一些碎碎念
查看>>
SecureCRT session 的迁移
查看>>
【COCOS CREATOR 系列教程之二】脚本开发篇&事件监听、常用函数等示例整合
查看>>
Python工程师求职比中秘籍——获取offer有这么多门道
查看>>
Exchange工具07—Http watch
查看>>
SCCM2012 R2实战系列之四:初始化配置
查看>>