首页 > 代码库 > Android侧滑菜单完整详细示例(基础版)

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>

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