概述
ViewDragHelper
是 Android
中专为手势处理的类,用在自定义ViewGroup
中将大大简化我们的工作.
其实在Android
中还有一个类GestureDetector
也是相同的作用.
相关概念
ViewDragHelper.Callback
是一个回调处理类,是连接ViewDragHelper
和ViewGroup
的桥梁ViewDragHelper
的本质其实是分析onInterceptTouchEvent
和onTouchEvent
的MotionEvent
参数,之后通过offsetTopAndBottom(int offset)
和offsetLeftAndRight(int offset)
方法来改变View
的位置.
一. create
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb)
参数说明:
forParent
: 当前的自定义ViewGroup
sensitivity
: 敏感的参数,设置值越大,touchSlop
值就越小.
作用位置:
helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
cb
: 各种事件回调,可以在里面处理子View
的各种事件
二. 事件拦截
ViewDragHelper
既然将我们的事件接管了,在自定义ViewGroup
的时候,需要复写事件处理的方法.
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//将拦截事件交个mDragHelper来处理,看是否需要拦截
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//通过mDragHelper来分析事件,返回true将 down 后的一些列事件都交个helper来处理
mDragHelper.processTouchEvent(event);
return true;
}
三. View的滑动
//是否拦截相关`View`事件,其中的 `child`参数就是发生事件的`View`
@Override public boolean tryCaptureView(View child, int pointerId) {
return true;
}
// 水平方向的滑动,left表示x轴坐标
@Override public int clampViewPositionHorizontal(View child, int left, int dx) {
//这里过滤掉边界
int leftBound = getPaddingLeft();
int rightBound = getWidth() - child.getWidth() - leftBound;
left = Math.min(Math.max(left, leftBound), rightBound);
return left;
}
// 垂直方向上的滑动,top表示y轴坐标
@Override public int clampViewPositionVertical(View child, int top, int dy) {
// 过滤掉边界
int topBound = getPaddingTop();
int bottomBound = getHeight() - child.getHeight() - topBound;
top = Math.min(Math.max(top, topBound), bottomBound);
return top;
}
通过重写上面的CallBack
方法,我们自定义的ViewGroup
内的子View
就可以在布局内来回拖动了.
四.拖动方向(setEdgeTrackingEnabled)
如果我们自定义一个侧边栏.那么这时候就需要可以从左边或者右边才能拉出,可以通过如下API
/**
* Enable edge tracking for the selected edges of the parent view.
* The callback's {@link Callback#onEdgeTouched(int, int)} and
* {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked
* for edges for which edge tracking has been enabled.
*
* @param edgeFlags Combination of edge flags describing the edges to watch
* @see #EDGE_LEFT
* @see #EDGE_TOP
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void setEdgeTrackingEnabled(int edgeFlags) {
mTrackingEdges = edgeFlags;
}
这是ViewDrugHelper
的方法,可以看出其是用一个常量来表示拉动的方向.
通常情况下,我们在设置了如上的值后,还需要复写CallBack
的相关方法才能生效.
//边界拖动时回调,这里先判断拖动的是否是左边界,之后拦截相关View的事件
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
if(edgeFlags==ViewDragHelper.EDGE_LEFT){
mDragHelper.captureChildView(mDragView, pointerId);
}
}
可以看到,这里并不需要再CallBack
的tryCaptureView
中拦截相关View
的事件.
五.释放回调(onViewReleased)
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//mAutoBackView手指释放时当前child回到什么地方去
if (releasedChild == mDragView) {
mDragHelper.settleCapturedViewAt( finalLeft, finalTop);
invalidate();
}
}
如果是侧滑菜单,一般当我们拉出超过一半松手会全部拉出,否则就会回去.就可以在这里实现.
在这里内部其实是通过Scroller
实现的,因此我们还需要复写一些computeScroll
方法
@Override
public void computeScroll() {
//判断当前截获的子view滑动是否完成
if (mDragHelper.continueSettling(true)) {
invalidate();
}
}
六.移动范围
@Override
public int getViewHorizontalDragRange(View child)
{
//横向的移动范围
return getMeasuredWidth()-child.getMeasuredWidth();
}
@Override
public int getViewVerticalDragRange(View child)
{
//纵向的移动范围
return getMeasuredHeight()-child.getMeasuredHeight();
}
如果子View是可以消耗事件的,则需要复写上面的方法,因为在shouldInterceptTouchEvent
方法的MOVE
中会通过此判断后才拦截tryCaptureView
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
//...
switch (action) {
//...
case MotionEvent.ACTION_MOVE: {
//...
for (int i = 0; i < pointerCount; i++) {
//...
if (pastSlop) {
//...
final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
toCapture);
final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
if ((horizontalDragRange == 0 || horizontalDragRange > 0
&& newLeft == oldLeft) && (verticalDragRange == 0
|| verticalDragRange > 0 && newTop == oldTop)) {
break;
}
}
//...
}
saveLastMotion(ev);
break;
}
//...
}
从上面源码中可以看出,如果滑动范围为0,则直接跳出循环.
七. 其他API
- onViewDragStateChanged: 当状态发生变化时回调[IDLE,DRAGGING,SETTING]
- onViewPositionChanged : 当
captureview
的位置发生改变时回调 - onViewCaptured : 当
captureview
被捕获时回调 - onEdgeTouched : 当触摸到边界时回调
- onEdgeLock :
true
的时候会锁住当前的边界,false
则unLock
- getOrderedChildIndex : 改变同一个坐标(x,y)去寻找captureView位置的方法.
参考:Android ViewDragHelper完全解析 自定义ViewGroup神器
扩展阅读:
Android ViewDragHelper实例