本文基本参考自http://blog.csdn.net/lmj623565791/article/details/39257409 ,添加了一些自己的思考和注释
先看最后的实现效果
左右滑动可以展开关闭菜单,菜单展开超过一半后抬起手指也会自动完成剩余的滑动,反之亦然。
我们用一个继承自HorizontalScrollView的自定义ViewGroup去实现,好处就是HorizontalScrollView本身就实现了水平方向可滚动的功能。
先来看一下布局文件
<com.example.yin.myscrollview.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:scrollbars="none"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:background="#B3E5FC">
<include layout="@layout/menu_layout"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0288D1">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="menu"/>
</LinearLayout>
</LinearLayout>
</com.example.yin.myscrollview.MyScrollView>
ScrollView中只能放一个布局,所以我们先直接放了一个LinearLayout,在这个LinearLayout里面水平方向排列我们的内容,首先include一个菜单布局放在左边。右边的LinearLayout里放我们的内容,这里只是简单地设置了一下背景色,添加了一个按钮可以用来控制打开或关闭menu。
那么我们来看一下这个ViewGroup到底是怎么实现的
public class MyScrollView extends HorizontalScrollView implements View.OnClickListener {
private int screenWidth;
private int menuWidth;
private LinearLayout linearLayout;
private ViewGroup menu;
private ViewGroup content;
private boolean isOnce = true;
private boolean isMenuShow = false;
public MyScrollView(Context context) {
this(context, null);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
/*获取屏幕宽度*/
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
screenWidth = size.x;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/*避免多次加载设置*/
if (isOnce) {
linearLayout = (LinearLayout) getChildAt(0);
menu = (ViewGroup) linearLayout.getChildAt(0);
content = (ViewGroup) linearLayout.getChildAt(1);
/*设置menu菜单的宽度为3/4的屏幕宽度,content宽度占满屏幕*/
menuWidth = (int) (3.0/4 *screenWidth);
menu.getLayoutParams().width = menuWidth;
content.getLayoutParams().width = screenWidth;
/*点击后展开或隐藏menu的按钮*/
Button button = (Button) linearLayout.findViewById(R.id.button);
button.setOnClickListener(this);
isOnce = false;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
/*同样避免多次加载*/
if (changed) {
/*初始化滚动条位置为menu的宽度,menu就会在屏幕外的左边,达到隐藏效果*/
this.scrollTo(menuWidth, 0);
}
}
/*如果不实现这个方法,菜单出现形式与ViewPager类似*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
/*l表示滚动条的位置,由于我们之前的设置,一开始的l就是menu的宽度大小。
当向右滑动时滚动条左移,左边隐藏的menu完全显示时,l变为0。所以ratio是一个0~1之间的数*/
float ratio = 1.0f * l / menuWidth;
float contentScale = (float) (1 - 0.3 * (1 - ratio));
float contentAlpha = (float) (1 - 0.3 * (1 - ratio));
float menuScale = (float) (0.7 + 0.3 * (1 - ratio));
float menuAlpha = (float) (0.7 + 0.3 * (1 - ratio));
menu.setScaleX(menuScale);
menu.setScaleY(menuScale);
menu.setAlpha(menuAlpha);
content.setAlpha(contentAlpha);
content.setPivotX(0);
content.setPivotY(content.getHeight() / 2);
content.setScaleX(contentScale);
content.setScaleY(contentScale);
/*menu的x方向偏移量随着改变,达到抽屉式菜单的效果*/
menu.setTranslationX((float) (l * 0.7));
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
/*滚动条位置大于menu宽度一半(屏幕左侧隐藏部分大于menu宽度一半),menu自动向左滑动全部隐藏*/
int scrollX = getScrollX();
if (scrollX >= menuWidth / 2) {
this.smoothScrollTo(menuWidth, 0);
isMenuShow = false;
} else {
this.smoothScrollTo(0, 0);
isMenuShow = true;
}
return true;
}
return super.onTouchEvent(ev);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
if (isMenuShow) {
this.smoothScrollTo(menuWidth, 0);
isMenuShow = false;
} else {
this.smoothScrollTo(0, 0);
isMenuShow = true;
}
break;
}
}
}
代码还是比较简单的,常规的onMeasure,onLayout以及事件的监听。注释已经写得很清楚了。
注意到我们还实现了一个ScrollView特有的onScrollChanged()方法,如果不在这个方法里写我们的代码的话就是上面的侧滑菜单效果。加了这部分代码后就变成了抽屉式展开菜单的效果,如下图。实现的逻辑用文字比较难表述,大家自己想一下。
最后还有一个问题就是博主想实现当拖动的速度达到一定值后也能让菜单自动展开和关闭的效果,而不是非要移动距离大于菜单宽度一半。事实上QQ等大多数app也是这么做的。考虑用手势监听器gesturedetector去实现发现如果把teachEvent事件交给gesturedetector处理,原先最基本的ScrollView的滑动也就没办法执行到了,希望有朋友实现后分享交流。
另外,用类似的方法实现两边菜单的方法请看 http://blog.csdn.net/lmj623565791/article/details/39670935 鸿洋大神的这篇文章。后半部分实现两边抽屉式菜单的实现方法简直服了。