主目錄見:Android高級進階知識(這是總目錄索引)
?看了很多的事件分發(fā)文章,感覺都有點欠缺,今天這篇文章將始于場景終于源碼。盡量深入地分析事件分發(fā),達到一篇過的效果,即看了這篇不用看第二篇,這里的場景大部分從《可能是講解Android事件分發(fā)最好的文章》來,然后進行用源碼進行覆蓋,程序員怎么能只記住結(jié)論?。?!
一.目標
今天的目標也很明確,為了讓大家能完全掌握事件分發(fā)機制,今天唯一的目標就是利用場景盡量覆蓋所有源碼,由易到難循序漸進。所以:
1.完全掌握事件分發(fā)機制,記住是完全?。?!
2.能為后面的實例做準備,所以一定要學好以后才能不迷茫。
二.事件分發(fā)
1.基礎假設(引用場景文章):
?我們只考慮最重要的四個觸摸事件,即:DOWN,MOVE,UP和CANCEL。一個手勢(gesture)是一個事件列,以一個DOWN事件開始(當用戶觸摸屏幕時產(chǎn)生),后跟0個或多個MOVE事件(當用戶四處移動手指時產(chǎn)生),最后跟一個單獨的UP或CANCEL事件(當用戶手指離開屏幕或者系統(tǒng)告訴你手勢(gesture)由于其他原因結(jié)束時產(chǎn)生)。當我們說到“手勢剩余部分”時指的是手勢后續(xù)的MOVE事件和最后的UP或CANCEL事件。
?在這里我也不考慮多點觸摸手勢(我們只假設用一個手指)并且忽略多個MOVE事件可以被歸為一組這一實際情況。
?我們將要討論的視圖層次是這樣的:最外層是一個ViewGroup A,包含一個或多個子view(children),其中一個子view是ViewGroup B,ViewGroupB中又包含一個或多個子view,其中一個子view是 View C,C不是一個ViewGroup。這里我們忽略同層級view之間可能的交叉疊加。

假設用戶首先觸摸到的屏幕上的點是C上的某個點,該點被標記為觸摸點(touch point),DOWN事件就在該點產(chǎn)生。然后用戶移動手指并最后離開屏幕,此過程中手指是否離開C的區(qū)域無關(guān)緊要,關(guān)鍵是手勢(gesture)是從哪里開始的。
2.場景一
?假設上面的A,B,C都沒有覆寫默認的事件傳播行為,那么下面就是事件傳播的過程:
?1.DOWN事件被傳遞到C,由于C沒有監(jiān)聽onTouchListener的onTouch方法,所以會走到C的onTouchEvent事件里面,該方法返回false,表示"我不關(guān)心該手勢"。
?2.因此該事件被傳遞到B的onTouchListener中的onTouch方法,因為沒有監(jiān)聽該方法,所以會被傳遞到onTouchEvent里面,該方法返回false,所以表示"我也不關(guān)心該手勢"。
?3.同樣,因為B不關(guān)心這個手勢,DOWN事件被傳到A的onTouchEvent方法中,該方法也返回false。
由于沒有view關(guān)心這個手勢(gesture),它們將不再會從“手勢剩余部分”中接收任何事件。
2.1場景一源碼分析:
?從場景一我們知道這是一個完整的傳遞過程。我們首先應該明確所有的事件的源頭都是從ViewGroup的dispatchTouchEvent事件開始,也就是首先從ViewGroup開始分發(fā),所以我們從這個方法開始(其他場景分析的源碼也會用到這里,這里每個注釋都用編號編好,到時我說注釋多少多少請對應這里面代碼):
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
........
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//1.在這個地方我們事件開始是初始化即清除響應控件和重置觸摸狀態(tài)
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
final boolean intercepted;
//2.如果我們的點擊事件是ACTION_DOWN或者響應控件不會空且
//requestDisallowInterceptTouchEvent(false)則會進入攔截事件
//onInterceptTouchEvent,不然會直接跳過攔截即直接說明已經(jīng)
//攔截過了intercepted = true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
//3.用于記錄最新響應控件的
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//4.如果不是取消和ViewGroup未攔截事件則進入該方法
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//5.如果事件是ACTION_DOWN,ACTION_POINTER_DOWN,ACTION_HOVER_MOVE則進入該判斷
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//6.如果還未有響應控件且ViewGroup下面有子視圖
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//7.這個ArrayList是對子視圖層次進行排序,即明確子視圖分發(fā)的層級關(guān)系
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//8.遍歷所有的子視圖
for (int i = childrenCount - 1; i >= 0; i--) {
//9.首先從arraylist獲取子視圖
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
........
//10.從響應對象鏈表中查找這個子視圖,查找的到就賦值
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//11.dispatchTransformedTouchEvent這個方法主要是看
//child不為空則調(diào)用他的dispatchTouchEvent方法,如果返回true則進入
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//12.將這個child添加進響應對象的鏈表中,這個地方是用頭部插入鏈表的方式,同時賦值mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
//13.如果mFirstTouchTarget 為空則進入這里,這里注意child為空
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//14.如果不為空,說明前面的DOWN事件已經(jīng)響應過了,所以會進入這里
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
.........
return handled;
}
這個方法很長,我也是極力省略了,但是這些方法都很重要,不過不用擔心,除了代碼中的注釋,我這里將一一說明。
我們的場景一中的DOWN事件首先就會進入到我們首先看到第一個注釋的地方:
//1.在這個地方我們事件開始是初始化即清除響應控件和重置觸摸狀態(tài)
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
如注釋說明的因為事件是DOWN,所以說明這是一個新的事件,所以要清除原先響應控件的一些記錄,同時重置觸摸狀態(tài)也就是初始化,回到初始狀態(tài)。然后我們看到下一個關(guān)鍵的地方:
//2.如果我們的點擊事件是ACTION_DOWN或者響應控件不會空且
//requestDisallowInterceptTouchEvent(false)則會進入攔截事件
//onInterceptTouchEvent,不然會直接跳過攔截即直接說明已經(jīng)
//攔截過了intercepted = true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
我們的場景一事件傳遞到這里,因為我們沒有覆寫默認的事件傳播行為,所以我們的onInterceptTouchEvent返回false即不攔截事件,我們的事件就往下傳遞:
//4.如果不是取消和ViewGroup未攔截事件則進入該方法
if (!canceled && !intercepted) {
}
我們看到事件傳遞到這邊會進行判斷,因為canceled為false且我們未攔截所以intercepted也為false,所以這個事件是成立的。會進入到這個判斷里面吧:
//5.如果事件是ACTION_DOWN,ACTION_POINTER_DOWN,ACTION_HOVER_MOVE則進入該判斷
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
}
這邊我們由于是個或判斷,由于我們這個事件就是DOWN,所以我們這個事件也是成立的,我們這個方法也會進入,所以進行到下面這個代碼:
//6.如果還未有響應控件且ViewGroup下面有子視圖
if (newTouchTarget == null && childrenCount != 0) {
}
這個判斷主要是看newTouchTarget == null 否,因為是DOWN事件,說明這是個新事件還沒有響應對象,所以newTouchTarget為空是成立的,然后看childrenCount != 0嗎?這個地方我們的視圖是不為空的,我們看基礎假設那幅圖可以知道。所以我們又會進入到這個判斷里面:
//7.這個ArrayList是對子視圖層次進行排序,即明確子視圖分發(fā)的層級關(guān)系
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
這個方法是干嘛的呢?其實從方法名字也可以看出,這個方法是根據(jù)層級結(jié)構(gòu)來排序我們的視圖,然后加入ArrayList中。這樣我們的視圖就有先后層級關(guān)系了。接著我們就調(diào)用來遍歷所有的視圖:
//8.遍歷所有的子視圖
for (int i = childrenCount - 1; i >= 0; i--) {
}
我們這個地方是對視圖層級結(jié)構(gòu)一一做操作首先我們就是:
//9.首先從arraylist獲取子視圖
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
可以看出這里獲取到這個i對應的子視圖,也就是取出我們的第一個視圖:
//10.從響應對象鏈表中查找這個子視圖,查找的到就賦值
newTouchTarget = getTouchTarget(child);
這個地方是從鏈表中查找到對應的這個視圖,然后賦值給newTouchTarget ,也就說明了我們這個視圖將要響應事件了。
//11.dispatchTransformedTouchEvent這個方法主要是看
//child不為空則調(diào)用他的dispatchTouchEvent方法,如果返回true則進入
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
}
這個方法很重要,我們看看這個方法干了啥:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
//如果收到的是cancel事件,則直接判斷child為空否,如果為空則直接調(diào)用ViewGroup父類的dispatchTouchEvent,不然就調(diào)用子View的dispatchTouchEvent
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//省略一些多點觸摸的內(nèi)容
..........
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//不為空則調(diào)用child的dispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
這個方法看下來刨去細節(jié),其實這個方法是看有沒有child,如果有的話直接調(diào)用子View的dispatchTouchEvent,這樣事件分發(fā)就分發(fā)下去了,這是一個遞歸的過程。這里的調(diào)用順序是:根ViewGroup——分發(fā)中間ViewGroup——分發(fā)到目標View,然后進行回歸。所以事件分發(fā)到View C的dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent event) {
.........
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
........
return result;
}
我們看到View里面的dispatchTouchEvent()方法并不復雜,首先看li.mOnTouchListener.onTouch(this, event)) 這個方法,這個方法其實就是我們控件addTouchListener監(jiān)聽器時候添加的,如果這個監(jiān)聽里面的onTouch返回true,我們就不調(diào)用下面的onTouchEvent()方法了,因為場景一里面是不覆寫默認方法的,所以這個li.mOnTouchListener.onTouch(this, event)) 默認是返回false的,所以我們會走到onTouchEvent()方法。這里的onTouchEvent我們先不講里面的具體內(nèi)容(這里面在UP事件時候會調(diào)用performClick()方法,其實就是調(diào)用的onClick方法)。我們知道我們場景一里面視圖C的onTouchEvent返回了false,所以會向上詢問B,B的onTouchEvent還有C的onTouchEvent都返回了false,所以DOWN事件就沒有被消費掉,所以dispatchTransformedTouchEvent()返回為false。所以我們接下來會走到下面這個判斷:
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
//13.如果mFirstTouchTarget 為空則進入這里,這里注意child為空
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//14.如果不為空,說明前面的DOWN事件已經(jīng)響應過了,所以會進入這里
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
因為我們前邊沒有一個相應DOWN事件,所以mFirstTouchTarget == null為真,所以我們看注釋13處:
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
即直接調(diào)用dispatchTransformedTouchEvent方法且參數(shù)child==null。這個方法我們前面看到如果child==null會調(diào)用super.dispatchTouchEvent(transformedEvent)即往父類傳,表示我不消費這個事件。
所以到這里我們場景一的DOWN就完成了,接下來"剩余手勢事件來了"(MOVE,UP,CANCEL),由于不是DOWN事件所以我們的程序會直接跳過注釋2,注釋5直接來到我們的if (mFirstTouchTarget == null) (注釋13,注釋14)這個判斷,也就是我們會直接調(diào)用dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);因為第三個參數(shù)child==null,所以我們的事件會直接傳給父類,不會再往下傳過C,B,A了。也就是說由于沒有view關(guān)心這個手勢(gesture),它們將不再會從“手勢剩余部分”中接收任何事件。
3.場景二
?現(xiàn)在,讓我們假設C實際上是關(guān)心這個手勢(gesture)的,原因可能是C被設置成可點擊的(clickable)或者你覆寫了C的onTouchEvent方法。
?1.DOWN事件被傳遞給C的onTouchEvent方法,該方法可以做任何它想做的事情,最后返回true。
?2.因為C說它正在處理這個手勢(gesture),則DOWN事件將不再被傳遞給B和A的onTouchEvent方法。
?3.因為C說它正在處理這個手勢(gesture),所以“手勢剩余部分”的事件也將傳遞給C的onTouchEvent方法,此時該方法返回true或false都無關(guān)緊要了,但是為保持一致最好還是返回true。
3.1場景二源碼分析
我們看到相較于第一個場景,這個地方增加了C關(guān)心這個手勢,也就是說C消費了這個事件,那么我們還是一樣,從ViewGroup的dispatchTouchEvent()開始,我們經(jīng)過初始化然后還是會進入到攔截:
//2.如果我們的點擊事件是ACTION_DOWN或者響應控件不會空且
//requestDisallowInterceptTouchEvent(false)則會進入攔截事件
//onInterceptTouchEvent,不然會直接跳過攔截即直接說明已經(jīng)
//攔截過了intercepted = true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
因為同樣,我們這個沒有攔截即intercepted為false,其他過程條件和場景一相似,所以我們會一步一步到我們的注釋11處,也會進行事件分發(fā),然后最終到C的onTouchEvent方法,因為我們知道我們這個場景二里面C的onTouchEvent這個地方返回true,所以注釋11處的判斷 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 會返回為true。這樣我們的流程就會走到注釋12處:
//12.將這個child添加進響應對象的鏈表中,這個地方是用頭部插入鏈表的方式,同時賦值mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
這樣我們就添加我們的響應對象到響應對象鏈表中,同時賦值mFirstTouchTarget所以我們mFirstTouchTarget就不為null了。然后程序直接break掉,意思就是說這個DOWN事件就不會傳遞給B和A了,接著我們程序走到注釋13處:
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else{
....
}
現(xiàn)在我們這個地方的mFirstTouchTarget == null不為空了,所以我們的流程就會到else程序里面了,我們這里看下else代碼里面做了什么:
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
我們看到這里我們會走到 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) 這個判斷,因為我們這個地方的target==mFirstTouchTarget且newTouchTarget==mFirstTouchTarget且alreadyDispatchedToNewTouchTarget 為true,所以我們這里直接就返回handled=true。到這里我們的DOWN事件也被消費完了,緊接著我們的"剩余手勢事件"(MOVE,UP)等也來了。跟DOWN事件一樣,我們也會到達注釋11處的判斷 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))即也會調(diào)用View C的onTouchEvent方法,這時候不管onTouchEvent的up,move事件返回true或者false都沒有關(guān)系,為什么呢?因為這個地方我們已經(jīng)記錄完了mFirstTouchTarget。到這里我們的場景二也已經(jīng)講解完畢了。現(xiàn)在我們難度要升級了。
4.場景三
?現(xiàn)在我們將討論一個新的方法:onInterceptTouchEvent,它只存在于ViewGroup中,普通的View中沒有這個方法。在任何一個view的onTouchEvent被調(diào)用之前,它的父輩們(ancestors)將先獲得攔截這個事件的一次機會,換句話說,它們可以竊取該事件。在剛才的“處理事件”部分中,我們遺漏了這一過程,現(xiàn)在,讓我們把它加上:
?1.DOWN事件被傳給A的onInterceptTouchEvent,該方法返回false,表示它不想攔截。
?2.DOWN又被傳遞給B的onInterceptTouchEvent,它也不想攔截,因此該方法也返回false。
?3.現(xiàn)在,DOWN事件被傳遞到C的onTouchEvent方法,該方法返回true,因為它想處理以該事件為首的手勢(gesture)。
現(xiàn)在,該手勢的下一個事件MOVE到來了。這個MOVE事件再一次被傳遞給A的onInterceptTouchEvent方法,該方法再一次返回false,B也同樣如此。
然后,MOVE事件被傳遞給C的onTouchEvent,就像在前一部分中一樣。
“手勢剩余部分”中其他事件的處理過程和上面一樣,假如A和B的onInterceptTouchEvent方法繼續(xù)返回false的話。
4.1場景三源碼分析
因為場景一和場景二我們都沒有說明onInterceptTouchEvent這個方法,所以我們場景三說明一下onInterceptTouchEvent這個方法,雖然我們場景三添加進來這個方法,但是我們看到這個地方都是返回的false,所以看到攔截代碼:
//2.如果我們的點擊事件是ACTION_DOWN或者響應控件不會空且
//requestDisallowInterceptTouchEvent(false)則會進入攔截事件
//onInterceptTouchEvent,不然會直接跳過攔截即直接說明已經(jīng)
//攔截過了intercepted = true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
因為這個地方A,B的onInterceptTouchEvent都返回false跟我們前面場景分析的默認返回false一樣。所以代碼流程和場景二其實是一樣的。要是想要一點不一樣,我們就直接來看最后一個場景,也是我認為價值比計較大的一個場景。

5.場景四
現(xiàn)在,讓我們更進一步,假設B沒有攔截DOWN事件,但它攔截了接下來的MOVE事件。原因可能是B是一個scrolling view。當用戶僅僅在它的區(qū)域內(nèi)點擊(tap)時,被點擊到的元素應當能處理該點擊事件。但是當用戶手指移動了一定的距離后,就不能再視該手勢(gesture)為點擊了——很明顯,用戶是想scroll。這就是為什么B要接管該手勢(gesture)。
下面是事件被處理的順序:
?1.DOWN事件被依次傳到A和B的onInterceptTouchEvent方法中,它們都返回的false,因為它們目前還不想攔截。
?2.DOWN事件傳遞到C的onTouchEvent方法,返回了true。
在后續(xù)到來MOVE事件時,A的onInterceptTouchEvent方法仍然返回false。
?3.B的onInterceptTouchEvent方法收到了該MOVE事件,此時B注意到用戶手指移動距離已經(jīng)超過了一定的threshold(或者稱為slop)。因此,B的onInterceptTouchEvent方法決定返回true,從而接管該手勢(gesture)后續(xù)的處理。
?然后,這個MOVE事件將會被系統(tǒng)變成一個CANCEL事件,這個CANCEL事件將會傳遞給C的onTouchEvent方法。
現(xiàn)在,又來了一個MOVE事件,它被傳遞給A的onInterceptTouchEvent方法,A還是不關(guān)心該事件,因此onInterceptTouchEvent方法繼續(xù)返回false。
此時,該MOVE事件將不會再傳遞給B的onInterceptTouchEvent方法,該方法一旦返回一次true,就再也不會被調(diào)用了。事實上,該MOVE以及“手勢剩余部分”都將傳遞給B的onTouchEvent方法(除非A決定攔截“手勢剩余部分”)。
C再也不會收到該手勢(gesture)產(chǎn)生的任何事件了。
5.1場景四源碼分析
我們看到我們的大boss來了,我們看到這個場景的1中,A和B都不想攔截DOWN所以DOWN事件最終到了視圖C,視圖C的onTouchEvent返回true表示說我想消費這個事件。所以程序會記錄下mFirstTouchTarget為視圖C,這個時候MOVE事件來了,到了注釋2攔截方法處:
//2.如果我們的點擊事件是ACTION_DOWN或者響應控件不會空且
//requestDisallowInterceptTouchEvent(false)則會進入攔截事件
//onInterceptTouchEvent,不然會直接跳過攔截即直接說明已經(jīng)
//攔截過了intercepted = true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
}
我們看到這時候雖然不是DOWN事件,但是這個時候mFirstTouchTarget != null,所以還是會走進攔截方法,A表示了不想攔截事件,但是B表示你符合我條件我想要攔截你。所以事件就會被B攔截onInterceptTouchEvent返回為true即intercepted為true。所以我們程序到注釋4處的時候:
//4.如果不是取消和ViewGroup未攔截事件則進入該方法
if (!canceled && !intercepted) {
}
這個判斷就不會通過,即這個過程就不會執(zhí)行,這樣程序就會直接到達注釋13和注釋14處即:
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
}else{
}
這個時候我們的mFirstTouchTarget 不為空,所以我們就會走到else的代碼里面,又到了判斷 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) ,這個判斷的target是我們的mFirstTouchTarget即視圖C,但是我們的newTouchTarget被重新賦值為null了,所以我們這個判斷會不成功,就會走到else方法里面即:
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
這段代碼里面第一個cancelChild 由于是個或判斷,我們知道intercepted被B攔截返回為true了,所以調(diào)用dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)這個方法的時候cancelChild為true,到這個dispatchTransformedTouchEvent方法里面的時候我們看看做了啥:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
//如果收到的是cancel事件,則直接判斷child為空否,如果為空則直接調(diào)用ViewGroup父類的dispatchTouchEvent,不然就調(diào)用子View的dispatchTouchEvent
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//省略一些多點觸摸的內(nèi)容
..........
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//不為空則調(diào)用child的dispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
我們看到第一個注釋處,判斷if (cancel || oldAction == MotionEvent.ACTION_CANCEL)這個判斷由于傳進來的cancel為true,所以我們就設置事件為CANCEL的然后調(diào)用給子視圖的dispatchTouchEvent即把CANCEL事件循環(huán)傳給了子視圖。也就是場景四里面說的這個MOVE事件將會被系統(tǒng)變成一個CANCEL事件,這個CANCEL事件將會傳遞給C的onTouchEvent方法。接著我們看上面代碼:
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
因為我們的cancelChild為true了,所以代碼會走進來這段代碼,由于這段代碼是循環(huán)執(zhí)行的,所以最終mFirstTouchTarget 會被最終設置為null。
那么如果這時候又來了一個MOVE事件呢?我們的流程又是怎樣的?首先我們知道這個MOVE事件也是先到達攔截部分,我們再來回顧下這個方法:
//2.如果我們的點擊事件是ACTION_DOWN或者響應控件不會空且
//requestDisallowInterceptTouchEvent(false)則會進入攔截事件
//onInterceptTouchEvent,不然會直接跳過攔截即直接說明已經(jīng)
//攔截過了intercepted = true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
這時候我們再來看這個判斷actionMasked == MotionEvent.ACTION_DOWN這個地方肯定是假的,因為我們現(xiàn)在是MOVE事件,mFirstTouchTarget 的話因為上面被設置為null了,所以這個條件肯定就是未假了,我們直接就跳到intercepted = true部分。即不再調(diào)用onInterceptTouchEvent方法了。程序又到了注釋4的判斷if (!canceled && !intercepted) 。由于intercepted為true,所以這個方法被跳過。程序直接會跳到注釋13,14處:
if (mFirstTouchTarget == null) {
.....
}else{
....
}
這個時候我們看到我們的mFirstTouchTarget == null是true的,所以我們代碼會調(diào)用if判斷里面的程序最終調(diào)用到 handled = super.dispatchTouchEvent(transformedEvent)方法。這個方法就會調(diào)用B里面onTouchEvent方法。也就是場景四最后說的該MOVE以及“手勢剩余部分”都將傳遞給B的onTouchEvent方法(除非A決定攔截“手勢剩余部分”)。C再也不會收到該手勢(gesture)產(chǎn)生的任何事件了。到這里我們的事件分發(fā)已經(jīng)講完了。其實過程還是蠻簡單的。只要認真閱讀源碼,結(jié)合場景,問題就迎刃而解了。
總結(jié):事件分發(fā)看是復雜,那是因為之前大家講解的都不是很徹底,導致看得人覺得很麻煩,但是只要認真往下走的時候發(fā)現(xiàn)還是很清晰的,學會了事件分發(fā),那我們對很多現(xiàn)象就能解釋了,接下來期待下一篇事件分發(fā)實踐吧。
