前言
在项目中,我们经常自定义ViewGroup,有时候需要拖拽它的子View,让其运动,一般情况下如果我们手动处理各种滑动事件,非常麻烦,谷歌给我们提供了一个辅助类ViewDragHelper,ViewDragHelper给我们提供了很多拖拽相关的方法以及状态跟踪。
创建实例
ViewDragHelper.create(vp, callback);
ViewDragHelper的创建比较简单,它的构造函数是私有的,只能通过create()方法创建,第一个参数是一个ViewGroup,也就是需要使用ViewDragHelper的自定义View,第二个参数callback,提供了很多拖拽相关的回调。
ViewDragHelper.Callback
下面是几个常用的方法
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() { public boolean tryCaptureView(View child, int pointerId) public int getViewHorizontalDragRange(View child) public int clampViewPositionHorizontal(View child, int left, int dx) public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) public void onViewReleased(View releasedChild, float xvel, float yvel)
- tryCaptureView返回一个boolean,用户判断是否捕获当前view的触摸事件
- getViewHorizontalDragRange获取child水平方向的拖拽范围
- clampViewPositionHorizontal控制child在水平方向的移动,我们可以通过dx修正left的值,返回值表示我们真正想让child的left变成的值
- clampViewPositionVertical和水平方向类似
- onViewPositionChanged 当child位置改变时候的回调
- onViewReleased当拖拽的view释放的时候回调
使用
自定义一个简单的侧拉菜单,该菜单有两个子View,一个主界面,一个侧边菜单界面
首先自定义一个ViewGroup并初始化ViewDragHelper
public class SlideMenu extends FrameLayout{ private void init(){ viewDragHelper = ViewDragHelper.create(this, callback); } }
然后重写onInterceptTouchEvent和onTouchEvent,将这两个方法的处理逻辑交给ViewDragHelper
public boolean onInterceptTouchEvent(MotionEvent ev) { return viewDragHelper.shouldInterceptTouchEvent(ev); } public boolean onTouchEvent(MotionEvent event) { viewDragHelper.processTouchEvent(event); return true; }
实现ViewDragHelper.Callback,重写tryCaptureView,在当前Layout中两个子VIew都需要滑动,所以直接返回true.
public boolean tryCaptureView(View child, int pointerId) { return true; }
先限制一下横向滑动范围,给一个最大值
public int getViewHorizontalDragRange(View child) { return (int) dragRange; }
主界面在滑动过程中,我们需要控制下它在水平方向的移动距离
public int clampViewPositionHorizontal(View child, int left, int dx) { if(child==mainView){ if(left<0)left=0;//限制mainView的左边 if(left>dragRange)left=(int) dragRange;//限制mainView的右边 } return left; }
在滑动过程中,根据拖拽回调重新对侧拉菜单和主界面布局,不断刷新他们的位置信息,这里简单起见,让侧拉菜单固定,只是主界面滑动。
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { if(changedView==menuView){ menuView.layout(0, 0, menuView.getMeasuredWidth(),menuView.getMeasuredHeight()); int newLeft = mainView.getLeft()+dx; if(newLeft<0)newLeft=0; if(newLeft>dragRange)newLeft=(int) dragRange;mainView.layout(newLeft,mainView.getTop()+dy,newLeft+mainView.getMeasuredWidth(),mainView.getBottom()+dy); } }
当手指抬起释放view的时候,可能我们只是拖拽了一点,这时候我们需要根据当前拖拽的信息决定是打开菜单还是关闭菜单。
public void onViewReleased(View releasedChild, float xvel, float yvel) { if(mainView.getLeft()<dragRange/2){ //在左半边 close(); }else { //在右半边 open(); } }
对于view的滑动ViewDragHelper也提供了smoothSlideViewTo方法,所以close和open方法就很简单
public void close() { viewDragHelper.smoothSlideViewTo(mainView,0,mainView.getTop()); ViewCompat.postInvalidateOnAnimation(SlideMenu.this); } public void computeScroll() { if(viewDragHelper.continueSettling(true)){ ViewCompat.postInvalidateOnAnimation(SlideMenu.this); } }
然后还可以提供一些拖拽状态回调,比如拖拽完成,拖拽中等状态,这些比较简单,直接在onViewPositionChanged中处理就可以了。
最后看一下效果