1.Google在其support库中为我们提供了DrawerLayout和SlidingPaneLayout两个布局来帮助开发者实现侧边栏滑动的效果。但是在这两个布局的背后,却隐藏着一个功能强大的类–ViewDragHelper。通过ViewDragHelper可以实现各种不同的滑动,拖放。
2.下面我们来创建一个类似于QQ滑动侧边栏的布局,初始时显示内容界面,当用户手指滑动超过一段距离时,内容界面侧滑显示菜单界面。
1.初始化ViewDragHelper
ViewDragHelper通常定义在一个viewGroup的内部,并且通过其静态工厂方法进行初始化。
private void initView() {
viewDragHelper = ViewDragHelper.create(this, callBack);
}
public static ViewDragHelper create(ViewGroup forParent, Callback cb) 第一个参数实惠要监听的参数view。通常需要的是一个viewgroup。即parentView,第二个参数是一个callBack回调,这个回调就是整个ViewDragHelper的核心逻辑。
2.拦截事件
接下来就是要重写事件拦截方法,将事件传递给ViewDragHelper进行处理。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// shouldInterceptTouchEvent()表示的的就是希望自身消耗掉该事件
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 将触摸事件传递给ViewDragHelper
viewDragHelper.processTouchEvent(event);
return true;
}
3.处理computeScroll()
因为ViewDragHelper的内部同样是通过scroller来实现平滑移动的,所以也要实现该方法。
@Override
public void computeScroll() {
if (viewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
4处理callBack回调
通过如下代码来实现ViewDragHelper.CallBack。
private ViewDragHelper.Callback callBack = new ViewDragHelper.Callback(){
// 什么时候开始检测触摸事件
@Override
public boolean tryCaptureView(View child, int pointerId) {
return false;
}
我们重写了tryCaptureView()方法,通过这个方法,我们可以指定爱创建ViewDragHelper时,参数parentView中哪一个子view可以被移动。例如在本例子中我们定义了一个viewGroup,里面定义了两个子view—menuView和mainView,当指定如下的代码时,则只有mainView是可以拖动的。
private ViewDragHelper.Callback callBack = new ViewDragHelper.Callback(){
// 什么时候开始检测触摸事件
@Override
public boolean tryCaptureView(View child, int pointerId) {
//return false;
// 如果当触摸的child是manView时开始检测
return mainView == child;
}
5具体的滑动方法
public int clampViewPositionHorizontal(View child, int left, int dx)和public int clampViewPositionVertical(View child, int top, int dy)分别对应的是垂直和水平方向上的滑动。如果要实现效果,这两个方法是必须要重写的。默认的是不滑动的,默认值为0,实现两个方法中的一个即可实现滑动的效果。
// 处理水平滑动的时候调用
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
// 处理垂直滑动的时候调用
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
public int clampViewPositionHorizontal(View child, int left, int dx)中的参数left,表示的是向左滑动的距离,dy表示的是增量。 public int clampViewPositionVertical(View child, int top, int dy)中也是类似的处理。通常情况下,只需要返回top和left即可,当需要更加精细的计算padding等属性的时候,需要对left进行一些处理,并且返回一些合适大小的值。
通过以上的的重写的三个方法就可以实现最基本的滑动效果了,当用手拖动mainView的时候就可以跟随手指的滑动而滑动了。
6.代码的优化
scroller类张总有这样的一个效果,当手指离开屏幕的时候,子View滑动回到初始的位置。当时我们使用的是监听ACTION_UP事件,并且调用Scroller类来实现的。这里我么可以使用callBack中的回调方法—public void onViewReleased(View releasedChild, float xvel, float yvel),可以重写这个方法,非常简单的实现当手指离开屏幕后实现的操作。这个方法的内部也是通过scroller类来实现的,这也是重写computeScroll()方法的原因。
// 手指离开屏幕的时候调用
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
// 手指离开屏幕后子View缓慢移动到指定的位置
if (mainView.getLeft() < 500) {
// 关闭菜单
viewDragHelper.smoothSlideViewTo(mainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
} else {
// 打开菜单
viewDragHelper.smoothSlideViewTo(mainView, 400, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
调用smoothSlideViewTo()方法使得mainview移动后距左边小鱼500像素的时候就范元回原来的状态,即坐标的(0,0)点,当其左边距大于500的时候就显示menuView.
其实这两行代码和
scroller.startScroll(x,y.dx,dy);
invalidate();
一样的效果.
7.细节的完善
在viewgroup中的onFinishInflate()方法中按顺序将子view分别定义成menuView和mainView,并且在onSizeChanged()方法中获取到view的宽度,如果徐亚根据view的宽度来处理滑动后的效果,可以用这个值来判断。
@Override
protected void onFinishInflate() {
super.onFinishInflate();
menuView = getChildAt(0);
mainView = getChildAt(1);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = menuView.getMeasuredWidth();
}
最终效果: