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>處理事件。
第一個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] & edge) == edge執行的結果是false。我們再跳到11到14行看看,absDelta < absODelta * 0.5f的意思是檢查在次要方向上移動的距離是否遠超過主要方向上移動的距離,如果是再調用Callback的onEdgeLock(edge)檢查是否需要鎖定某個邊緣,如果鎖定了某個邊緣,那個邊緣就算觸摸到了也不會被記錄在mEdgeDragsInProgress裡了,也不會收到Callback的onEdgeDragStarted()通知了。并且将鎖定的邊緣記錄在mEdgeDragsLocked變量裡,再次調用本方法時就會在第6行進行判斷了,第6行裡如果檢測到給定的edge被鎖定,就直接傳回false了。
回到第7行的(mEdgeDragsInProgress[pointerId] & edge) == edge,mEdgeDragsInProgress是儲存已發生過拖動事件的邊緣的,如果給定的edge已經儲存過了,那就沒必要再檢測其他東西了,直接傳回false了。
第8行(absDelta <= mTouchSlop && absODelta <= 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,如需轉載請自行聯系原作者