简单的侧滑菜单实现
Posted 哦...
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单的侧滑菜单实现相关的知识,希望对你有一定的参考价值。
本文参考自郭霖和鸿洋的多篇文章,无法逐一列举了
- 实现思路
利用自定义的HorizontalScrollView实现。 HorizontalScrollView中管理两个视图,一个视图为“菜单”,另一个为“正文”。初始时“菜单”部分在屏幕可视区域以外。当利用HorizontalScrollView的滑动机制进行水平滑动时,将隐藏在屏幕外的“菜单”部分“拖入”屏幕可视区域。 - 效果增强
以自定义的方式继承HorizontalScrollView并改写了其中的一些方法,可以在侧滑时增加一些滑动效果。比如滑动中“正文”的逐步缩小,“菜单”的逐步放大以及透明度的变化等。 实现步骤
- 初步示意图
初始时只会在屏幕中看见正文区域
随着向右“滑动”,“菜单”会出现在屏幕的可视区域内
- 代码实现
写一个自定义的View继承自HorizontalScrollView
MySlidingMenu extends HorizontalScrollView
然后利用自定义的MySlidingMenu构建MainActivity中的布局
<com.tarena.myviewtest.view.MySlidingMenu android:id="@+id/msm_main" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@mipmap/img_frame_background" app:menu_padding="100dp"> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal"> <include layout="@layout/left_menu_layout" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@mipmap/qq"> </LinearLayout> </LinearLayout> </com.tarena.myviewtest.view.MySlidingMenu>
其中left_menu_layout就是一个简单的LinearLayout,罗列了几个TextView形式的菜单项,正文区域是一幅图片
- 初步示意图
- 方法重写
为了实现初始时“菜单”部分在屏幕可视区域的左侧,需要重写MySlidingMenu的一些方法。这些方法包括:
onMeasure方法:在这里要确认一下菜单区域和正文区域的大小
onLayout方法:在这里完成对MySlidingMenu中内容的“摆放”。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
wrapper = (LinearLayout) this.getChildAt(0);
menu = wrapper.getChildAt(0);
content = wrapper.getChildAt(1);
menuWidth = screenWidth - menuRightPadding;
menu.getLayoutParams().width = menuWidth;
content.getLayoutParams().width = screenWidth;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
this.scrollTo(menuWidth, 0);
}
}
在onMeasure方法中screenWidth是屏幕的宽度:
DisplayMetrics outMetrics = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(outMetrics);
screenWidth = outMetrics.widthPixels;
menuRightPadding是“菜单”打开后,菜单距离屏幕右侧的距离。该值可以被当做一个自定义属性在布局文件中由使用者传入。默认值为80dp
TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.MySlidingMenu);
menuRightPadding = t.getDimensionPixelSize(R.styleable.MySlidingMenu_menu_padding,
(int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 80, getResources().getDisplayMetrics()));
t.recycle();
在确定了“菜单”部分的宽度后,在onLayout方法中调用scrollTo方法,该方法就是将MySlidingMenu的“内容区域”的左边缘移动到MySlidingMenu左边缘的左侧,距离恰好是“菜单”的宽度。这样就保证了“菜单”恰好位于屏幕可视区域之外。
此时就可以运行代码,实现“菜单”的侧滑了。
- 效果增强
基本效果实现了,接下来利用代码来实现效果的增强:
如果在滑动过程中松手,菜单已经显示的区域如果大于菜单尺寸的1/2则应该自动的完成菜单显示的后续滑动,反之则应该让菜单完成收起的后续动作。
上述逻辑写在MySlidingMenu的onTouchEvent中,因为HorizontalScrollView本身实现了一套onTouchEvent的逻辑,这里我们只针对MontionEvent的Action_Up动作即可:
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_UP) {
int sx = getScrollX();
if (sx >= menuWidth / 2) {
smoothScrollTo(menuWidth, 0);
isOpen = false;
} else {
smoothScrollTo(0, 0);
isOpen = true;
}
return true;
}
return super.onTouchEvent(ev);
}
这里在完成滑动时直接调用了HorizontalScrollView的smoothScrollTo方法,这样可以实现一个平滑的滑动过程,而不会像scrollTo那样瞬间完成滑动。
在上面的实现中,“菜单”是从左侧被“拖”出来的,接下来实现一种正文好像覆盖在“菜单”上面的感觉,先看示意图,对比一下两者的差异:
可以明显看出两者的效果差异吧。
要从“拖出”效果转为“覆盖”效果,仅仅需要一行代码:
重写一下父类的onScrollChanged方法即可:
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
ViewHelper.setTranslationX(menu, l);
super.onScrollChanged(l, t, oldl, oldt);
}
其中ViewHelper是nineoldandroids.jar中的一个类,nineoldandroids不用过多介绍了,它是一个兼容3.0以下版本实现属性动画的一个安卓类库。setTranslationX的意思就是设定“菜单”这个视图的translationX的值。方法中将menu视图的translationX的值设定为了onScrollChanged方法的第一个参数值。这是因为:
当MySlidingMenu的scrollTo方法被调用的时候(scrollBy和smoothScrollTo方法内部也是在调用scrollTo方法),scrollTo方法内部做的事情就是设定好滑动终止时的目标scrollX和scrollY的值以及滑动开始时的scrollX与scrollY的值,然后调用onScrollChanged方法,将这四个值作为onScrollChanged方法的四个参数,因此l参数的意思实际就是当滑动结束时“菜单”视图的scrollX的值。
MySlidingMenu的本身的视图宽度为屏幕的宽度,而MySlidingMenu中管理的内容是“菜单”视图+“正文”视图,内容的宽度是大于MySlidingMenu本身的宽度的,scrollX代表的就是MySlidingMenu现实的内容左边缘与MySlidingMenu本身左边缘的距离。
如果此时不做任何额外的操作,那么“菜单”视图停留在MySlidingMenu左侧边缘的左侧,距离MySlidingMenu左侧边缘scrollX个像素。现在希望它能够出现在可视区域中,也就是让“菜单”视图的左边缘恰好出现在MySlidingMenu的左边缘,那就需要让“菜单”视图从当前自己的左边缘开始再多滑动scrollX个像素的距离。如何滑动呢,就设定“菜单”视图的translationX属性值即可,translationX属性值代表让视图产生移动,视图移动后左侧边缘与视图left属性之间的距离(这部分内容的详情可以参考我的另一篇blogView的坐标系)。所以只要设定“菜单”视图的translationX属性值为滑动结束后的scrollX的值即可,这样每次滑动结束后,利用translationX再让“菜单”视图多滑动scrollX个像素值。这样就实现了每次滑动时,“菜单”视图始终都是左侧与MySlidingMenu的左侧保持一致的效果。
接下来再增加一些滑动中的效果。比如随着滑动,让“内容”区域产生一个由大到小的变化,让“菜单”区域产生一个由小到大的变化,并伴随一个透明度的变化:
这一切的发生都是随着滑动而发生的,因此这里需要找到一个随着滑动而不断变化的比例值:
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
float scale = l*1.0f/menuWidth;
ViewHelper.setTranslationX(menu, l);
super.onScrollChanged(l, t, oldl, oldt);
}
随着滑动l的值会不断发生变化,但是“菜单”的宽度是在onMeasure方法中设定好的,并不会变化,而滑动的整体变化过程或者说l的取值范围就是从0到menuWidth。有了这个scale后,就可以利用它实现各种滑动过程中的变化了:
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
//随着滑动,l的值不断变化(从menuWidth到0)。将绝对值值转化为一个比值
//scale的变化区间是1--->0
float scale = l*1.0f/menuWidth;
ViewHelper.setTranslationX(menu, l);
//随着拖动,content区域由大到小从100%--->70%左右
ViewHelper.setScaleX(content,0.7f+0.3f*scale);
ViewHelper.setScaleY(content,0.7f+0.3f*scale);
ViewHelper.setPivotX(content,0);
ViewHelper.setPivotY(content,content.getHeight()/2);
//随着拖动,菜单区域的透明度在变化,变化区间大概是0.7--->1
ViewHelper.setAlpha(menu,0.5f+0.5f*(1-scale));
//随着拖动,菜单区域的大小在变化变化区间大概是0.7--->1
ViewHelper.setScaleX(menu,0.7f+0.3f*(1-scale));
ViewHelper.setScaleY(menu,0.7f+0.3f*(1-scale));
super.onScrollChanged(l, t, oldl, oldt);
}
代码运行后的效果如图所示:
还可以再增加一点效果。此时的效果是随着拖动“菜单”视图恰好每次都是出现在MySlidingMenu的左边缘。如果每次不让“菜单”视图都移动scrollX的距离,而是移动的少一点,这样在MySlidingMenu的滑动过程除了上述已经有的效果外,还可以再带着一点“菜单”平滑拖出的效果。
只需要修改setTranslationX部分即可,原先移动l,现在让l乘以一个比例值,就可以不移动scrollX了:
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
//随着滑动,l的值不断变化(从menuWidth到0)。将绝对值值转化为一个比值
//scale的变化区间是1--->0
float scale = l*1.0f/menuWidth;
//ViewHelper.setTranslationX(menu, l);
ViewHelper.setTranslationX(menu, l*0.7f);
//随着拖动,content区域由大到小
//但是并不是到0,大概是从100%--->70%左右
ViewHelper.setScaleX(content,0.7f+0.3f*scale);
ViewHelper.setScaleY(content,0.7f+0.3f*scale);
ViewHelper.setPivotX(content,0);
ViewHelper.setPivotY(content,content.getHeight()/2);
//随着拖动,菜单区域的透明度在变化
//变化区间大概是0.7--->1
ViewHelper.setAlpha(menu,0.5f+0.5f*(1-scale));
//随着拖动,菜单区域的大小在变化
//变化区间大概是0.7--->1
ViewHelper.setScaleX(menu,0.7f+0.3f*(1-scale));
ViewHelper.setScaleY(menu,0.7f+0.3f*(1-scale));
super.onScrollChanged(l, t, oldl, oldt);
}
看一下这次的运行效果:
可以看到“菜单”区域除了透明度、大小变化之外,还有了侧拉效果。
该策滑菜单的基本实现需要掌握自定义View的基本知识,以及scrollTo的用法。在效果增强部分,需要先理解View中的几个坐标点的差异包括left,scrollX,translationX等,这样才能逐步实现效果。
以上是关于简单的侧滑菜单实现的主要内容,如果未能解决你的问题,请参考以下文章
Android 使用DrawerLayout快速实现侧滑菜单
Android 使用DrawerLayout快速实现侧滑菜单
Android 使用DrawerLayout快速实现侧滑菜单