• 精灵菜单


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

    一、功能介绍

    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<PointF> 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<PointF> getDataPoints(View[] views) {
    		if (views == null) {
    			isShow = false;
    			return null;
    		}
    		Location locationOnScreen = new Location(mContext , this);
    		location = locationOnScreen.getLocation();
    		List<PointF> points = new ArrayList<PointF>();
    		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:
    <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" >
    
            <com.example.template.sprite.ChildsMenuLayout
                android:id="@+id/layout_bb_menu"
                android:layout_width="150sp"
                android:layout_height="150sp"
                android:layout_centerInParent="true" >
    
                <ImageView
                    android:id="@+id/btn_bb_menu"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:longClickable="true"
                    android:scaleType="matrix"
                    android:src="@drawable/ic_launcher" />
            </com.example.template.sprite.ChildsMenuLayout>
        </RelativeLayout>

    调用时,仅仅需一行代码:
    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


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

  • 相关阅读:
    Java-LockSupport
    Kafka Eagle 安装
    Kafka shell
    python pip 使用
    Kafka 集群部署
    Kafka 概述
    DockerFile 简单使用
    《深入理解Java虚拟机》读书笔记
    linux安装redis
    Java多线程基础知识例子
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/4798845.html
Copyright © 2020-2023  润新知