Android 事件分發(fā)流程淺析
事件分發(fā)流程模型
假設(shè)當(dāng)前Activity中存在兩個ViewGroup,每一個ViewGroup中各自有3個View。大致的模型是一個這樣的結(jié)構(gòu):

事件分發(fā)流程的四個特點
事件的分發(fā)流程是通過【傳播鏈】進行的,最頂層是Activity,最下層是View
事件傳播鏈不能跨級別,只能逐級別發(fā)送
傳播鏈形成之后,會按照之前的傳播鏈直接進行(傳播鏈具有記憶性)
傳播鏈行程后,View可以通過api影響傳播鏈
相對上層的View具有兩次對于事件的處理機會
剛收到事件
沒有子View處理事件
事件的種類
ACTION_DOWN
ACTION_MOVE
ACTION_UP
ACTION_CANCEL
前言:View的事件處理邏輯
ViewGroup也是一個View,在這個事件分發(fā)的過程中,不免會調(diào)用到View的dispatchTouchEvent方法, 我們首先分析一下這個。
View#dispatchedTouchEvent
這塊的邏輯比較單純,總結(jié)來說,如果是設(shè)置了touchListener的話,就回執(zhí)行onTouch回調(diào);否則就回看看onTouchEvent咋寫的。
View#setOnTouchListener中的lambda的返回值會影響onClick
設(shè)置的setOnTouchListener的結(jié)果會影響onClick的執(zhí)行。我們同時也分析一下這個過程。
在上面的代碼中,如果沒有設(shè)置onTouchEvent,result默認(rèn)為false,所以會執(zhí)行onTouchEvent,在onTouchEvent中,當(dāng)motionEvent為action_up時,會調(diào)用performClickInternal():
在performClick中才會調(diào)用onClick。所以onClick的執(zhí)行在onTouch之后,同時受到OnTouchListener的返回值的影響。在OnClickListener中手動調(diào)用performClick可以緩解這個問題。
事件分發(fā)流程的代碼解析
事件分發(fā)流程的執(zhí)行過程是從Activity開始的,到View結(jié)束,具體的傳播鏈(調(diào)用鏈)如下:

Activity在收到事件之后,其實會交給PhoneWindow處理。
PhoneWindow中持有DecorView,然后會調(diào)用DecorView的相關(guān)方法:
DecorView其實是一個FrameLayout,因此現(xiàn)在調(diào)用的重點就進入了ViewGroup#dispatchTouchEvent。
ViewGroup#dispatchTouchEvent?
ACTION_DOWN事件來臨
滑動觸摸的起始一般是從down事件開始,緊接著是一連串的move事件,最后以up事件結(jié)束。事件的傳遞機制非常的復(fù)雜,其中的細節(jié)點非常多,我通過圖示的方式也很難很好地呈現(xiàn),所以我想將這個ViewGroup#dispatchTouchEvent拆分成很多小塊,揉碎了來講解,在最后再進行回顧。
首先我們先看看當(dāng)ACTION_DOWN到來時的代碼邏輯:
Case#1 action_down & onInterceptTouchEvent 返回true
假設(shè)此時在ViewGroup2中重寫了onInterceptTouchEvent返回true的邏輯,我們看下面的代碼,代碼高度簡化后就是這樣的邏輯:
在這個情況下,會調(diào)用dispatchTransformedTouchEvent,其中child!=null的情況,我們緊接著具體看看:
super指的就是View#dispatchTouchEvent,上面已經(jīng)分析過。
Case#1 action_down & onInterceptTouchEvent 返回false
如果是返回false的話,就需要執(zhí)行if語句。在這個if語句中,會構(gòu)建當(dāng)前ViewGroup的View的優(yōu)先級,具體是通過z坐標(biāo)來排序的。這個過程通過buildTouchDispatchChildList()實現(xiàn)。
for循環(huán)中,主要的邏輯是這樣:
首先通過調(diào)用dispatchTransformedTouchEvent進行分發(fā), 如果返回true,就進行一些變量的設(shè)置。
這個地方,我個人認(rèn)為是事件分發(fā)的機制的精髓所在。因為dispatchTransformedTouchEvent讓child!=null時,會調(diào)用子View的dispatchTouchEvent,在我們的例子中,當(dāng)前的ViewGroup2的下面全部都是子View,但是在更復(fù)雜的情況下,ViewGroup2中會有嵌套更加深層的ViewGroup。通過這樣的分析,我們可以簡單把這里理解為【遞歸】的開始。
回到我們這個簡單的例子,如果ViewGroup2傳到View1,View1選擇消費事件,View#dispatchTouchEvent返回true,這里就進入if條件(不過如果有更深層的View嵌套,具體的分析也大差不差)

會設(shè)置:

并且中斷當(dāng)前的for循環(huán)。此時在后續(xù)的條件判斷中,會走if的第二個分支,設(shè)置handled為true并且返回給Activity,這樣就達成了一次從Activity-> ViewGroup -> View的事件傳遞。
Case#3?action_move
在后續(xù)的事件中,同樣會從Activity開始分發(fā),然后走到ViewGroup這里。
因為不是滑動事件的開始(action_down),因此不會重新構(gòu)建View的列表。
而且在第一次的過程中,有些重要的變量,如mFirstTouchTarget已經(jīng)被賦值了,所以邏輯會稍微有些改變,在后續(xù)的流程中會繼續(xù)dispatchTransformedTouchEvent,這樣其實又是一個【遞歸】的過程。
同時需要注意一點的是,在子View中可以通過getParent().reqeustDisallowInterceptTouchEvent進行事件攔截,這樣就不會調(diào)用父ViewGroup的onInterceptTouchEvent。

實踐例子
我直接使用老師上課的那個例子說明一下問題。在老師的例子中,只用了一個ViewPager作為ViewGroup,其中包含了一個ListView。繼承自ViewPager的ViewGroup中重寫了onInterceptTouchEvent,返回true。
改動1
此時,對于down事件,取消true;對于move事件按照水平滑動的閾值進行調(diào)整。但是這樣改動之后,還是只能水平滑動,不能垂直滑動。

這是因為ViewGroup中的返回true,直接使得onIntercept執(zhí)行,不可能通過dispatch傳到子View中,子View就不能通過設(shè)置disallow的flag。這也就是四個特性中的,需要注意讓【傳播鏈】形成。
改動2
改動ViewGroup的onInterceptTouchEvent,改動如下。不能攔截down事件,需要讓【傳播鏈】完成。這樣就可以左右滑動奏效了。

總結(jié)
事件傳遞過程主要有四個特性
事件逐級傳遞
上層ViewGroup可以通過onInterceptTouchEvent干預(yù)事件接收
傳播鏈形成后,后續(xù)事件按照傳播鏈傳播,不會重新構(gòu)建
傳播鏈形成后,可以通過子View設(shè)置disallowIntercept的flag
是這樣的傳播鏈
action_down &?!intercept = false,此時傳播鏈沒有構(gòu)建mFirstTouchTarget=null,會調(diào)用View的方法(View這里會消費事件,并且應(yīng)該會返回true?我猜的)
action_down &?!intercept = true,構(gòu)建view的列表,并且【遞歸】交給子view處理
action_move 傳播鏈構(gòu)件好了,mFirstTouchTarget != null,返回true

????