这两天公司比较闲,所以重新研究了一下侧滑菜单。可能是因为很多东西都是现成的吧,基本上都是可以直接拿来就能用的。但是确实感觉自己的技术方面有很多薄弱的地方,都需要去补,所以虽然有现成的框架,还是打算自己实现。

        以前有听老师讲过类似的效果,大概的思路是:自定义一个view,横向排列,左边的布局设置margin为负值,加载的时候先隐藏。然后在onTouchListener中,通过移动的x的值来判断是否需要显示左边的菜单。大概是这样,有兴趣的可以自己试试。

        不过这两天查看了很多资料后发现,有一种更简单的方式:重写HorizontalScrollView的onMeasure和onLayout,来实现。

public class SlidingMenu extends HorizontalScrollView {
    private int ScreenWidth;//屏幕宽度
    private int mMenuRightPadding;//菜单离右边的距离
    private int mMenuWidth;//菜单的宽度
    private TypedArray typedarray;
    private int mHalfMenuWidth;
    private boolean once=false;
    private String TAG="TAG";
	public SlidingMenu(Context context) {
		super(context);
		this.ScreenWidth=getResources().getDisplayMetrics().widthPixels;
	}
	public SlidingMenu(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.ScreenWidth=getResources().getDisplayMetrics().widthPixels;//获取手机屏幕宽度
		typedarray=context.obtainStyledAttributes(attrs,R.styleable.SlidingMenu);
//为了不让菜单铺满整个布局,所以设置一个距离。
		mMenuRightPadding=typedarray.getDimensionPixelSize(
				R.styleable.SlidingMenu_rightPadding, 50);
		/**
		 *官方的解释是:回收TypedArray,以便后面重用。在调用这个函数后,你就不能再使用这个TypedArray。
                 *在TypedArray后调用recycle主要是为了缓存。当recycle被调用后,这就说明这个对象从现在可以被重用了。
                 *TypedArray 内部持有部分数组,它们缓存在Resources类中的静态字段中,这样就不用每次使用前都需要分配内存。
		 */
		typedarray.recycle();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		/**
		 * 设置菜单的宽度和内容的宽度
		 */
		Log.i(TAG, "---------onMeasure-------------");
		if(!once){
			LinearLayout linearlayout=(LinearLayout) this.getChildAt(0);
			ViewGroup menu=(ViewGroup) linearlayout.getChildAt(0);
			ViewGroup content=(ViewGroup) linearlayout.getChildAt(1);
			mMenuWidth=ScreenWidth-mMenuRightPadding;
			mHalfMenuWidth=mMenuWidth/2;
			menu.getLayoutParams().width=mMenuWidth;
			content.getLayoutParams().width=ScreenWidth;
		}
	}
	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		Log.i(TAG, "---------onLayout-------------");
		if(changed)
		{
			this.scrollTo(mMenuWidth, 0);
			once=true;
		}
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		switch(ev.getAction())
		{
		case MotionEvent.ACTION_UP:
			int upX=getScrollX();
			if(upX>mHalfMenuWidth)
			{
				this.scrollTo(mMenuWidth,0);
			}else
			{
				this.scrollTo(0, 0);
			}
			return true;
		}
		return super.onTouchEvent(ev);
	}
}

        在我们自定义view的时候,首先肯定是需要实现构造方法的。主要实现一个参数的构造方法和两个参数的构造方法。其中一个参数的构造方法,是我们如果直接在代码中实例化对象的时候需要用到的,两个参数的构造方法,是我们在xml文件中需要用到的。

       在SlidingMenu这两个构造方法中,我们都获取了屏幕的宽度。在两个参数的构造方法中,并且我们在两个参数的构造方法中,获取了自定义的属性。

this.ScreenWidth=getResources().getDisplayMetrics().widthPixels;
		typedarray=context.obtainStyledAttributes(attrs,R.styleable.SlidingMenu);
		mMenuRightPadding=typedarray.getDimensionPixelSize(
				R.styleable.SlidingMenu_rightPadding, 50);
		/**
		 *官方的解释是:回收TypedArray,以便后面重用。在调用这个函数后,你就不能再使用这个TypedArray。
                 *在TypedArray后调用recycle主要是为了缓存。当recycle被调用后,这就说明这个对象从现在可以被重用了。
                 *TypedArray 内部持有部分数组,它们缓存在Resources类中的静态字段中,这样就不用每次使用前都需要分配内存。
		 */
		typedarray.recycle();

       然后在onMeasure这个方法中,我们分别拿到根部局的子布局和子布局的子布局,并分别为它们设置了宽度。

       然后在onLayout中只做了一件事,就是隐藏菜单栏。

this.scrollTo(mMenuWidth, 0);//隐藏菜单

       最后在onTouchEvent中,判断如果在action_up的时候,得到的X坐标值如果大于菜单宽度的一半,就隐藏菜单,反之则显示。这个可能不太好理解,不过自己操作几下应该就能明白。这里需要注意的是,在判断结尾处,必须要加return true,否则会出现菜单隐藏或者出现一半的情况。

case MotionEvent.ACTION_UP:
			int upX=getScrollX();
			if(upX>mHalfMenuWidth)
			{
				this.scrollTo(mMenuWidth,0);
			}else
			{
				this.scrollTo(0, 0);
			}
			return true;