繼續分析DrawerLayout的手勢分發部分
談到手勢分發,這本身就是個好話題,DrawerLayout作為繼承自ViewGroup得佈局他可以攔截手勢也可以分發給子view,也就是在onInterceptTouchEvent中做的操作,但是他的下面還有一個onTouchEvent方法,先看哪個呢?追溯程式碼我們可以知道ViewGroup繼承自View,而onTouchEvent是View的方法
我們還是先花點時間把兩者的關係先確認再繼續。
onInterceptTouchEvent和onTouchEvent---雞和蛋?
定位到ViewGroup,可以發現onInterceptTouchEvent分定義如下,從它前面一段非常長的註釋就可以看出其重要性和複雜,預設的返回是false
/**
* Implement this method to intercept all touch screen motion events. This
* allows you to watch events as they are dispatched to your children, and
* take ownership of the current gesture at any point.
*
* <p>Using this function takes some care, as it has a fairly complicated
* interaction with {@link View#onTouchEvent(MotionEvent)
* View.onTouchEvent(MotionEvent)}, and using it requires implementing
* that method as well as this one in the correct way. Events will be
* received in the following order:
*
* <ol>
* <li> You will receive the down event here.
* <li> The down event will be handled either by a child of this view
* group, or given to your own onTouchEvent() method to handle; this means
* you should implement onTouchEvent() to return true, so you will
* continue to see the rest of the gesture (instead of looking for
* a parent view to handle it). Also, by returning true from
* onTouchEvent(), you will not receive any following
* events in onInterceptTouchEvent() and all touch processing must
* happen in onTouchEvent() like normal.
* <li> For as long as you return false from this function, each following
* event (up to and including the final up) will be delivered first here
* and then to the target's onTouchEvent().
* <li> If you return true from here, you will not receive any
* following events: the target view will receive the same event but
* with the action {@link MotionEvent#ACTION_CANCEL}, and all further
* events will be delivered to your onTouchEvent() method and no longer
* appear here.
* </ol>
*
* @param ev The motion event being dispatched down the hierarchy.
* @return Return true to steal motion events from the children and have
* them dispatched to this ViewGroup through onTouchEvent().
* The current target will receive an ACTION_CANCEL event, and no further
* messages will be delivered here.
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
前兩段告訴我們,複寫onInterceptTouchEvent方法,可以實現監聽所有的動作事件MotionEvent,在向子view傳遞事件前做我們需要的操作,當然這指的是和這個viewgroup相關的事件;同時我們需要慎重處理該函式,因為他和onTouchEvent關係非常緊密,下面是事件接收的順序:
首先接收的的事按下事件,down事件,他可以被view處理也可以在自身的onTouchEvent裡處理,所以實現onTouchEvent並且返回true,這樣onTouchEvent繼續才能收到down之後的其他事件,同時onInterceptTouchEvent不會在收到後續事件,因為已經轉移到onTouchEvent處理了。
那麼什麼時候onInterceptTouchEvent會把後續事件轉移到他的onTouchEvent呢?這取決於onInterceptTouchEvent的返回值,如果返回false,所有事件都會先分發到這裡,然後再到目標view的onTouchEvent;相反如果返回true,那麼onInterceptTouchEvent將不再收到後續事件,並且目標view會收到cancel事件,接著自身的onTouchEvent幾首後續的事件。
這其實從名字來看是比較好理解的onInterceptTouchEvent表示在擷取觸控事件的被呼叫的方法,既然是擷取就可以直接吧事件截下來後不再往後傳遞,這是就是上面的第二種情況,返回true,即我們自己消耗了觸控事件,子view將沒有機會得到喚醒。
大致意思就是如果希望自身消耗掉改事件就可以直接返回true,這一點和onTouchEvent的返回類似目的。
部落格園有篇文章對這些事件分發做了很好的分析:http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html
詳細的闡述了了dispatchTouchEvent,onInterceptTouchEvent以及onTouchEvent之間的關係
現在我們回過頭來看DrawerLayout裡的分發是如何寫的:
重寫了後面兩個方法,先看onInterceptTouchEvent:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
// "|" used deliberately here; both methods should be invoked.
final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) |
mRightDragger.shouldInterceptTouchEvent(ev);
boolean interceptForTap = false;
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mInitialMotionX = x;
mInitialMotionY = y;
if (mScrimOpacity > 0 &&
isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) {
interceptForTap = true;
}
mDisallowInterceptRequested = false;
mChildrenCanceledTouch = false;
break;
}
case MotionEvent.ACTION_MOVE: {
// If we cross the touch slop, don't perform the delayed peek for an edge touch.
if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) {
mLeftCallback.removeCallbacks();
mRightCallback.removeCallbacks();
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
closeDrawers(true);
mDisallowInterceptRequested = false;
mChildrenCanceledTouch = false;
}
}
return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
}
1.首先從touch event裡面獲取當前具體的action動作,MotionEventCompat.getActionMasked(ev),內部實際上做了一次按位於操作event.getAction() & ACTION_MASK;
2.檢查當前是否滿足擷取drag狀態,用於決定onInterceptTouchEvent返回值,這裡有個註解說是故意用了|或,而不是||或,兩者區別在於||只要第一個條件滿足就不在執行第二個檢查,二|不同,無論如何都會將兩個條件檢查一遍;
3.接下來是幾個case,根據當前的action做處理;
ACTION_DOWN,當按下時記錄按下點的x,y座標值,根據條件設定當前是否滿足tap狀態,具體條件有兩個,一是mScrimOpacity,表示子view中在螢幕上佔據的最大寬度(0-1),二時根據座標點的位置取得改點對應的最上層view物件,如果是預定義的content view即DrawerLayout裡的主內容展示view,也就是同時滿足view在螢幕上且點選的位置直接落在了content view上。
ACTION_MOVE,當手按下後開始在螢幕上移動時,如果垂直和水平上的位移差量達到了drag helper的閥值則一處左右兩邊的回撥介面
ACTION_CANCLE和ACTION_UP,手勢結束後,關閉選單
最後結合幾個狀態來那個來決定onInterceptTouchEvent返回true還是false,
未完待續