天天看点

Android -- ViewDragHelper

SlidingPaneLayout和DrawerLayout,现在这俩个类被广泛的运用,其实研究他们的源码你会发现这两个类都运用了ViewDragHelper来处理拖动。

ViewDragHelper并不是第一个用于分析手势处理的类,gesturedetector也是,但是在和拖动相关的手势分析方面gesturedetector只能说是勉为其难。

ViewDragHelper.Callback是连接ViewDragHelper与view之间的桥梁(这个view一般是指拥子view的容器即parentView)

ViewDragHelper可以检测到是否触及到边缘

ViewDragHelper并不是直接作用于要被拖动的View,而是使其控制的视图容器中的子View可以被拖动,如果要指定某个子view的行为,需要在Callback中想办法;

ViewDragHelper的本质其实是分析onInterceptTouchEvent和onTouchEvent的MotionEvent参数,然后根据分析的结果去改变一个容器中被拖动子View的位置( 通过offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 ),他能在触摸的时候判断当前拖动的是哪个子View;

虽然ViewDragHelper的实例方法 ViewDragHelper.create(ViewGroup forParent, Callback cb) 可以指定一个被ViewDragHelper处理拖动事件的对象 ,但ViewDragHelper类的设计决定了其适用于被包含在一个自定义ViewGroup之中,而不是对任意一个布局上的视图容器使用ViewDragHelper。

<code>onInterceptTouchEvent</code>中通过使用<code>mDragger.shouldInterceptTouchEvent(event)</code>来决定我们是否应该拦截当前的事件。<code>onTouchEvent</code>中通过<code>mDragger.processTouchEvent(event)</code>处理事件。

Android -- ViewDragHelper
Android -- ViewDragHelper

第一个View基本没做任何修改,就是演示简单的移动 。

第二个View,实现的是除了移动后,松手自动返回到原本的位置。(注意你拖动的越快,返回的越快)。我们在onLayout之后保存了最开启的位置信息,最主要还是重写了Callback中的<code>onViewReleased</code>,我们在onViewReleased中判断如果是mAutoBackView则调用<code>settleCapturedViewAt</code>回到初始的位置。大家可以看到紧随其后的代码是invalidate();因为其内部使用的是mScroller.startScroll,所以别忘了需要invalidate()以及结合<code>computeScroll</code>方法一起。

第三个View,实现的是边界移动时对View进行捕获。我们在<code>onEdgeDragStarted</code>回调方法中,主动通过<code>captureChildView</code>对其进行捕获,该方法可以绕过tryCaptureView,所以我们的tryCaptureView虽然并为返回true,但却不影响。注意如果需要使用边界检测需要添加上<code>mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);</code>。

将View全部加上clickable=true,意思就是子View可以消耗事件。再次运行,你会发现本来可以拖动的View不动了。原因是什么呢?主要是因为,如果子View不消耗事件,那么整个手势(DOWN-MOVE*-UP)都是直接进入onTouchEvent,在onTouchEvent的DOWN的时候就确定了captureView。如果消耗事件,那么就会先走<code>onInterceptTouchEvent</code>方法,判断是否可以捕获,而在判断的过程中会去判断另外两个回调的方法:<code>getViewHorizontalDragRange</code>和<code>getViewVerticalDragRange</code>,只有这两个方法返回大于0的值才能正常的捕获。

所以,记得重写下面这两个方法:

ViewDragHelper中拦截和处理事件时,需要会回调CallBack中的很多方法来决定一些事,比如:哪些子View可以移动、对个移动的View的边界的控制等等。

onViewDragStateChanged

当ViewDragHelper状态发生变化时回调(IDLE,DRAGGING,SETTING[自动滚动时])

onViewPositionChanged

当captureview的位置发生改变时回调

onViewCaptured

当captureview被捕获时回调

onViewReleased

当capture view被释放的时候

onEdgeTouched

当触摸到边界时回调。

onEdgeLock

true的时候会锁住当前的边界,false则unLock。

getOrderedChildIndex

改变同一个坐标(x,y)去寻找captureView位置的方法。(具体在:findTopChildUnder方法中)

ViewDragHelper重载了两个create()静态方法,先看两个参数的create()方法:

create()的两个参数很好理解,第一个是我们自定义的ViewGroup,第二个是控制子View拖拽需要的回调对象。create()直接调用了ViewDragHelper构造方法, 我们再来看看这个构造方法。

这个构造函数是私有的,也是仅有的构造函数,所以外部只能通过create()工厂方法来创建ViewDragHelper实例了。 这里要求了我们传递的自定义ViewGroup和回调对象不能为空,否则会直接抛出异常中断程序。在这里也初始化了一些触摸滑动需要的参考值和辅助类。

mParentView和mCallback分别保存传递过来的对应参数

ViewConfiguration类里定义了View相关的一系列时间、大小、距离等常量

mEdgeSize表示边缘触摸的范围。例如mEdgeSize为20dp并且用户注册监听了左侧边缘触摸时,触摸点的x坐标小于mParentView.getLeft() + mEdgeSize时(即触摸点在容器左边界往右20dp内)就算做是左侧的边缘触摸,详见ViewDragHelper的getEdgesTouched()方法。

mTouchSlop是一个很小的距离值,只有在前后两次触摸点的距离超过mTouchSlop 的值时,我们才把这两次触摸算作是“滑动”,我们只在此时进行滑动处理,否则任何微小的距离的变化我们都要处理的话会显得太频繁,如果处理过程又比较复杂耗时就会使界面产生卡顿。

mMaxVelocity、mMinVelocity是fling时的最大、最小速率,单位是像素每秒。

mScroller是View滚动的辅助类

再看三个参数的create()方法:

第二个参数sensitivity是用来调节mTouchSlop的值。sensitivity越大,mTouchSlop越小,对滑动的检测就越敏感。 例如sensitivity为1时,前后触摸点距离超过20dp才进行滑动处理,现在sensitivity为2的话,前后触摸点距离超过10dp就进行处理了。

当mParentView(自定义ViewGroup)被触摸时,首先会调用mParentView的onInterceptTouchEvent(MotionEvent ev), 接着就调用shouldInterceptTouchEvent(MotionEvent ev) ,所以先来看看这个方法的ACTION_DOWN部分:

看9~21行,首先是关于多点触控.mVelocityTracker记录下触摸的各个点信息,稍后可以用来计算本次滑动的速率,每次发生ACTION_DOWN事件都会调用cancel(), 而在cancel()方法里mVelocityTracker又被清空了,所以mVelocityTracker记录下的是本次ACTION_DOWN事件直至ACTION_UP事件发生后 (下次ACTION_DOWN事件发生前)的所有触摸点的信息。

再来看24~42行case MotionEvent.ACTION_DOWN部分,先是调用saveInitialMotion(x, y, pointerId)保存手势的初始信息,即ACTION_DOWN发生时的触摸点坐标(x、y)、 触摸手指编号(pointerId),如果触摸到了mParentView的边缘还会记录触摸的是哪个边缘。接着调用findTopChildUnder((int) x, (int) y); 来获取当前触摸点下最顶层的子View,看findTopChildUnder的源码:

代码很简单,注释里也说明的很清楚了。如果在同一个位置有两个子View重叠,想要让下层的子View被选中, 那么就要实现Callback里的getOrderedChildIndex(int index)方法来改变查找子View的顺序;例如topView(上层View)的index是4, bottomView(下层View)的index是3,按照正常的遍历查找方式(getOrderedChildIndex()默认直接返回index),会选择到topView, 要想让bottomView被选中就得这么写:

32~35行,这里还看到了一个mDragState成员变量,它共有三种取值:

STATE_IDLE:所有的View处于静止空闲状态

STATE_DRAGGING:某个View正在被用户拖动(用户正在与设备交互)

STATE_SETTLING:某个View正在安置状态中(用户并没有交互操作),就是自动滚动的过程中

mCapturedView默认为null,所以一开始不会执行这里的代码,mDragState处于STATE_SETTLING状态时才会执行tryCaptureViewForDrag(), 执行的情况到后面再分析.

37~40行调用了Callback.onEdgeTouched向外部通知mParentView的某些边缘被触摸到了,mInitialEdgesTouched是在刚才调用过的saveInitialMotion方法里进行赋值的。

ACTION_DOWN部分处理完了,跳过switch语句块,剩下的代码就只有return mDragState == STATE_DRAGGING;。在ACTION_DOWN部分没有对mDragState进行赋值,其默认值为STATE_IDLE,所以此处返回false。

那么返回false后接下来应该是会调用哪个方法呢,接下来会在mParentView的所有子View中寻找响应这个Touch事件的View(会调用每个子View 的dispatchTouchEvent()方法,dispatchTouchEvent里一般又会调用onTouchEvent()).

1.如果没有子View消费这次事件(子View的dispatchTouchEvent()返回都是false),会调用mParentView的super.dispatchTouchEvent (ev),即View中的dispatchTouchEvent(ev),然后调用mParentView的onTouchEvent()方法, 再调用ViewDragHelper的processTouchEvent(MotionEvent ev)方法。此时(ACTION_DOWN事件发生时)mParentView的onTouchEvent()要返回true, onTouchEvent()才能继续接受到接下来的ACTION_MOVE、ACTION_UP等事件,否则无法完成拖动(除了ACTION_DOWN外的其他事件发生时返回true或false都 不会影响接下来的事件接受),因为拖动的相关代码是写在processTouchEvent()里的ACTION_MOVE部分的。 要注意的是返回true后mParentView的onInterceptTouchEvent()就不会收到后续的ACTION_MOVE、ACTION_UP等事件了。

2.如果有子View消费了本次ACTION_DOWN事件,mParentView的onTouchEvent()就收不到ACTION_DOWN事件了, 也就是ViewDragHelper的processTouchEvent(MotionEvent ev)收不到ACTION_DOWN事件了。 不过只要该View没有调用过requestDisallowInterceptTouchEvent(true),mParentView的onInterceptTouchEvent()的ACTION_MOVE部分还是会执行的, 如果在此时返回了true拦截了ACTION_MOVE事件,processTouchEvent()里的ACTION_MOVE部分也就会正常执行,拖动也就没问题了。 onInterceptTouchEvent()的ACTION_MOVE部分具体做了怎样的处理,稍后再来解析。

接下来对这两种情况逐一解析。

假设没有子View消费这次事件,根据刚才的分析最终就会调用processTouchEvent(MotionEvent ev)的ACTION_DOWN部分:

这段代码跟shouldInterceptTouchEvent()里ACTION_DOWN那部分基本一致,唯一区别就是这里没有约束条件直接调用了tryCaptureViewForDrag()方法,现在来看看这个方法:

这里调用了Callback的tryCaptureView(View child, int pointerId)方法,把当前触摸到的View和触摸手指编号传递了过去,在tryCaptureView()中决定是否需要拖动当前触摸到的View,如果要拖动当前触摸到的View就在tryCaptureView()中返回true,让ViewDragHelper把当前触摸的View捕获下来, 接着就调用了captureChildView(toCapture, pointerId)方法:

代码很简单,在captureChildView(toCapture, pointerId)中将要拖动的View和触摸的手指编号记录下来,并调用Callback的onViewCaptured(childView, activePointerId)通知外部有子View被捕获到了, 再调用setDragState()设置当前的状态为STATE_DRAGGING,看setDragState()源码:

状态改变后会调用Callback的onViewDragStateChanged()通知状态的变化。

假设ACTION_DOWN发生后在mParentView的onTouchEvent()返回了true,接下来就会执行ACTION_MOVE部分:

要注意的是,如果一直没松手,这部分代码会一直调用。这里先判断mDragState是否为STATE_DRAGGING,而唯一调用setDragState(STATE_DRAGGING)的地方就是tryCaptureViewForDrag()了, 刚才在ACTION_DOWN里调用过tryCaptureViewForDrag(),现在又要分两种情况。

如果刚才在ACTION_DOWN里捕获到要拖动的View,那么就执行if部分的代码,这个稍后解析,先考虑没有捕获到的情况。没有捕获到的话,mDragState依然是STATE_IDLE,然后会执行else部分的代码。这里主要就是检查有没有哪个手指触摸到了要拖动的View上,触摸上了就尝试捕获它,然后让mDragState变为STATE_DRAGGING,之后就会执行if部分的代码了。这里还有两个方法涉及到了Callback里的方法,需要来解析一下, 分别是reportNewEdgeDrags()和checkTouchSlop(),先看reportNewEdgeDrags():

这里对四个边缘都做了一次检查,检查是否在某些边缘产生拖动了,如果有拖动,就将有拖动的边缘记录在mEdgeDragsInProgress中,再调用Callback的onEdgeDragStarted(int edgeFlags, int pointerId)通知某个边缘开始产生拖动了。虽然reportNewEdgeDrags()会被调用很多次(因为processTouchEvent()的ACTION_MOVE部分会执行很多次), 但mCallback.onEdgeDragStarted(dragsStarted, pointerId)只会调用一次,具体的要看checkNewEdgeDrag()这个方法:

checkNewEdgeDrag()返回true表示在指定的edge(边缘)开始产生拖动了。

方法的两个参数delta和odelta需要解释一下,odelta里的o应该代表opposite,这是什么意思呢,以reportNewEdgeDrags()里调用checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)为例,我们要监测左边缘的触摸情况,所以主要监测的是x轴方向上的变化,这里delta为dx,odelta为dy,也就是说delta是指我们主要监测的方向上的变化,odelta是另外一个方向上的变化,后面要判断假另外一个方向上的变化是否要远大于主要方向上的变化,所以需要另外一个方向上的距离变化的值。

mInitialEdgesTouched是在ACTION_DOWN部分的saveInitialMotion()里生成的,ACTION_DOWN发生时触摸到的边缘会被记录在mInitialEdgesTouched中。如果ACTION_DOWN发生时没有触摸到边缘,或者触摸到的边缘不是指定的edge,就直接返回false了。

mTrackingEdges是由setEdgeTrackingEnabled(int edgeFlags)设置的,当我们想要追踪监听边缘触摸时才需要调用setEdgeTrackingEnabled(int edgeFlags),如果我们没有调用过它,这里就直接返回false了。

mEdgeDragsLocked它在这个方法里被引用了多次,它在整个ViewDragHelper里唯一被赋值的地方就是这里的第12行,所以默认值是0,第6行mEdgeDragsLocked[pointerId] &amp; edge) == edge执行的结果是false。我们再跳到11到14行看看,absDelta &lt; absODelta * 0.5f的意思是检查在次要方向上移动的距离是否远超过主要方向上移动的距离,如果是再调用Callback的onEdgeLock(edge)检查是否需要锁定某个边缘,如果锁定了某个边缘,那个边缘就算触摸到了也不会被记录在mEdgeDragsInProgress里了,也不会收到Callback的onEdgeDragStarted()通知了。并且将锁定的边缘记录在mEdgeDragsLocked变量里,再次调用本方法时就会在第6行进行判断了,第6行里如果检测到给定的edge被锁定,就直接返回false了。

回到第7行的(mEdgeDragsInProgress[pointerId] &amp; edge) == edge,mEdgeDragsInProgress是保存已发生过拖动事件的边缘的,如果给定的edge已经保存过了,那就没必要再检测其他东西了,直接返回false了。

第8行(absDelta &lt;= mTouchSlop &amp;&amp; absODelta &lt;= mTouchSlop)很简单了,就是检查本次移动的距离是不是太小了,太小就不处理了。

最后一句返回的时候再次检查给定的edge有没有记录过,确保了每个边缘只会调用一次reportNewEdgeDrags的mCallback.onEdgeDragStarted(dragsStarted, pointerId)

再来看checkTouchSlop()方法:

这个方法主要就是检查手指移动的距离有没有超过触发处理移动事件的最短距离(mTouchSlop)了,注意dx和dy指的是当前触摸点到ACTION_DOWN触摸到的点的距离。这里先检查Callback的getViewHorizontalDragRange(child)和getViewVerticalDragRange(child)是否大于0,如果想让某个View在某个方向上滑动,就要在那个方向对应的方法里返回大于0的数。否则在processTouchEvent()的ACTION_MOVE部分就不会调用tryCaptureViewForDrag()来捕获当前触摸到的View了,拖动也就没办法进行了。

回到processTouchEvent()的ACTION_MOVE部分,假设现在我们的手指已经滑动到可以被捕获到的View上了,也都正常的实现了Callback中的相关方法,让tryCaptureViewForDrag()正常的捕获到触摸到的View了,下一次ACTION_MOVE时就执行if部分的代码了,也就是开始不停的调用dragTo()对mCaptureView进行真正拖动了,看dragTo()方法:

参数dx和dy是前后两次ACTION_MOVE移动的距离,left和top分别为mCapturedView.getLeft() + dx, mCapturedView.getTop() + dy,也就是期望的移动后的坐标,对View的getLeft()等方法不理解的请参阅Android View坐标getLeft, getRight, getTop, getBottom。

这里通过调用offsetLeftAndRight()和offsetTopAndBottom()来完成对mCapturedView移动,这两个是View中定义的方法,看它们的源码就知道内部是通过改变View的mLeft、mRight、mTop、mBottom,即改变View在父容器中的坐标位置,达到移动View的效果,所以如果调用mCapturedView的layout(int l, int t, int r, int b)方法也可以实现移动View的效果。

具体要移动到哪里,由Callback的clampViewPositionHorizontal()和clampViewPositionVertical()来决定的,如果不想在水平方向上移动,在clampViewPositionHorizontal(View child, int left, int dx)里直接返回child.getLeft()就可以了,这样clampedX - oldLeft的值为0,这里调用mCapturedView.offsetLeftAndRight(clampedX - oldLeft)就不会起作用了。垂直方向上同理。

最后会调用Callback的onViewPositionChanged(mCapturedView, clampedX, clampedY,clampedDx, clampedDy)通知捕获到的View位置改变了,并把最终的坐标(clampedX、clampedY)和最终的移动距离(clampedDx、 clampedDy)传递过去。

ACTION_MOVE部分就算告一段落了,接下来应该是用户松手触发ACTION_UP,或者是达到某个条件导致后续的ACTION_MOVE被mParentView的上层View给拦截了而收到ACTION_CANCEL,一起来看这两个部分:

这两个部分都是重置所有的状态记录,并通知View被放开了,再看下releaseViewForPointerUp()和dispatchViewReleased()的源码:

releaseViewForPointerUp()里也调用了dispatchViewReleased(),只不过传递了速率给它,这个速率就是由processTouchEvent()的mVelocityTracker追踪算出来的。再看dispatchViewReleased():

首先这两个方法是干什么的呢。在现实生活中保龄球的打法是,先做扔的动作让球的速度达到最大,然后突然松手,由于惯性,保龄球就以最后松手前的速度为初速度抛出去了,直至自然停止,或者撞到边界停止,这种效果叫fling。 flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)就是对捕获到的View做出这种fling的效果,用户在屏幕上滑动松手之前也会有一个滑动的速率。fling也引出来的一个问题,就是不知道View最终会滚动到哪个位置,最后位置是在启动fling时根据最后滑动的速度来计算的(flingCapturedView的四个参数int minLeft, int minTop, int maxLeft, int maxTop可以限定最终位置的范围),假如想要让View滚动到指定位置应该怎么办,答案就是使用settleCapturedViewAt(int finalLeft, int finalTop)。

为什么唯一可以调用settleCapturedViewAt()和flingCapturedView()的地方是Callback的onViewReleased()呢?看看它们的源码

这两个方法里一开始都会判断mReleaseInProgress为false,如果为false就会抛一个IllegalStateException异常, 而mReleaseInProgress唯一为true的时候就是在dispatchViewReleased()里调用onViewReleased()的时候。

ViewDragHelper还有一个移动View的方法是smoothSlideViewTo(View child, int finalLeft, int finalTop),看下它的源码:

可以看到它不受mReleaseInProgress的限制,所以可以在任何地方调用,效果和settleCapturedViewAt()类似,因为它们最终都调用了forceSettleCapturedViewAt()来启动自动滚动,区别在于settleCapturedViewAt()会以最后松手前的滑动速率为初速度将View滚动到最终位置,而smoothSlideViewTo()滚动的初速度是0。 forceSettleCapturedViewAt()里有地方调用了Callback里的方法,所以再来看看这个方法:

可以看到自动滑动是靠Scroll类完成,在这里生成了调用mScroller.startScroll()需要的参数。再来看看计算滚动时间的方法computeSettleDuration():

clampMag()方法确保参数中给定的速率在正常范围之内。最终的滚动时间还要经过computeAxisDuration()算出来,通过它的参数可以看到最终的滚动时间是由dx、 xvel、mCallback.getViewHorizontalDragRange()共同影响的。看computeAxisDuration():

如果给定的速率velocity不为0,就通过距离除以速率来算出时间;如果velocity为0,就通过要滑动的距离(delta)除以总的移动范围(motionRange,就是Callback里getViewHorizontalDragRange()、getViewVerticalDragRange()返回值)来算出时间。最后还会对计算出的时间做过滤,最终时间反正是不会超过MAX_SETTLE_DURATION的,源码里的取值是600毫秒,所以不用担心在Callback里getViewHorizontalDragRange()、getViewVerticalDragRange()返回错误的数而导致自动滚动时间过长了。

在调用settleCapturedViewAt()、flingCapturedView()和smoothSlideViewTo()时,还需要实现mParentView的computeScroll():

至此,整个触摸流程和ViewDragHelper的重要的方法都过了一遍。之前在讨论shouldInterceptTouchEvent()的ACTION_DOWN部分执行完后应该再执行什么的时候,还有一种情况没有展开详解,就是有子View消费了本次ACTION_DOWN事件的情况,现在来看看这种情况。

假设现在shouldInterceptTouchEvent()的ACTION_DOWN部分执行完了,也有子View消费了这次的ACTION_DOWN事件,那么接下来就会调用mParentView的onInterceptTouchEvent()的ACTION_MOVE部分,接着调用ViewDragHelper的shouldInterceptTouchEvent()的ACTION_MOVE部分:

如果有多个手指触摸到屏幕上了,对每个触摸点都检查一下,看当前触摸的地方是否需要捕获某个View。这里先用findTopChildUnder(int x, int y)寻找触摸点处的子View,再用checkTouchSlop(View child, float dx, float dy)检查当前触摸点到ACTION_DOWN触摸点的距离是否达到了mTouchSlop,达到了才会去捕获View。

接着看19~41行<code>if (pastSlop){…}</code>部分,这里检查在某个方向上是否可以进行拖动,检查过程涉及到getView[Horizontal|Vertical]DragRange和clampViewPosition[Horizontal|Vertical]四个方法。如果getView[Horizontal|Vertical]DragRange返回都是0,就会认作是不会产生拖动。clampViewPosition[Horizontal|Vertical]返回的是被捕获的View的最终位置,如果和原来的位置相同,说明我们没有期望它移动,也就会认作是不会产生拖动的。不会产生拖动就会在39行直接break,不会执行后续的代码,而后续代码里有调用tryCaptureViewForDrag(),所以不会产生拖动也就不会去捕获View了,拖动也不会进行了。 如果检查到可以在某个方向上进行拖动,就会调用后面的tryCaptureViewForDrag()捕获子View,如果捕获成功,mDragState就会变成STATE_DRAGGING,shouldInterceptTouchEvent()返回true,mParentView的onInterceptTouchEvent()返回true,后续的移动事件就会在mParentView的onTouchEvent()执行了,最后执行的就是mParentView的processTouchEvent()的ACTION_MOVE部分,拖动正常进行。

回头再看之前在shouldInterceptTouchEvent()的ACTION_DOWN部分留下的坑:

现在应该明白这部分代码会在什么情况下执行了。当我们松手后捕获的View处于自动滚动的过程中时,用户再次触摸屏幕,就会执行这里的tryCaptureViewForDrag()尝试捕获View,如果捕获成功,mDragState就变为STATE_DRAGGING了,shouldInterceptTouchEvent()就返回true了,然后就是mParentView的onInterceptTouchEvent()返回true,接着执行mParentView的onTouchEvent(),再执行processTouchEvent()的ACTION_DOWN部分。此时(ACTION_DOWN事件发生时)mParentView的onTouchEvent()要返回true,onTouchEvent()才能继续接受到接下来的ACTION_MOVE、ACTION_UP等事件,否则无法完成拖动。

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,可在任何地方调用。

void onViewDragStateChanged(int state) 拖动状态改变时会调用此方法,状态state有STATE_IDLE、STATE_DRAGGING、STATE_SETTLING三种取值。 它在setDragState()里被调用,而setDragState()被调用的地方有

1.tryCaptureViewForDrag()成功捕获到子View时

1.1 shouldInterceptTouchEvent()的ACTION_DOWN部分捕获到

1.2 shouldInterceptTouchEvent()的ACTION_MOVE部分捕获到

1.3 processTouchEvent()的ACTION_MOVE部分捕获到

2.调用settleCapturedViewAt()、smoothSlideViewTo()、flingCapturedView()时

3.拖动View松手时(processTouchEvent()的ACTION_UP、ACTION_CANCEL)

4.自动滚动停止时(continueSettling()里检测到滚动结束时)

5.外部调用abort()时

void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 正在被拖动的View或者自动滚动的View的位置改变时会调用此方法。

1.在dragTo()里被调用(正在被拖动时)

2.在continueSettling()里被调用(自动滚动时)

3.外部调用abort()时被调用

void onViewCaptured(View capturedChild, int activePointerId) tryCaptureViewForDrag()成功捕获到子View时会调用此方法。

1.在shouldInterceptTouchEvent()的ACTION_DOWN里成功捕获

2.在shouldInterceptTouchEvent()的ACTION_MOVE里成功捕获

3.在processTouchEvent()的ACTION_MOVE里成功捕获

4.手动调用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

本文转自我爱物联网博客园博客,原文链接:http://www.cnblogs.com/yydcdut/p/4945052.html,如需转载请自行联系原作者

继续阅读