Android侧滑菜单完整详细示例(基础版)

简介: MainActivity如下:package cc.cd;import android.os.AsyncTask;import android.
MainActivity如下:
package cc.cd;

import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.app.Activity;
import android.content.Context;
/**
 * Demo描述:
 * 侧滑菜单SlidingMenu的简单示例
 * 即在一个Activity中加入SlidingMenu
 * 
 * 示例说明:
 * 示例中一共两个界面menu和content.当进入应用时显示content界面.
 * 若手指滑向屏幕右侧则显示menu和隐藏content.反之,同理.
 * menu的显示和隐藏主要是修改menu的LayoutParams中的leftMargin从而达到效果.
 * 
 * 步骤整理:
 * 1 初始化时,通过设置menu的LayoutParams中的leftMargin使其完全隐藏.
 * 2 为content设置Touch监听.
 * 3 在move的过程中不断修改menu的LayoutParams中的leftMargin使其按照
 *   手势的滑动而显示或者隐藏
 * 4 在up时通过异步任务AsyncTask修改menu的LayoutParams中的leftMargin
 *   从而实现menu的完全显示或者完全隐藏.
 *   
 * 以上套路还是挺常见的.
 * 
 * 参考资料:
 * 1 http://blog.csdn.net/guolin_blog/article/details/8714621
 * 2 http://blog.csdn.net/hudashi/article/details/7352157
 *   Thank you very much
 * 
 * 备注说明:
 * 1 示例中使用的图片亦来自参考资料1
 * 2 为简化逻辑,示例中的两个界面均为截图而不是实际View界面
 */
public class MainActivity extends Activity {
	private int screenWidth;
    private View contentView;
    private View menuView;
    private float xDown;
    private float xMove;
    private float xUp;
    //当完全显示menu时content的宽度最小值
    private int contentViewMinWidth = 80;
    //menu是否可见的标志位,该值在滑动过程中无效.
    //只有在滑动结束后,完全显示或隐藏menu时才会更改此值
    private boolean isMenuVisible=false;
    private int menuParamsMaxLeftMargin=0;
    private int menuParamsMinLeftMargin=0;
    //速度追踪
    private VelocityTracker mVelocityTracker;
    //阈值
    public static final int VELOCITY_THRESHOLD=200;
    //TAG
    private final static String TAG="MainActivity";
    //menu的布局LayoutParams
    private LinearLayout.LayoutParams menuLayoutParams;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		init();
	}

	private void init(){
		//获取屏幕宽度
		WindowManager windowManager=(WindowManager) getSystemService(Context.WINDOW_SERVICE);
		screenWidth=windowManager.getDefaultDisplay().getWidth();
		
		//初始化contentView
		contentView=findViewById(R.id.contentLinearLayout);
		//将contentView的宽度设置为屏幕的宽度
		contentView.getLayoutParams().width=screenWidth;
		//设置Touch监听
		contentView.setOnTouchListener(new TouchListenerImpl());
		
		//初始化menuView
		menuView=findViewById(R.id.menuLinearLayout);
		menuLayoutParams=(LinearLayout.LayoutParams) menuView.getLayoutParams();
		//设置menuView的宽度.
		//设置其宽度为屏幕宽度减去contentView的最小宽度
		menuLayoutParams.width=screenWidth-contentViewMinWidth;
		//初始化时完全隐藏了menuView.
		menuParamsMinLeftMargin=-menuLayoutParams.width;
		menuLayoutParams.leftMargin=menuParamsMinLeftMargin;
	}
	
	/**
	 * 关于ACTION_MOVE中distanceX的细节说明.
	 * 在一次滑动过程中(从手指按下到手指抬起)distanceX的值是持续变大或者变小的.
	 * 因为int distanceX=(int) (xMove-xDown);
	 * 这个xDown是按下时的坐标值,在Moving的过程中计算distanceX时一直采用
	 * 的是xDown减去每时刻的xMove.即在滑动过程中xDown一直不变而xMove是不断
	 * 在变化的.
	 * 代码说明:
	 * if (isMenuVisible) {
	 *     menuLayoutParams.leftMargin=distanceX;
	 * } else {
	 *    menuLayoutParams.leftMargin=menuParamsMinLeftMargin+distanceX;
	 * }
	 * 在最开始时,menu是隐藏的.手指按下,滑向屏幕的右边.调用:
	 * menuLayoutParams.leftMargin=menuParamsMinLeftMargin+distanceX;
	 * 所以这个menuLayoutParams.leftMargin是不断在变大的直到0为止(注意越界判断),此时
	 * menuView完全显示.这时手指再按下,滑向屏幕的左边.调用:
	 * menuLayoutParams.leftMargin=distanceX;
	 * distanceX这个负数一直在减小,它就是menuLayoutParams.leftMargin的值直至
	 * menuView完全隐藏为止,此时它的值为menuParamsMinLeftMargin(注意越界判断).
	 * 
	 * 该问题不难,但在此备注一下以防以后反应不过来.
	 */
	private class TouchListenerImpl implements OnTouchListener{
		@Override
		public boolean onTouch(View v, MotionEvent event) {
			//开始速度追踪
			startVelocityTracker(event);
			switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:
				xDown=event.getRawX();
				break;
			case MotionEvent.ACTION_MOVE:
				xMove=event.getRawX();
				int distanceX=(int) (xMove-xDown);
				Log.i(TAG, "xDown="+xDown+",xMove="+xMove+",distanceX="+distanceX);
				if (isMenuVisible) {
					menuLayoutParams.leftMargin=distanceX;
				} else {
					menuLayoutParams.leftMargin=menuParamsMinLeftMargin+distanceX;
				}
				
				//处理越界的情况
				if(menuLayoutParams.leftMargin<menuParamsMinLeftMargin){
					menuLayoutParams.leftMargin=menuParamsMinLeftMargin;
				}
				if(menuLayoutParams.leftMargin>menuParamsMaxLeftMargin){
					menuLayoutParams.leftMargin=menuParamsMaxLeftMargin;
				}
				//设置menuView的LayoutParams
				menuView.setLayoutParams(menuLayoutParams);
				break;
			case MotionEvent.ACTION_UP:
				xUp=event.getRawX();
				
				//判断手势意图想显示menu
				if (wantToShowMenu()) {
					//判断是否显示menu
					if (shouldScrollToMenu()) {
						scrollToMenu();
					} else {
						scrollToContent();
					}
				}
				
				//判断手势意图想显示content
				if (wantToShowContent()) {
					//判断是否显示content
					if (shouldScrollToContent()) {
						scrollToContent();
					} else {
						scrollToMenu();
					}
				}
				//终止速度追踪
				stopVelocityTracker();
				break;
			default:
				break;
			}
			return true;
		}
		
	}
	
	/**
	 * 判断当前手势是否想显示菜单Menu
	 * 判断条件:
	 * 1 抬起坐标大于按下坐标
	 * 2 menu本身不可见
	 */
	private boolean wantToShowMenu(){
		return ((xUp-xDown>0)&&(!isMenuVisible));
	}
	
	/**
	 * 判断是否应该将menu完整显示出来
	 * 判断条件:
	 * 滑动距离大于屏幕的二分之一
	 * 或者滑动速度大于速度阈值VELOCITY_THRESHOLD
	 */
	private boolean shouldScrollToMenu(){
		return ((xUp-xDown>screenWidth/2)||(getScrollVelocity()>VELOCITY_THRESHOLD));
	}
	
	/**
	 * 将屏幕滚动到menu.即将menu完整显示.
	 * 按照30的步调不断修改修改menu的LayoutParams中的leftMargin
	 */
	private void scrollToMenu(){
		new ScrollAsyncTask().execute(30);
	}
	
	/**
	 * 判断当前手势是否想显示菜单Content
	 * 判断条件:
	 * 1 抬起坐标小于按下坐标
	 * 2 menu本身可见
	 */
	private boolean wantToShowContent(){
		return ((xUp-xDown<0)&&(isMenuVisible));
	}
	
	
	/**
	 * 判断是否应该将content完整显示出来
	 * 判断条件:
	 * xDown-xUp+contentViewMinWidth大于屏幕的二分之一
	 * 或者滑动速度大于速度阈值VELOCITY_THRESHOLD
	 */
	private boolean shouldScrollToContent(){
		return ((xDown-xUp+contentViewMinWidth>screenWidth/2)||(getScrollVelocity()>VELOCITY_THRESHOLD));
	}
	
	/**
	 * 将屏幕滚动到content.即将content完整显示
	 * 按照-30的步调不断修改修改menu的LayoutParams中的leftMargin
	 */
	private void scrollToContent(){
		new ScrollAsyncTask().execute(-30);
	}
	
	
	/**
	 * 开始速度追踪
	 */
	private void startVelocityTracker(MotionEvent event){
		if (mVelocityTracker==null) {
			mVelocityTracker=VelocityTracker.obtain(); 
		}
		mVelocityTracker.addMovement(event);
	}
	
	/**
	 * 获取在content上X方向的手指滑动速度
	 */
	private int getScrollVelocity(){
		//设置VelocityTracker单位.1000表示1秒时间内运动的像素 
		mVelocityTracker.computeCurrentVelocity(1000);
		//获取在1秒内X方向所滑动像素值
		int xVelocity=(int) mVelocityTracker.getXVelocity();
		return Math.abs(xVelocity);
	}
	
	/**
	 * 终止速度追踪
	 */
	private void stopVelocityTracker(){
		if (mVelocityTracker!=null) {
			mVelocityTracker.recycle();
			mVelocityTracker=null;
		}
	}
	
	
	/**
	 * 利用异步任务不断修改menu的LayoutParams中的leftMargin从而达到一个
	 * 视图移动的效果
	 */
	private class ScrollAsyncTask extends AsyncTask<Integer, Integer, Integer>{
		@Override
		protected Integer doInBackground(Integer... speed) {
			int leftMargin=menuLayoutParams.leftMargin;
			while(true){
				//每次变化的speed
				leftMargin=leftMargin+speed[0];
				//若越界,则处理越界且跳出循环
				if (leftMargin>menuParamsMaxLeftMargin) {
					leftMargin=menuParamsMaxLeftMargin;
					break;
				}
				//若越界,则处理越界且跳出循环
				if (leftMargin<menuParamsMinLeftMargin) {
					leftMargin=menuParamsMinLeftMargin;
					break;
				}
				//通知进度更新
				publishProgress(leftMargin);
				//线程睡眠25毫秒,便于体现滚动效果
				try {
					Thread.sleep(25);
				} catch (Exception e) {
				}
			}
			
			//依据滑动的速度设置标志位isMenuVisible
			if (speed[0]>0) {
				isMenuVisible=true;
			}else{
				isMenuVisible=false;
			}
			return leftMargin;
		}

		@Override
		protected void onProgressUpdate(Integer... leftMargin) {
			super.onProgressUpdate(leftMargin);
			menuLayoutParams.leftMargin=leftMargin[0];
			menuView.setLayoutParams(menuLayoutParams);
		}
		
		@Override
		protected void onPostExecute(Integer leftMargin) {
			super.onPostExecute(leftMargin);
			menuLayoutParams.leftMargin=leftMargin;
			menuView.setLayoutParams(menuLayoutParams);
		}
	}

}

main.xml如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <LinearLayout
        android:id="@+id/menuLinearLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/menu"
        android:orientation="vertical" />

    <LinearLayout
        android:id="@+id/contentLinearLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/content"
        android:orientation="vertical" />

</LinearLayout>

相关文章
|
3月前
|
存储 算法 开发工具
OpenCV 安卓编程示例:1~6 全
OpenCV 安卓编程示例:1~6 全
55 0
|
8月前
|
Android开发
Android 中选项菜单(Option menu)的用法
Android 中选项菜单(Option menu)的用法
82 0
|
4月前
|
Android开发
[Android]DrawerLayout滑动菜单+NavigationView
[Android]DrawerLayout滑动菜单+NavigationView
27 0
|
4月前
|
XML Java Android开发
Android App手势冲突处理中上下左右滑动的处理以及侧滑边缘菜单的讲解及实战(附源码 可直接使用)
Android App手势冲突处理中上下左右滑动的处理以及侧滑边缘菜单的讲解及实战(附源码 可直接使用)
67 0
|
4月前
|
XML Java Android开发
Android Studio App开发中工具栏Toolbar、溢出菜单OverflowMenu、标签布局TabLayout的讲解及实战(实现京东App的标签导航栏,附源码)
Android Studio App开发中工具栏Toolbar、溢出菜单OverflowMenu、标签布局TabLayout的讲解及实战(实现京东App的标签导航栏,附源码)
59 0
|
9月前
|
XML Android开发 数据格式
Android 彩色上下文菜单 Context
Android 彩色上下文菜单 Context
|
9月前
|
XML Android开发 数据格式
Android上机实验-4 菜单和对话框
Android上机实验-4 菜单和对话框
110 1
|
9月前
|
XML Java 测试技术
【Android开发日常】一文弄懂桌面图标快捷菜单 & 桌面小组件
开发可以定义快捷方式,以便在应用中执行特定操作。 这些快捷方式可在受支持的启动器或助理(如 Google 助理)中显示,方便用户快速启动应用中的常见任务或推荐任务。 通过本文你还将了解一些可提升快捷方式效果的最佳做法。
|
10月前
|
前端开发 Android开发
Android高仿qq及微信底部菜单的几种实现方式
Android高仿qq及微信底部菜单的几种实现方式
|
10月前
|
数据库 Android开发
重新构建711的Android项目(一),巧妙的小屏菜单查询框架实现
重新构建711的Android项目(一),巧妙的小屏菜单查询框架实现