本文使用ViewDragHelper实现类qq侧滑菜单栏的效果,同时也简单介绍了DrawerLayout和SlidingPanLayout实现侧滑效果的方法。
ViewDragHelper
Google在其support库中为我们提供了DrawerLayout和SlidingPaneLayout两个布局来实现侧滑效果,两个强大的布局背后是通过ViewDragHelper来实现的,ViewDragHelper基本可以实现各种不同的滑动,拖放,这个方法是各种滑动方案中的终极绝招。
类QQ侧滑菜单栏的效果图:
ViewDragHelper的使用步骤:
1. 使用静态工厂方法初始化ViewDragHelper
2. 拦截事件,通过onInterceptTouchEcent将事件传递给ViewDragHelper进行处理
3. 重写computeScroll()通过Scroller类来实现平滑移动
4.callback回调事件
使用静态工厂初始化ViewDragHelper
mViewDragHelper = ViewDragHelper.create(this, callback);
//另一种写法
mViewDragHelper = ViewDragHelper.create(this, 1.0f, callback);
其中1.0f是敏感度参数参数越大越敏感。(官方文档中指明 1.0f 是默认值)。第一个参数为this,表示该类生成的对象,他是ViewDragHelper的拖动处理对象,必须为ViewGroup。
将事件传递给ViewDragHelper来处理
//拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//将触摸事件传递给ViewDragHelper
mViewDragHelper.processTouchEvent(event);
return true;
}
重写computeScroll(),使用continueSettling来判断移动是否还在继续,如果还在移动,我们就调用postInvalidateOnAnimation()方法进行刷新
@Override
public void computeScroll() {
super.computeScroll();
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
我们要实现侧滑效果,我们首先要获取到子View,然后再让子View做相应的位置改变。这里我们布局的设置是:MainView覆盖MenuView,让MenuView固定,MainView滑动来显示出MenuView。
//获取子布局
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}
tryCaptureView方法是用来决定哪些view是可以移动的,这里我们让子View 中的mMainView移动,所以返回 child == mMainView; 如果直接返回true,那么所有的子View都是可以移动的。
clampViewPositionHorizontal返回一个适当的数值就能实现横向拖动效果,默认为0。第二个参数是指当前拖动子view应该到达的x坐标。所以按照常理这个方法原封返回第二个参数就可以了,为了让被拖动的view遇到边界之后就不再拖动,可以对返回的值做更多的考虑。
clampViewPositionVertical是指竖直方向
onViewReleased用来实现手指离开屏幕后的操作。在这里我们进行判断,当滑动的位置大于500,打开菜单栏,即MainView移动到固定的位置,将MenuView显示出来。否则,不显示。注意使用 ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);进行刷新。
ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
//主要对mMainView来进行触摸移动
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mMainView;
}
//水平滑动
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
//实现手指离开屏幕后的操作
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (mMainView.getLeft() > 500) {//当滑动的距离超过500,打开菜单
mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
//刷新
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
else {
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
};
完整代码:
public class DragViewGroup extends FrameLayout {
private View mMainView;
private View mMenuView;
private ViewDragHelper mViewDragHelper;
public DragViewGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//使用静态工厂方法初始化ViewDragHelper
mViewDragHelper = ViewDragHelper.create(this, callback);
}
//callback回调
ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
//主要对mMainView来进行触摸移动
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mMainView;
}
//水平滑动
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
//实现手指离开屏幕后的操作
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (mMainView.getLeft() > 500) {//当滑动的距离超过500,打开菜单
mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
//刷新
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
else {
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
};
//获取子布局
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}
//拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//将触摸事件传递给ViewDragHelper
mViewDragHelper.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
super.computeScroll();
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}
布局代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<example.com.myapplication2.DragViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="menu"
/>
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="main"
/>
</FrameLayout>
</example.com.myapplication2.DragViewGroup>
</RelativeLayout>
探究
1.我们将上述代码中下面这段代码删除,即不做view移动后的处理,那么MainView就会跟随手指移动。
//实现手指离开屏幕后的操作
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (mMainView.getLeft() > 500) {//当滑动的距离超过500,打开菜单
mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
//刷新
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
else {
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
2.在原代码的基础上,添加竖直滑动,那么从竖直方向我们也可以滑动菜单栏
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
对手指离开屏幕后的操作进行修改
//实现手指离开屏幕后的操作
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (mMainView.getLeft() > 500) {//当滑动的距离超过500,打开菜单
mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
//刷新
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
else if (mMainView.getTop() > 500) {
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 300);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
} else {
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
};
3.我们将tryCaptureView返回true,即让所有view都能滑动,再添加上水平方向的滑动,竖直方向上的滑动,把onViewReleased删除,那么我们就可以实现所有view跟随手指移动。
//callback回调
ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
//水平滑动
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
//竖直滑动
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
};
DrawerLayout
DrawerLayout只是一个布局,在这个布局中允许放置两个空间,第一个控件是主屏幕的内容,第二个子控件是滑动菜单栏的内容。我们只需要对activity_main做修改,就可以实现滑动效果,注意滑动菜单栏要设置背景颜色,否则默认颜色和阴影相似,导致无法区分。layout_gravity这个属性是必须要指明的,在这里指定为start,表示会根据系统语言进行判断,如果系统语言是从左往右,滑动菜单栏就在左边,如英语,汉语。如果系统语言从右往左,如阿拉伯语,滑动菜单栏在右边。
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"
android:text="This is Menu"
android:gravity="center"
android:textSize="30dp"
android:layout_gravity="start"
/>
</android.support.v4.widget.DrawerLayout>
SlidingPaneLayout
SlidingPaneLayout也是布局,只需要在布局中设置好相应的布局就可以实现侧滑的效果。
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:text="this is Menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="30dp"
android:background="@color/colorAccent"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:text="Hello world"
android:gravity="center"
android:textSize="30dp"
/>
</android.support.v4.widget.SlidingPaneLayout>