Android UI 之SlidingMenu实现方法(一)

    SlidingMenu的实现方法有很多,今天先来分享一种方法。

主要的思路是根据手指在屏幕上的滑动情况,不断更改一个页面的LayoutParams,然后请求重绘,造成这个页面随着手指而移动的效果。

    先来看一看实现的效果:

下面具体说一说思路,其实很简单:

    我们的例子里面有两个页面,一个mainPage页面,相当于应用的主功能页面。另一个menuPage页面,就相当于是我们需要滑动呼出的菜单页面了。对照上面的gif图,mainPage相当于美女页面,menuPage就相当于是ListView所在的那个菜单页面。

    首先我们把这两个页面布局到RelativeLayout里,先布局菜单页面,设置该页面的左侧边距离屏幕左侧边一个固定的距离。然后布局美女页面,让美女页面覆盖菜单页面,后面的工作就是针对美女页面设置好监听,让美女页面能够跟随手指的坐标变化不断刷新自己的布局参数了。美女页面移动后,它覆盖的菜单页面就会显露出来。

    我们在这里的思路是通过不断改变美女页面LayoutParams的leftMargin参数来实现页面的移动,在初始状态leftMargin的值是0,如果我们要让页面向左移动,就需要设置leftMargin继续变小,这个变小的值的极限应该是 ( 菜单页面的宽度 - 菜单页面左侧到屏幕左侧的距离),配合上面的动态图应该能很容易理解。相应的,leftMargin的最大值明显是0。

    这里有一个问题要注意,我们在设置美女页面的LayoutParams 的宽度参数的时候,不能用MATCH_PARENT,因为在这种情况下这个页面会永拉伸远填充满屏幕,我们应该设置页面的宽度是屏幕的宽度。

    剩下的就是在滑动过程中抬起手指以及点击美女页面自动伸缩的处理,我们设置一个页面伸缩的速度值,然后在线程里每隔一段时间就让leftMargin变化该值,就可以实现页面的自动伸缩了。需要注意的就是伸缩方向的判断。

    好了,具体的细节大家可以来看代码,我做的注释挺详细的,相信每个人都可以看懂。

SlidingMenu类:

/**
 * SlidingMenu
 * @author Carrey
 *
 */
public class SlidingMenu extends RelativeLayout 
		implements OnTouchListener, GestureDetector.OnGestureListener {
	/** Menu页面 */
	private RelativeLayout menuPage;
	/** 应用的主页面 */
	private RelativeLayout mainPage;
	/** Menu页面不靠屏幕的那条边到屏幕边缘的距离 */
	private int distanceToScreenEdge;
	/** mainPage可以伸展的范围,实际上就是menuPage的宽度 */
	private int maxWidth = 0;
	/** 是否已经按照屏幕的宽度计算并设置了mainPage的宽度 */
	private boolean hasMeasured = false;
	/** 窗口Activity */
	private Activity parentActivity;
	/** 手势处理 */
	private GestureDetector mGestureDetector;
	/** 标记是否在滚动状态 */
	private boolean isScrolling = false;
	/** 屏幕宽度 */
	private int windowWidth;
	/** 自动伸缩的速度 */
	private int speed = 30;
	/** 一次手指滚动的动作中手指滚动的距离 */
	private float mScrollX;
	/** mainPage的LayoutParams */
	private LayoutParams mainParam;
	
	public SlidingMenu(Activity context) {
		super(context);
		this.parentActivity = context;
		//设置 distanceToScreenEdge 数值是 50dp
		distanceToScreenEdge = dip2px(context, 50);
	}
	
	/**
	 * 添加Menu页面
	 * @param menuPage
	 */
	public void addMenuPage(RelativeLayout menuPage) {
		this.menuPage = menuPage;
	}
	/**
	 * 添加应用的主页面
	 * @param mainPage
	 */
	public void addMainPage(RelativeLayout mainPage) {
		this.mainPage = mainPage;
	}
	/**
	 * 安装Menu页面和主页面,以及其他处理
	 */
	public void setUp() {
		//先得到mainPage的固定宽度,等于屏幕的宽度
		windowWidth = parentActivity.getWindowManager().getDefaultDisplay().getWidth();
		//重新设置两个页面的布局并添加
		LayoutParams menuParam = new LayoutParams(
				LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
		menuParam.leftMargin = distanceToScreenEdge;
		menuPage.setLayoutParams(menuParam);
		this.addView(menuPage);
		mainParam = new LayoutParams(
				windowWidth, LayoutParams.MATCH_PARENT);
		mainPage.setLayoutParams(mainParam);
		this.addView(mainPage);
		
		mGestureDetector = new GestureDetector(parentActivity, this);
		//禁止长按是因为长按一旦触发,滚动事件就不能触发了
		mGestureDetector.setIsLongpressEnabled(false);
		
		mainPage.setOnTouchListener(this);
	}
	/** 
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素) 
     */  
    private int dip2px(Context context, float dpValue) {  
        float scale = context.getResources().getDisplayMetrics().density;
        //四舍五入
        return (int) (dpValue * scale + 0.5f);  
    }  
  
    /** 
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp 
     */  
    private int px2dip(Context context, float pxValue) {  
        final float scale = context.getResources().getDisplayMetrics().density;  
        return (int) (pxValue / scale + 0.5f);  
    }
    /** ------------------OnTouchListener Methods------------------- */
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		if (!hasMeasured) {
			maxWidth = menuPage.getMeasuredWidth();
			hasMeasured = true;
		}
		//UP事件在GestureDetector里不会捕捉,所以在这里处理
		if (event.getAction() == MotionEvent.ACTION_UP 
				&& isScrolling) {
			
			if (mainParam.leftMargin < -windowWidth / 2) {
				new AsynMove().execute(-speed);
			} else {
				new AsynMove().execute(speed);
			}
			isScrolling = false;
		}
		return mGestureDetector.onTouchEvent(event);
	}
	/** -------------------OnGestureListener Methods---------------------- */
	@Override
	public boolean onDown(MotionEvent e) {
		mScrollX = 0;
		isScrolling = false;
		//这里要返回true,这样后面的事件才会传递进来,
		//如果返回false,事件会传递给菜单页面,主页面就不能捕捉后续事件了
		return true;
	}

	@Override
	public void onShowPress(MotionEvent e) {
		
	}

	@Override
	public boolean onSingleTapUp(MotionEvent e) {
		if (mainParam.leftMargin >= 0) {
			new AsynMove().execute(-speed);
		} else {
			new AsynMove().execute(speed);
		}
		return true;
	}

	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
			float distanceY) {
		isScrolling = true;
		//这个distanceX是之前的坐标减去之后的坐标
		//final float scrollX = mLastFocusX - focusX;
        //final float scrollY = mLastFocusY - focusY;
		mScrollX += distanceX;
		
		mainParam.leftMargin -= mScrollX;
		if (mainParam.leftMargin >=0) {
			isScrolling = false;
			mainParam.leftMargin = 0;
		} else if (mainParam.leftMargin <= -maxWidth) {
			isScrolling = false;
			mainParam.leftMargin = -maxWidth;
		}
		//因为mainPage的LayoutParams改变了,所以如果请求ViewTree重新遍历mainPage一定会重绘,但是
		//menuPage的布局参数没有改变,所以我们应该通过menuPage强制requestLayout()进行重绘,以避免
		//绘制发生错误
		menuPage.requestLayout();
		return false;
	}

	@Override
	public void onLongPress(MotionEvent e) {
		
	}

	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
			float velocityY) {
		return false;
	} 
	/** -------------AsyncTask---------------- */
	private class AsynMove extends AsyncTask<Integer, Integer, Void> {

		@Override
		protected Void doInBackground(Integer... params) {
			int times = 0;
			if (maxWidth % Math.abs(params[0]) == 0)
				times = maxWidth / Math.abs(params[0]);
			else
				times = maxWidth / Math.abs(params[0]) + 1;
			
			for (int i = 0; i < times; i++) {
				publishProgress(params[0]);
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			return null;
		}

		@Override
		protected void onProgressUpdate(Integer... values) {
			if (values[0] > 0) {
				mainParam.leftMargin = Math.min(mainParam.leftMargin + values[0], 0);
			} else {
				mainParam.leftMargin = Math.max(mainParam.leftMargin + values[0], -maxWidth);
			}
			//因为mainPage的LayoutParams改变了,所以如果请求ViewTree重新遍历mainPage一定会重绘,但是
			//menuPage的布局参数没有改变,所以我们应该通过menuPage强制requestLayout()进行重绘,以避免
			//绘制发生错误
			menuPage.requestLayout();
			super.onProgressUpdate(values);
		}

	}
}
MainActivity

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		//设置全屏
		getWindow().setFlags(
				WindowManager.LayoutParams.FLAG_FULLSCREEN, 
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		
		ListView menuItemList = new ListView(this);
		menuItemList.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
		ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, 
				new String[]{"墨尔本","罗兰加洛斯","温布尔登","法拉盛"});
		menuItemList.setAdapter(adapter);
		
		RelativeLayout menuPage = new RelativeLayout(this);
		menuPage.addView(menuItemList);
		RelativeLayout mainPage = new RelativeLayout(this);
		mainPage.setBackgroundResource(R.drawable.pic);
		
		SlidingMenu slidingMenu = new SlidingMenu(this);
		slidingMenu.addMenuPage(menuPage);
		slidingMenu.addMainPage(mainPage);
		slidingMenu.setUp();
		
		setContentView(slidingMenu);
	}

}
代码下载

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值