打造自己的侧滑菜单
现在大多数的软件中都能看到侧滑菜单的身影,而且这个功能看起来真的很高大上,不是么,滑的一下就出来了一个菜单,真牛逼啊,下面我就来介绍下如何实现这个功能,废话不多说先上图。这里我就使用了两个很简单的布局。
具体实现
本着能简单就简单的原则,在开始设计这个菜单之前,我们想一下,在android中,有什么视图是可以横向滑动的,答案就是HorizontalScrollView,在我们平常的布局中,我们只要使用HorizontalScrollView包裹一些视图,那么这些视图就可以开始滑动了,那么,我们这里可不可以继承这个类,然后自己修改下他的属性,变成自己需要的侧滑菜单呢,答案是肯定的,不然我这篇博客也不用写了。
上面啰嗦了那么多,现在就来介绍下原理,首先我们自定义一个类继承自HorizontalScrollView,这样我们的布局就拥有了HorizontalScrollView可以横向滚动的特性,由于HorizontalScrollView,只能有一个子View,所以在xml中,使用一个线性布局包裹了两个自布局,然后在onMeasure中获取这个线性布局,然后获取它的两个子布局,第一个当做侧滑菜单,第二个当做主布局,多余两个个也很好处理,可以直接在代码中删除掉多余的布局,也可以类似于HorizontalScrollView直接抛出异常,用于提示用户。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { LinearLayout ll = (LinearLayout) this.getChildAt(0); measureChildren(widthMeasureSpec, heightMeasureSpec); if (ll != null) { //获取两个布局 mSlidingView = ll.getChildAt(0); mContentView = ll.getChildAt(1); if (mSlidingView != null && mContentView != null) { //设置侧滑菜单的宽度:宽度为整个屏幕的宽度 - 和屏幕右边的宽度 mSlidingView.getLayoutParams().width = mSlidingMenuWidth; //设置主布局的宽度 : 为整个屏幕的宽度 mContentView.getLayoutParams().width = mScrWidth; } } setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); }
隐藏掉侧滑菜单
在HorizontalScrollView中有这么一个方法 : scrollTo(int xOffset,int yOffset),这个可以用于移动当前布局的位置,我们如果在一开始的时候就将布局往左边移动侧滑菜单布局的宽度,那不就能隐藏掉侧滑菜单了么?所以我们可以在onLayout中调用这个方法去隐藏(onLayout只调用一次)。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); this.scrollTo(mSlidingMenuWidth, 0); }
增加手势判断
在上面一步中,我们也只将侧滑菜单给隐藏掉了,可是用手指滑动以后,滑动到哪里就是哪里了,我们想让滑动以后,如果滑动的距离大于某一个值,自己滑动出来,小于某个值就自己收缩回去,该怎么实现呢?上面我们隐藏的时候调用了 scrollTo(int xOffset,int yOffset)这个方法去移动侧滑菜单,那么我们是不是只需要在手指抬起的时候,判断一下滑动的距离,然后再调用这个函数就可以实现移动了呢?
在下面的代码中我们可以看到,我们在手指抬起的时候通过getScrollX()来判断当前移动的距离,如果大于侧滑菜单的一半,那就调用this.smoothScrollTo()这个方法去完全打开菜单,小于则关闭,这里的smoothScrollTo和上文中的scrollTo类似,不同的是,smoothScrollTo会有移动的动画,平滑的打开和关闭菜单。
@Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_UP: if (this.getScrollX() > mHalfMenuWidth) { this.smoothScrollTo(mSlidingMenuWidth, 0); } else { this.smoothScrollTo(0, 0); } return true; } return super.onTouchEvent(ev); }
完整代码
下面我贴出目前为止"完整"的代码。这里之所以打上引号,是因为还有很多优化的地方,这里我们也只是非常基础的给系统的HorizontalScrollView增加了一个手势判断和一开始隐藏的功能而已,是不是觉得很简单呢?不过这样可不够,不信我们看看最后的效果。
package com.jay.slidingmenu; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; public class SlidingMenu extends HorizontalScrollView { /** * 屏幕宽 */ private int mScrWidth; /** * 侧滑菜单宽 */ private int mSlidingMenuWidth; /** * 侧滑view */ private View mSlidingView; /** * 主界面View */ private View mContentView; /** * 滑动的阈值,设置为了一半菜单宽,当大于这个值,打开菜单,小于这个值则关闭 */ private int mHalfMenuWidth; /** * 侧滑菜单滑出的最大距离后和屏幕右边的距离,默认为150dp */ private int mRightPadding = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 150, getResources() .getDisplayMetrics()); public SlidingMenu(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SlidingMenu(Context context) { this(context, null); } public SlidingMenu(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyle, 0); int count = array.getIndexCount(); for (int i = 0; i < count; i++) { switch (array.getIndex(i)) { case R.styleable.SlidingMenu_rightPadding: mRightPadding = (int) array.getDimension(i, 50f); break; } } array.recycle(); DisplayMetrics metrics = this.getResources().getDisplayMetrics(); mScrWidth = metrics.widthPixels; mHalfMenuWidth = (mScrWidth - mRightPadding) / 2; mSlidingMenuWidth = mScrWidth - mRightPadding; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { LinearLayout ll = (LinearLayout) this.getChildAt(0); measureChildren(widthMeasureSpec, heightMeasureSpec); if (ll != null) { // 获取两个布局 mSlidingView = ll.getChildAt(0); mContentView = ll.getChildAt(1); if (mSlidingView != null && mContentView != null) { // 设置侧滑菜单的宽度:宽度为整个屏幕的宽度 - 和屏幕右边的宽度 mSlidingView.getLayoutParams().width = mSlidingMenuWidth; // 设置主布局的宽度 : 为整个屏幕的宽度 mContentView.getLayoutParams().width = mScrWidth; } } setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); this.scrollTo(mSlidingMenuWidth, 0); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_UP: if (this.getScrollX() > mHalfMenuWidth) { this.smoothScrollTo(mSlidingMenuWidth, 0); } else { this.smoothScrollTo(0, 0); } return true; } return super.onTouchEvent(ev); } }
可以看到,我们的侧滑菜单是跟着主布局一起出来的,而不是“在主布局的下面”,这也是HorizontalScrollView的特性决定的,不过我们可以在滑动的过程中,动态调整侧滑菜单的位置,让他看起来“在主布局的下面”。
@Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); mSlidingView.setTranslationX(l); }
很简单的一句话,当视图移动的时候,当前视图往右边移动了多少,我们就把侧滑菜单的视图跟着往右边移动多少,这样看起来,就可以实现博客一开头的效果了。我们还可以实现跟更多的动画,这里就不在扩展了。具体可以查看下面的源码。
PS:由于本人技术有限,如果有遗漏或者错误,欢迎留言指出。
源码:360云盘下载 访问密码 4e3e