标签:
本文参考自郭霖和鸿洋的多篇文章,无法逐一列举了
实现步骤
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形式的菜单项,正文区域是一幅图片
@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左边缘的左侧,距离恰好是“菜单”的宽度。这样就保证了“菜单”恰好位于屏幕可视区域之外。
此时就可以运行代码,实现“菜单”的侧滑了。
@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等,这样才能逐步实现效果。
标签:
原文地址:http://blog.csdn.net/piglite/article/details/52351806