在這之前看了很多相關(guān)文章,有一個(gè)整體認(rèn)識(shí)以后,就要開始動(dòng)手體驗(yàn)一下了。動(dòng)手之前要明確事件分發(fā)機(jī)制要研究的是什么:事件序列在ViewGroup/View之間的傳遞規(guī)則。
注意幾點(diǎn):
- 研究的是事件序列而不是單個(gè)事件
- 至少要考慮到一個(gè)ViewGroup和一個(gè)View
- 傳遞或者說分發(fā)包括父View向子View傳遞事件(事件下發(fā)過程)和子View向父View傳遞事件(事件消費(fèi)過程)。
舉例說明一下為什么要考慮上面這幾點(diǎn),如果事件傳到了一個(gè)沒有子View的View里面,這時(shí)view的onTouchEvent()會(huì)被回調(diào),我們可以通過重寫onTouchEvent()的返回值來決定是否消費(fèi)這個(gè)事件。如果這個(gè)事件是down,而onTouchEvent()返回false不消費(fèi)它,那么事件序列后面的事件都不再分發(fā)給這個(gè)View,如果消費(fèi)了down事件,那么后續(xù)事件會(huì)繼續(xù)分發(fā)給這個(gè)View,這時(shí),如果不消費(fèi)后續(xù)事件的某個(gè)move事件,那么這個(gè)move事件后面的事件依然會(huì)分發(fā)給這個(gè)View。
由此可見,對(duì)事件序列里某個(gè)事件的消費(fèi)情況是會(huì)影響后續(xù)事件的分發(fā)的。
另一方面,如果這個(gè)View沒消費(fèi)down事件,那么會(huì)一層層往上回調(diào)父View的onTouchEvent()將down事件傳遞給父View,所以事件的消費(fèi)也是一個(gè)傳遞事件的過程。
上面的例子是通過編寫代碼驗(yàn)證的一個(gè)事實(shí),至于原因,會(huì)在文中詳細(xì)分析,這里只為說明需要考慮上面的幾個(gè)注意點(diǎn)。
雖然事件體系很復(fù)雜,但是是有規(guī)律可循的,我們實(shí)踐的目的就是尋找這個(gè)規(guī)律。根據(jù)我的理解,畫了一張圖作為Android事件機(jī)制的一個(gè)總結(jié):

由圖可以看到將整個(gè)流程分為了4種情況,下面將通過實(shí)踐詳細(xì)分析這4種情況,包括為什么這樣劃分,其他更多的情況如何歸類排除等。
首先實(shí)現(xiàn)下圖界面,界面每一層View的dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()三個(gè)方法中加上相應(yīng)的log并返回默認(rèn)的super,也可以使用這份代碼:示例代碼。圖中,我們可以把Activity看作是頂級(jí)父View。

然后研究 Android 事件分發(fā)流程圖中的4種情況:
- 默認(rèn)情況,全部返回super,默認(rèn)情況是不攔截不消費(fèi)事件的。
- View的onTouchEvent()消費(fèi)down事件,其他默認(rèn)。
- ViewGroup2的onTouchEvent()消費(fèi)down事件,其他默認(rèn)。
- ViewGroup2的onInterceptTouchEvent()攔截down之后的事件。
消費(fèi)事件指onTouchEvent()返回true,攔截事件指onInterceptTouchEvent()返回true。從4種情況可以看出,將一個(gè)事件序列的ACTION_DOWN和ACTION_DOWN之后的事件分開考慮了,分析完源碼之后就會(huì)明白為什么這樣做。
下面開始結(jié)合log分析4種情況,為了方便查看和描述,我會(huì)把log用空行分為三部分,第一部分是down事件,第二部分是move事件,第三部分是up事件。
情況1:默認(rèn)情況,全部返回super,默認(rèn)情況是不攔截不消費(fèi)事件的。
( 1955): MainActivity->dispatchTouchEvent
( 1955): MyViewGroup1-->dispatchTouchEvent
( 1955): MyViewGroup1-->onInterceptTouchEvent
( 1955): MyViewGroup2--->dispatchTouchEvent
( 1955): MyViewGroup2--->onInterceptTouchEvent
( 1955): MyView--------------->dispatchTouchEvent //down事件下發(fā)過程結(jié)束
( 1955): MyView--------------->onTouchEvent // down事件消費(fèi)過程開始
( 1955): MyViewGroup2--->onTouchEvent
( 1955): MyViewGroup1-->onTouchEvent
( 1955): MainActivity->onTouchEvent
( 1955): MainActivity->dispatchTouchEvent //move1事件
( 1955): MainActivity->onTouchEvent
( 1955): MainActivity->dispatchTouchEvent //move2事件
( 1955): MainActivity->onTouchEvent
( 1955): MainActivity->dispatchTouchEvent //up事件
( 1955): MainActivity->onTouchEvent
上面的log我手動(dòng)加了幾個(gè)注釋,默認(rèn)情況下Activity/ViewGroup/View不攔截不消費(fèi)事件,由log很容易看出,down事件經(jīng)歷了一下一上的過程,下是分發(fā)過程,上是消費(fèi)過程。如果下發(fā)無人攔截,事件會(huì)一直向下傳遞到子View,如果子View不消費(fèi)事件,會(huì)傳給父View去消費(fèi),即依次回調(diào)父View的onTouchEvent。
然后就是move和up事件,log很簡(jiǎn)單,因?yàn)閐own事件子View們無人消費(fèi),那么事件序列里面的后續(xù)事件也就不再下發(fā)了,直接頂級(jí)Activity(DecorView)自己來處理。
為什么后續(xù)事件不再下發(fā)給子View,答案在源碼里。接下來開始分析源碼。
下面是ViewGroup的dispatchTouchEvent()方法,只保留了關(guān)鍵代碼,其中if語句都是完整的,可以通過if語句在完整源碼中定位代碼。數(shù)字編號(hào)的注釋是 ACTION_DOWN事件的下發(fā)過程,注意是down事件的下發(fā)過程。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
//1.首先如果是down事件,reset 一些狀態(tài),其中包括將mFirstTouchTarget置空
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//2.是否攔截事件,用布爾變量intercepted表示
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//這里調(diào)用onInterceptTouchEvent()看是否攔截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {//如果mFirstTouchTarget是空,而且當(dāng)前事件不是ACTION_DOWN,就攔截事件
//注意此處直接給intercepted賦值而沒有調(diào)用onInterceptTouchEvent()方法
intercepted = true;
}
// 3.得到intercepted之后,如果不攔截,進(jìn)入這個(gè)if
if (!canceled && !intercepted) {
//4.如果是down事件,進(jìn)入這個(gè)if
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//5.newTouchTarget是這個(gè)方法里面定義的,默認(rèn)是空,進(jìn)入這個(gè)if
if (newTouchTarget == null && childrenCount != 0) {
//6.循環(huán),尋找處理事件的子View
for (int i = childrenCount - 1; i >= 0; i--) {
//7.注意這個(gè)if里面的dispatchTransformedTouchEvent方法,里面調(diào)用了子View的dispatchTouchEvent方法
// 調(diào)用子View的dispatchTouchEvent方法即把事件傳給了子View去處理,
// 這時(shí)先走一遍子View里的dispatchTouchEvent邏輯并返回一個(gè)布爾值表示是否消費(fèi)掉了事件,
// 如果子View的dispatchTouchEvent返回true說明子View消費(fèi)事件,進(jìn)入這個(gè)if,否則沒消費(fèi)不進(jìn)入此if
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//8.如果子View消費(fèi)了事件,那么給mFirstTouchTarget賦值
// 給mFirstTouchTarget賦值的操作在addTouchTarget()方法里
newTouchTarget = addTouchTarget(child, idBitsToAssign);
break;
}
}
}
}
}
//在編號(hào)7的if條件里面已經(jīng)把事件下發(fā)到了子View,并得到了子View返回的結(jié)果
//至此,默認(rèn)情況下發(fā)事件的邏輯結(jié)束
//下面消費(fèi)事件相關(guān)的代碼,省略
}
//最后返回結(jié)果,此方法結(jié)束
return handled;
//至此,如果編號(hào)8的代碼沒執(zhí)行,也就是子View的dispatchTouchEvent沒消費(fèi)事件,那么mFirstTouchTarget的值是空
//結(jié)合編號(hào)3的條件,可以得出結(jié)論:當(dāng)ViewGroup不攔截事件且它的子View消費(fèi)事件的時(shí)候,mFirstTouchTarget不為空,否則mFirstTouchTarget是空。
}
代碼中注釋很詳細(xì),最終可以得到一個(gè)結(jié)論:當(dāng)ViewGroup不攔截down事件且它的子View消費(fèi)down事件的時(shí)候,mFirstTouchTarget不為空,否則mFirstTouchTarget是空。
下面帶著這個(gè)結(jié)論重新看源碼,不過這次分析的不是down事件,而是down之后的事件。只看關(guān)鍵部分,上面代碼的編號(hào)2部分如下:
///2.是否攔截事件,用布爾變量intercepted表示
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//這里調(diào)用onInterceptTouchEvent()看是否攔截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {//如果mFirstTouchTarget是空,而且當(dāng)前事件不是ACTION_DOWN,就攔截事件
//注意此處直接給intercepted賦值而沒有調(diào)用onInterceptTouchEvent()方法
intercepted = true;
}
首先if里面的actionMasked == MotionEvent.ACTION_DOWN肯定是不成立了,看mFirstTouchTarget != null,如果mFirstTouchTarget不是空,那么說明子View消費(fèi)了down事件,會(huì)執(zhí)行到intercepted = onInterceptTouchEvent(ev);這一行代碼。如果mFirstTouchTarget是空,說明子View沒消費(fèi)down事件,直接else里面intercepted = true;攔截事件。然后看默認(rèn)情況下的log:
( 1955): MainActivity->dispatchTouchEvent
( 1955): MyViewGroup1-->dispatchTouchEvent
( 1955): MyViewGroup1-->onInterceptTouchEvent
( 1955): MyViewGroup2--->dispatchTouchEvent
( 1955): MyViewGroup2--->onInterceptTouchEvent
( 1955): MyView--------------->dispatchTouchEvent //down事件下發(fā)過程結(jié)束
( 1955): MyView--------------->onTouchEvent // down事件消費(fèi)過程開始
( 1955): MyViewGroup2--->onTouchEvent
( 1955): MyViewGroup1-->onTouchEvent
( 1955): MainActivity->onTouchEvent
( 1955): MainActivity->dispatchTouchEvent //move1事件
( 1955): MainActivity->onTouchEvent
( 1955): MainActivity->dispatchTouchEvent //move2事件
( 1955): MainActivity->onTouchEvent
( 1955): MainActivity->dispatchTouchEvent //up事件
( 1955): MainActivity->onTouchEvent
這里頂級(jí)的ViewGroup是MainActivity(DecorView),首先down事件下發(fā)到子View,然后子View沒消費(fèi)它,又一層層交給父View消費(fèi),最終無人消費(fèi)傳回了MainActivity,down事件結(jié)束。由上面的源碼分析可知,這時(shí)的mFirstTouchTarget是空,如果move事件來了,那么直接執(zhí)行源碼編號(hào)2部分的else攔截事件,所以后續(xù)事件的log就是上面這樣不再下發(fā)(同時(shí)也沒有onInterceptTouchEvent()方法的log因?yàn)闆]調(diào)用到它)。如果子View消費(fèi)down事件,mFirstTouchTarget就不是空,后續(xù)事件的流程就與down相似了,讀者可以修改MyView的onTouchEvent()消費(fèi)掉down事件試一下消費(fèi)down事件的情況。
對(duì)于后續(xù)事件,無非就是攔截不攔截,決定權(quán)還是在編號(hào)2部分的代碼。決定的結(jié)果是是否進(jìn)入編號(hào)3的if,進(jìn)入的話,如果不是down事件的就直接跳出編號(hào)3的if了。
擴(kuò)展:由上面的分析可知,dispatchTouchEvent()方法是由父View調(diào)用的,子View是通過這個(gè)方法的返回值來告訴父View是否消費(fèi)事件了,這個(gè)大家都知道。但是,考慮一個(gè)問題,Activity -> ViewGroup1 -> ViewGroup2 -> View,如果事件按這個(gè)順序下發(fā),最終View消費(fèi)了down事件,那么Activity如何知道View是否消費(fèi)事件呢。過程必然是這樣
View.dispatchTouchEvent() 返回 true ->
ViewGroup2.dispatchTouchEvent() 返回 true ->
ViewGroup1.dispatchTouchEvent() 返回 true ->
Activity得到ViewGroup1.dispatchTouchEvent() 返回 true后就是上面分析的源碼流程了。這個(gè)過程只是猜測(cè),我沒有深入分析源碼,不過我打了個(gè)log,結(jié)果是一系列的dispatchTouchEvent()確實(shí)是返回了true。也就是說ViewGroup1和ViewGroup2并沒有消費(fèi)事件,dispatchTouchEvent()卻返回了true,那么網(wǎng)上的一種說法:dispatchTouchEvent()返回true就是消費(fèi)事件。這種說法或許并不完全準(zhǔn)確,具體還需去源碼找答案,這里先不分析了,只是說明一個(gè)問題:不要輕易重寫dispatchTouchEvent()。就算重寫,也要盡量保證調(diào)用到super方法并且返回值與super結(jié)果一致。其實(shí),源碼已經(jīng)提供了兩個(gè)接口來間接的重寫它,就是onInterceptTouchEvent()和onTouchEvent(),通過源碼可以知道它們最終都是dispatchTouchEvent()來調(diào)用的。而且用onTouchEvent()的返回值來描述是否消費(fèi)事件是沒有問題的(不消費(fèi)事件的View根本調(diào)用不到onTouchEvent())。
針對(duì)默認(rèn)情況下的log的源碼分析到此結(jié)束,理解了默認(rèn)情況,后面3種情況就簡(jiǎn)單了。
情況2:View的onTouchEvent()消費(fèi)down事件,其他默認(rèn)
(這里說的View是界面圖里的最小的子View,不是ViewGroup1或ViewGroup2)
先分析一下,由上面的默認(rèn)情況來看,對(duì)于事件機(jī)制只研究單個(gè)事件是不能說明問題的,需要看整個(gè)事件序列里每個(gè)事件是如何處理的。所以研究事件消費(fèi)需要考慮以下幾種情況:

說明一下上圖,對(duì)于圖中第1點(diǎn)不消費(fèi)down事件,其實(shí)就是情況1(默認(rèn)情況),由情況1的log可以看出,不消費(fèi)down事件的時(shí)候,后續(xù)事件不再分發(fā)給該View,所以圖中分支1對(duì)于View來說不用再考慮后續(xù)事件。
然后看圖中2,3,4,5分支,通過前面的Android事件分發(fā)流程圖可以看出,它們可以得出同一個(gè)結(jié)論,所以它們可以看成是一種情況。
分析至此,只有情況1和情況2兩種情況。對(duì)于2,3,4,5分支得出結(jié)論?這里拿圖中分支5來舉例,修改MyView的onTouchEvent()的代碼如下:
int x = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
x = 0;
Log.d(TAG, "MyView--------------->onTouchEvent x=" + x);
return true;
}
x++;
Log.d(TAG, "MyView--------------->onTouchEvent x=" + x);
if (x == 3) {
return true;
}
if (x == 5) {
return true;
}
return super.onTouchEvent(event);
}
代碼不難理解,實(shí)現(xiàn)了MyView消費(fèi)事件序列里的down事件和第3個(gè),第5個(gè)事件(從0開始計(jì)數(shù)),其他事件都不消費(fèi)。同時(shí),為了方便查看,在log中打印出了x的值。log如下(每個(gè)事件的log用空行分開了):
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent
( 1888): MyView--------------->dispatchTouchEvent // 下發(fā)過程結(jié)束
( 1888): MyView--------------->onTouchEvent x=0 // 消費(fèi)過程開始
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent
( 1888): MyView--------------->dispatchTouchEvent // 下發(fā)過程結(jié)束
( 1888): MyView--------------->onTouchEvent x=1 // 消費(fèi)過程開始
( 1888): MainActivity->onTouchEvent
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent
( 1888): MyView--------------->dispatchTouchEvent // 下發(fā)過程結(jié)束
( 1888): MyView--------------->onTouchEvent x=2 // 消費(fèi)過程開始
( 1888): MainActivity->onTouchEvent
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent
( 1888): MyView--------------->dispatchTouchEvent // 下發(fā)過程結(jié)束
( 1888): MyView--------------->onTouchEvent x=3 // 消費(fèi)過程開始
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent
( 1888): MyView--------------->dispatchTouchEvent // 下發(fā)過程結(jié)束
( 1888): MyView--------------->onTouchEvent x=4 // 消費(fèi)過程開始
( 1888): MainActivity->onTouchEvent
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent
( 1888): MyView--------------->dispatchTouchEvent // 下發(fā)過程結(jié)束
( 1888): MyView--------------->onTouchEvent x=5 // 消費(fèi)過程開始
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent
( 1888): MyView--------------->dispatchTouchEvent // 下發(fā)過程結(jié)束
( 1888): MyView--------------->onTouchEvent x=6 // 消費(fèi)過程開始
( 1888): MainActivity->onTouchEvent
上面log一共7個(gè)事件,雖然log很長(zhǎng),其實(shí)很簡(jiǎn)單,主要分為兩類:MyView消費(fèi)的和MyView沒消費(fèi)的。其中x=0,3,5是消費(fèi)的,其他未消費(fèi)。下發(fā)過程 log 中的所有事件全部一樣(和默認(rèn)情況下的down事件也一樣),為什么每次都會(huì)調(diào)用onInterceptTouchEvent(),通過源碼分析也已經(jīng)很清楚了。消費(fèi)過程的規(guī)律也很明顯,x=0,3,5的事件消費(fèi)掉就沒了,其他沒消費(fèi)的事件直接傳給頂級(jí)父View而不是一層層傳回去。
情況3:ViewGroup2的onTouchEvent()消費(fèi)down事件
首先,由情況1的log可以看出(如果理解了默認(rèn)情況,這時(shí)候是沒必要回去翻log的,腦中自然形成),要想出現(xiàn)當(dāng)前的情況3,首先得讓View的onTouchEvent()不消費(fèi)down事件(如果消費(fèi)就是情況2了,已經(jīng)分析完),同時(shí),由于View的onTouchEvent()不消費(fèi)down事件,那么后續(xù)事件都不再傳給View,也就是說沒View什么事了,所以界面相當(dāng)于變成了下圖這樣:

看圖和標(biāo)題發(fā)現(xiàn)了什么,變成情況2了,那就簡(jiǎn)單了,直接看log,下面是ViewGroup2的onTouchEvent()消費(fèi)down事件,后續(xù)事件都不消費(fèi)的log(相當(dāng)于情況2那個(gè)圖的分支2,可以順便驗(yàn)證上面分支5得出的結(jié)論):
( 2008): MainActivity->dispatchTouchEvent
( 2008): MyViewGroup1-->dispatchTouchEvent
( 2008): MyViewGroup1-->onInterceptTouchEvent
( 2008): MyViewGroup2--->dispatchTouchEvent
( 2008): MyViewGroup2--->onInterceptTouchEvent
( 2008): MyView--------------->dispatchTouchEvent
( 2008): MyView--------------->onTouchEvent
( 2008): MyViewGroup2--->onTouchEvent
( 2008): MainActivity->dispatchTouchEvent
( 2008): MyViewGroup1-->dispatchTouchEvent
( 2008): MyViewGroup1-->onInterceptTouchEvent
( 2008): MyViewGroup2--->dispatchTouchEvent
( 2008): MyViewGroup2--->onTouchEvent
( 2008): MainActivity->onTouchEvent
( 2008): MainActivity->dispatchTouchEvent
( 2008): MyViewGroup1-->dispatchTouchEvent
( 2008): MyViewGroup1-->onInterceptTouchEvent
( 2008): MyViewGroup2--->dispatchTouchEvent
( 2008): MyViewGroup2--->onTouchEvent
( 2008): MainActivity->onTouchEvent
( 2008): MainActivity->dispatchTouchEvent
( 2008): MyViewGroup1-->dispatchTouchEvent
( 2008): MyViewGroup1-->onInterceptTouchEvent
( 2008): MyViewGroup2--->dispatchTouchEvent
( 2008): MyViewGroup2--->onTouchEvent
( 2008): MainActivity->onTouchEvent
log中需要注意down事件經(jīng)歷了一次MyView的onTouchEvent()方法,down之后的事件不再執(zhí)行ViewGroup2的onInterceptTouchEvent()方法。原因在源碼分析中已經(jīng)給出,結(jié)論在前兩種情況已經(jīng)得出,沒什么好解釋的了。
由上面分析可知,本情況是可以算成情況2的,只是感覺單獨(dú)把它分出來邏輯更清楚合理一些,分析情況4的時(shí)候也會(huì)解釋一些原因,同時(shí),這也是對(duì)前兩種情況的一個(gè)運(yùn)用,如果理解了情況1和情況2,其他很多本文沒提到的情況都能解釋通的。
情況4:ViewGroup2的onInterceptTouchEvent()攔截down之后的事件
前面研究的都是onTouchEvent()消費(fèi)相關(guān)的情況,這里研究onInterceptTouchEvent()事件下發(fā)攔截。
首先仔細(xì)看標(biāo)題,為什么不考慮攔截down事件而只考慮攔截down之后的事件。因?yàn)閿r截down事件之后,事件直接交給自身的onTouchEvent()處理,不再經(jīng)過子View,也就變成了情況3的界面(注意這里是變成情況3的界面而不是變成情況3,內(nèi)部流程與情況3是有區(qū)別的,后面解釋)。變成了情況3的界面之后,回調(diào)到自身的onTouchEvent()又分為不消費(fèi)down和消費(fèi)down:不消費(fèi)down就是情況1;消費(fèi)down就是情況2。
這里解釋為什么 “是變成情況3的界面而不是變成情況3”,這里的情況是ViewGroup2的onInterceptTouchEvent()攔截down之后,down直接交給ViewGroup2的onTouchEvent()去消費(fèi)。而情況3是沒有攔截,down事件先去子View溜了一圈,發(fā)現(xiàn)子View不消費(fèi)它,然后才傳遞給父親ViewGroup2去消費(fèi)。
說明了有兩種方式可以讓ViewGroup2消費(fèi)到down事件。這也是把情況3從情況2分離出來的一個(gè)原因。
上面的分析說明攔截down后必然出現(xiàn)前面的三種情況之一,所以這里不再考慮攔截down。下面看看攔截down之后的事件會(huì)有什么不同。
修改ViewGroup2的onInterceptTouchEvent()代碼如下:
int x = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG, "MyViewGroup2--->onInterceptTouchEvent x="+x);
if (x==3) {
x++;
return true;
}
x++;
return super.onInterceptTouchEvent(ev);
}
代碼很簡(jiǎn)單,當(dāng)x==3的時(shí)候攔截事件,也就是攔截事件序列里的第4個(gè)事件,log如下:
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent x=0
( 1888): MyView--------------->dispatchTouchEvent
( 1888): MyView--------------->onTouchEvent
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent x=1
( 1888): MyView--------------->dispatchTouchEvent
( 1888): MyView--------------->onTouchEvent
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent x=2
( 1888): MyView--------------->dispatchTouchEvent
( 1888): MyView--------------->onTouchEvent
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onInterceptTouchEvent x=3
( 1888): MyView--------------->dispatchTouchEvent
( 1888): MyView--------------->onTouchEvent
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onTouchEvent
( 1888): MainActivity->onTouchEvent
( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1-->dispatchTouchEvent
( 1888): MyViewGroup1-->onInterceptTouchEvent
( 1888): MyViewGroup2--->dispatchTouchEvent
( 1888): MyViewGroup2--->onTouchEvent
( 1888): MainActivity->onTouchEvent
log需要分析3個(gè)地方:
x=3之前的事件,很好理解,就是情況2。
x=3的時(shí)候的事件,x=3的時(shí)候ViewGroup2已經(jīng)攔截事件了,為什么MyView還是收到了一個(gè)事件?如果用log把MyView收到的這個(gè)事件的action值打印出來的話發(fā)現(xiàn)它是ACTION_CANCEL這個(gè)事件,它表示非人為原因結(jié)束本次事件。對(duì)于ACTION_CANCEL事件我舉一個(gè)例子,一般我們處理事件的時(shí)候會(huì)在down事件記錄一些東西,然后在up事件清掉記錄。那么如果在ListView中有一個(gè)Button,我們按住Button的時(shí)候按鈕收到down事件,這時(shí)手指開始滑動(dòng),事件就會(huì)被ListView攔截處理了,這時(shí)候按鈕就再也收不到up事件了,但是ListView攔截事件的時(shí)候按鈕會(huì)收到cancel事件表示非人為原因結(jié)束本次事件,所以這時(shí)可以把up事件要做的事情讓cancel來做。
x=3之后的事件,代碼中只改了onInterceptTouchEvent(),而onTouchEvent()是默認(rèn)返回,也就是說ViewGroup2攔截事件之后并沒有消費(fèi),從log也可以看出,每個(gè)事件都傳回了Activity。但是,ViewGroup2不消費(fèi)事件為什么后續(xù)事件還是不斷的分發(fā)給ViewGroup2,而且執(zhí)行到它的onTouchEvent()方法?(看似好像違背了默認(rèn)情況的規(guī)則),這是一個(gè)問題,也是需要注意的一點(diǎn)。
首先,事件為什么分發(fā)到ViewGroup2?其實(shí)源碼分析中已經(jīng)解釋了,當(dāng)有子View消費(fèi)掉down事件后,(MainActivity的)mFirstTouchTarget就不再是空,后續(xù)事件都會(huì)被下發(fā)到子View(遞歸),除非父View的onInterceptTouchEvent()主動(dòng)攔截事件,而例子中ViewGroup2的父View和Actvity并沒有攔截事件。
然后是為什么總是調(diào)到ViewGroup2的onTouchEvent()方法,原因是ViewGroup2沒有處理事件的子View了,所以會(huì)調(diào)用到ViewGroup2的super.dispatchTouchEvent(),也就是調(diào)用父類View.dispatchTouchEvent()方法,所以如果沒有設(shè)置onTouchListener就必然會(huì)調(diào)用到onTouchEvent()了。
結(jié)束語
文章到這里就分析完了,這時(shí)候再看Android事件分發(fā)流程圖,此圖就是根據(jù)上面4種情況畫出來的,如果理解了那4種情況(至少要做到給出一種攔截消費(fèi)方式,能想到每個(gè)事件的log是什么樣的),那么看懂這張圖應(yīng)該是不難的。

關(guān)于結(jié)論,這里就不給出純文字描述了(即使描述出來也是很復(fù)雜的,一堆if),《Android開發(fā)藝術(shù)探索》中給了11條結(jié)論,有興趣的可以看一下。但是,上圖中有一些文字描述,可以當(dāng)作結(jié)論,主要是綠色箭頭對(duì)應(yīng)的兩部分,描述的是各個(gè)事件最終的去向,圖中的各個(gè)分支箭頭,就是各種情況下的條件。
最后,文中有沒考慮到的情況或不對(duì)的地方歡迎留言討論。另外,對(duì)于源碼,文中只給出了下發(fā)過程的分析。消費(fèi)過程就不再給出詳細(xì)分析了,提供一個(gè)思路,文中的源碼分析里有一行注釋 //下面消費(fèi)事件相關(guān)的代碼,省略,讀者可以從這里開始看消費(fèi)相關(guān)的代碼,注意這是ViewGroup類里的dispatchTouchEvent(),這行注釋下面會(huì)有相關(guān)的邏輯調(diào)用到dispatchTransformedTouchEvent()這個(gè)方法(下發(fā)過程也調(diào)到了這個(gè)方法),這個(gè)方法里會(huì)有super.dispatchTouchEvent(event)這樣的代碼調(diào)用到ViewGroup類的父類View里面的dispatchTouchEvent(),然后看View里面的dispatchTouchEvent(),就會(huì)發(fā)現(xiàn)onTouchEvent()被調(diào)用了,同時(shí)也會(huì)發(fā)現(xiàn)一些其他的東西,比如onTouchListener()和onTouchEvent()的優(yōu)先級(jí)。
關(guān)于dispatchTransformedTouchEvent()再解釋一下,里面有兩種代碼:super.dispatchTouchEvent(event)和child.dispatchTouchEvent(event)。簡(jiǎn)單理解,前者就是調(diào)用ViewGroup自身的onTouchEvent()去消費(fèi),后者是將事件分發(fā)給(界面上的)子View去處理。如果子View是ViewGroup,那么子View處理事件的流程和父View類似,從ViewGroup的dispatchTouchEvent()開始執(zhí)行;如果子View不是ViewGroup,那么直接執(zhí)行View的dispatchTouchEvent()邏輯。而View的dispatchTouchEvent()里并沒有將事件傳給父View去消費(fèi)的代碼,其實(shí)消費(fèi)過程事件不是傳遞的,而是根據(jù)子View的dispatchTouchEvent()返回值和一些ViewGroup自身的記錄來決定是否調(diào)用super.dispatchTouchEvent(event)來調(diào)用自身的onTouchEvent()。好了,思路就提供到這里。
解釋評(píng)論區(qū)的一個(gè)問題:
問題:情況2分析中最后一段話“消費(fèi)過程的規(guī)律也很明顯,x=0,3,5的事件消費(fèi)掉就沒了,其他沒消費(fèi)的事件直接傳給頂級(jí)父View而不是一層層傳回去?!?這里是為什么呢?
先看下Activity里面的dispatchTouchEvent()源碼
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
不難看出activity的onTouchEvent()能否被調(diào)用取決于if條件getWindow().superDispatchTouchEvent(ev)。
從前面的源碼分析那可以知道一點(diǎn):dispatchTouchEvent()方法是由父View調(diào)用的,子View是通過這個(gè)方法的返回值來告訴父View是否消費(fèi)事件了(具體看源碼分析編號(hào)7那里,還有情況1末尾的擴(kuò)展那一部分)。
所以在這個(gè)問題中,對(duì)于不消費(fèi)的事件,每一層View/ViewGroup的dispatchTouchEvent()返回值如下:
MyView.dispatchTouchEvent() 返回 false ->
ViewGroup2.dispatchTouchEvent() 返回 false ->
ViewGroup1.dispatchTouchEvent() 返回 false ->
......
最終根View,DecorView.dispatchTouchEvent() 返回 false
這個(gè)過程由于每個(gè)View都不消費(fèi)事件,它們的onTouchEvent()都沒被調(diào)用到,具體的代碼就不分析了。
然后看Activity的dispatchTouchEvent()源碼,getWindow().superDispatchTouchEvent(ev),這個(gè)方法是Window抽象類類的方法,大家都知道Window的實(shí)現(xiàn)類是PhoneWindow,所以直接看PhoneWindow的superDispatchTouchEvent()方法:
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
ctrl+左鍵跳到DecorView.java的superDispatchTouchEvent()方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
ctrl+左鍵再跳就是ViewGroup的dispatchTouchEvent()方法。上面說了,對(duì)于不消費(fèi)的事件,這一系列的方法都返回false,所以最終Activity的dispatchTouchEvent()方法里面,if(getWindow().superDispatchTouchEvent(ev))條件是false,執(zhí)行return onTouchEvent(ev);
要看PhoneWindow代碼卻沒有源碼的可以試試這個(gè)方法:如何查看FrameWork層源碼(例如:PhoneWindow) android.jar