对于整个触摸事件传递过程,我画了简要的流程图,方便日后快速回顾。
单点触摸,没有考虑边缘滑动检测的最简流程图
单点触摸,考虑了边缘滑动检测的流程图
多点触摸情况我就没研究了,在这里忽略~
三个开启自动滚动的方法:
settleCapturedViewAt(int finalLeft, int finalTop)以松手前的滑动速度为初速动,让捕获到的View自动滚动到指定位置。只能在Callback的onViewReleased()中调用。
flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)以松手前的滑动速度为初速动,让捕获到的View在指定范围内fling。只能在Callback的onViewReleased()中调用。
smoothSlideViewTo(View child, int finalLeft, int finalTop)指定某个View自动滚动到指定的位置,初速度为0,可在任何地方调用。
Callback的各个方法总结:
void onViewDragStateChanged(int state)拖动状态改变时会调用此方法,状态state有STATE_IDLE、STATE_DRAGGING、STATE_SETTLING三种取值。它在setDragState()里被调用,而setDragState()被调用的地方有tryCaptureViewForDrag()成功捕获到子View时shouldInterceptTouchEvent()的ACTION_DOWN部分捕获到
shouldInterceptTouchEvent()的ACTION_MOVE部分捕获到
processTouchEvent()的ACTION_MOVE部分捕获到
调用settleCapturedViewAt()、smoothSlideViewTo()、flingCapturedView()时
拖动View松手时(processTouchEvent()的ACTION_UP、ACTION_CANCEL)
自动滚动停止时(continueSettling()里检测到滚动结束时)
外部调用abort()时
void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)正在被拖动的View或者自动滚动的View的位置改变时会调用此方法。在dragTo()里被调用(正在被拖动时)
在continueSettling()里被调用(自动滚动时)
外部调用abort()时被调用
void onViewCaptured(View capturedChild, int activePointerId)tryCaptureViewForDrag()成功捕获到子View时会调用此方法。在shouldInterceptTouchEvent()的ACTION_DOWN里成功捕获
在shouldInterceptTouchEvent()的ACTION_MOVE里成功捕获
在processTouchEvent()的ACTION_MOVE里成功捕获
手动调用captureChildView()
void onViewReleased(View releasedChild, float xvel, float yvel)拖动View松手时(processTouchEvent()的ACTION_UP)或被父View拦截事件时(processTouchEvent()的ACTION_CANCEL)会调用此方法。
void onEdgeTouched(int edgeFlags, int pointerId)ACTION_DOWN或ACTION_POINTER_DOWN事件发生时如果触摸到监听的边缘会调用此方法。edgeFlags的取值为EDGE_LEFT、EDGE_TOP、EDGE_RIGHT、EDGE_BOTTOM的组合。
boolean onEdgeLock(int edgeFlags)返回true表示锁定edgeFlags对应的边缘,锁定后的那些边缘就不会在onEdgeDragStarted()被通知了,默认返回false不锁定给定的边缘,edgeFlags的取值为EDGE_LEFT、EDGE_TOP、EDGE_RIGHT、EDGE_BOTTOM其中之一。
void onEdgeDragStarted(int edgeFlags, int pointerId)ACTION_MOVE事件发生时,检测到开始在某些边缘有拖动的手势,也没有锁定边缘,会调用此方法。edgeFlags取值为EDGE_LEFT、EDGE_TOP、EDGE_RIGHT、EDGE_BOTTOM的组合。可在此手动调用captureChildView()触发从边缘拖动子View的效果。
int getOrderedChildIndex(int index)在寻找当前触摸点下的子View时会调用此方法,寻找到的View会提供给tryCaptureViewForDrag()来尝试捕获。如果需要改变子View的遍历查询顺序可改写此方法,例如让下层的View优先于上层的View被选中。
int getViewHorizontalDragRange(View child)、int getViewVerticalDragRange(View child)返回给定的child在相应的方向上可以被拖动的最远距离,默认返回0。ACTION_DOWN发生时,若触摸点处的child消费了事件,并且想要在某个方向上可以被拖动,就要在对应方法里返回大于0的数。被调用的地方有三处:在checkTouchSlop()中被调用,返回值大于0才会去检查mTouchSlop。在ACTION_MOVE里调用tryCaptureViewForDrag()之前会调用checkTouchSlop()。如果checkTouchSlop()失败,就不会去捕获View了。
如果ACTION_DOWN发生时,触摸点处有子View消费事件,在shouldInterceptTouchEvent()的ACTION_MOVE里会被调用。如果两个方向上的range都是0(两个方法都返回0),就不会去捕获View了。
在调用smoothSlideViewTo()时被调用,用于计算自动滚动要滚动多长时间,这个时间计算出来后,如果超过最大值,最终时间就取最大值,所以不用担心在getView[Horizontal|Vertical]DragRange里返回了不合适的数导致计算的时间有问题,只要返回大于0的数就行了。
boolean tryCaptureView(View child, int pointerId)在tryCaptureViewForDrag()中被调用,返回true表示捕获给定的child。tryCaptureViewForDrag()被调用的地方有shouldInterceptTouchEvent()的ACTION_DOWN里
shouldInterceptTouchEvent()的ACTION_MOVE里
processTouchEvent()的ACTION_MOVE里
int clampViewPositionHorizontal(View child, int left, int dx)、int clampViewPositionVertical(View child, int top, int dy)child在某方向上被拖动时会调用对应方法,返回值是child移动过后的坐标位置,clampViewPositionHorizontal()返回child移动过后的left值,clampViewPositionVertical()返回child移动过后的top值。两个方法被调用的地方有两处:在dragTo()中被调用,dragTo()在processTouchEvent()的ACTION_MOVE里被调用。用来获取被拖动的View要移动到的位置。
如果ACTION_DOWN发生时,触摸点处有子View消费事件,在shouldInterceptTouchEvent()的ACTION_MOVE里会被调用。如果两个方向上返回的还是原来的left和top值,就不会去捕获View了。
案例参考
在这里列举一部分对ViewDragHelper的应用案例,大家自己剖析它们的源码来实践巩固。
YoutubeLayout,这是最简单的Demo
QQ5.x侧滑菜单、ResideLayout
SwipeBackLayout、SwipeBack
SlidingUpPanel
DrawerLayout
其他关于ViewDragHelper的分析文章
Each Navigation Drawer Hides a ViewDragHelper,文中的源码就是上面的YoutubeLayout
ViewDragHelper详解,这是上面文章的简略中文版