Android事件分發機制探究

Anumbrella發表於2017-03-19

瞭解Android中的事件分發機制是Android開發人員進階的必要知識,網上其實也有很多的文章介紹,但要想好好理解它,除了要有一定的耐心外還要去親自實踐。同時這個事件分發機制的博文也不好寫,要在短短的文字介紹中讓讀者理解,確實不容易,但為了加深理解於是就寫了這篇文章。

首先我們介紹一下點選事件的分發,說白了就是對MotionEvent事件的分發過程。當MotionEvent產生後,系統需要把這個事件傳遞給一個具體的view,而這個過程就是所謂的分發過程。

在事件分發中,主角有兩個,一個是ViewGroup,另一個則是View。

在ViewGroup中主要涉及三個函式:
dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()

而在View中則主要涉及兩個函式:
dispatchTouchEvent()、onTouchEvent()

我們先來熟悉一下這三個函式的功能:
dispatchTouchEvent()是來進行事件的分發。如果事件能夠傳遞給當前View,那麼此方法一定會被呼叫。返回不同結果會有不同影響,後面再進行分析。

onInterceptTouchEvent()只有在ViewGroup中才有,用來判斷是否要攔截某個事件。如果攔截,那麼在同一個事件序列當中,此方法不會被再次呼叫(即下次預設直接呼叫dispatchTouchEvent()來進行事件的分發,不會再詢問是否還要攔截事件),返回結果表示是否要攔截當前事件。

onTouchEvent()是在dispatchEvent()方法中呼叫的,表示用來處理點選事件,返回結果表示是消耗當前事件。如果不消耗,則在同一個事件序列中,當前的View無法再次接收到事件。

這裡補充一下:同一事件序列是指在手指觸控手機螢幕到離開過程,產生的一系列的DOWN、MOVE、UP事件,這個序列事件以DOWN開始,中間包含不定的MOVE事件,最後以UP結束。

首先我們知道當一個事件產生後,它是根據Activity->Window->View的順序來傳遞的。即事件總是先傳給Activity,再由Activity傳給Window,最後由Window傳遞給頂級的View。

Window會將事件傳遞給Decor View,而這個Decor View一般就是當前介面的底層容器。

首先當頂級的View獲得事件後,這裡的事件均是指ACTION_DWON。ACTION_MOVE,ACTION_UP與ACTION_DWON不同,後面會再進行分析。其實這裡的ACTION_DWON就是確定在哪一個控制元件中消耗剩下的一系列事件用的,以便後面把事件傳遞過去。

現在我們先建立自定義的檢視佈局如下:
佈局

然後在各個方法裡列印日誌。有了上面方法的分析後,再通過日誌我們便可以知道事件分發的大致流程。

比如我們點選Button1,則最先呼叫Activity的dispatchTouchEvent(),然後通過ViewGroup1的dispatchTouchEvent(),如果ViewGroup1不攔截事件,即onInterceptTouchEvent()返回為false,(onInterceptTouchEvent()預設返回的也是false),就會呼叫
ViewGroup2的dispatchTouchEvent(),同理如果ViewGroup2也不攔截,就會呼叫Button1的dispatchTouchEvent(),然後把事件傳遞給onTouchEvent()去執行。

這個就是預設情況下事件分發的流向。

接下來我們來分析那三個重要函式的返回值,產生的影響。


在ViewGroup中的dispatchTouchEvent()方法裡:
如果返回的是super.dispatchTouchEvent(event),則會對事件進行向下分發,比如上面的ViewGroup1傳遞給ViewGroup2一樣;
如果返回的是true——表示ViewGroup的dispatTouchEvent()方法自己消費掉該事件,它也不會把事件傳遞給子檢視(如ViewGroup2),也不會把事件傳遞給onTouchEvent()。並且後續的事件也只會傳遞給dispatchTouchEvent(),即事件在這裡就終止了,不會向下分發;
如果返回的是false——表示事件會傳遞給它的上一級的onTouchEvent()方法,並且後續事件不會再傳到該方法中,而是直接傳遞給它上一級的onTouchEvent()方法中。比如在ViewGroup2的dispatchTouchEvent()方法返回false,則就會呼叫ViewGroup1的onTouchEvent()方法,而在ViewGroup1返回false,就會呼叫Activity的onTouchEvent()方法。


而在View的dispatchTouchEvent()中基本一致,只是當返回super.dispatchTouchEvent(event)時,是直接傳遞給自己的onTouchEvent()方法,而不是傳遞給子檢視(View也沒有子檢視)。


在ViewGroup中的onInterceptTouchEvent()方法裡:
在前面dispatchTouchEvent()方法中,無論我們返回什麼,ViewGroup的onTouchEvent()都沒有執行。那是因為dispatchTouchEvent()要通過onInterceptTouchEvent()來實現事件傳遞給onTouchEvent()。
如果返回的是true——表示ViewGroup要對事件進行攔截,此時dispatchTouchEvent()方法便會把事件傳遞給ViewGroup的onTouchEvent(),並且下次不會再呼叫onInterceptTouchEvent()方法了,後續事件dispatchTouchEvent()會預設傳給onTouchEvent()。
如果返回的是false——表示ViewGroup不會對事件進行攔截。預設返回的就是false,此時就可以向下進行事件分發。


在onTouchEvent()方法裡:
如果返回的是true——表示控制元件(View或ViewGroup)要消耗該事件。事件傳遞到此為止,後續的事件也會陸續傳遞過來。
如果返回的是false——表示不消耗該事件。此時就會傳遞給上一級檢視的onTouchEvent()。比如在Button1的onTouchEvent()返回false,預設就會呼叫ViewGroup2的onTouchEvent()方法。如果如果所有元素都沒處理該事件,就會返回到Activity的onTouchEvent()方法中。

注意:View的onTouchEvent()預設都會消耗事件(返回true,即super.onTuchEvent()==true),除非它是不可點選的(clickable和longClickable同時為false)。View的longClickable屬性預設為false,click屬性則分情況,Button為true,TextView為false。

通過上面的講解,相信對事件分發有了一定的認識了。其實整個事件分發先從上到下,再從下到上執行。完成一個U型的流程。如下圖:

流程圖

以上便是針對ACTON_DOWN的事件分發流程。

關於ACTION_MOVE與ACTION_UP的事件分發

ACTION_DOWN與ACTION_MOVE、ACTION_UP的分發是不一樣的,在上面的講解中我們針對的ACTION_DOWN的情況。ACTION_DOWN就像先行者,找到我們要消費的具體控制元件,後面才把一系列的事件傳遞過去。

因此只要收到down事件的控制元件,才會獲取到後面的move、up事件。簡單的說,就是當dispatchTouchEvent()在進行事件分發的時候,只有前一個事件(如ACTION_DOWN)返回true,才會收到ACTION_MOVE和ACTION_UP的事件。

接下來我們來驗證一下:
黑色箭頭表示:down事件傳遞方向
紅色箭頭表示:move、up事件傳遞方向
1、在ViewGroup1的dispatchTouchEvent()方法返回true,根據列印的日誌我們可以畫出down、move、up事件的流向。如下圖所示:
demo3

2、在ViewGroup2的dispatchTouchEvent()方法返回true。如下圖所示:
demo4

3、在View的dispatchTouchEvent()方法返回true。(這裡我們選擇Button1中的方法返回)。如下圖所示:
demo5

通過上面的分析我們可以得出結論:
如果在某個控制元件的dispatchEvent中返回true消費終結事件,那麼收到ACTION_DWON的函式也能收到ACTION_MOVE、ACTION_UP。

4、在ViewGroup1的onTouchEvent()方法中返回true,在Button1的onTouchEvent()返回false。我們觀察結果圖如下:
demo6

可以看到move、up事件是直接傳遞到down事件到達的控制元件的onTouchEvent()方法中的。

比如我們在ViewGroup2的onTouchEvent()方法中返回true,同樣的在Button1的onTouchEvent()返回false。結果如下圖:
demo7

我們可以得到結論:
ACTION_DOWN事件在哪個控制元件消費了(return true), 那麼ACTION_MOVE和ACTION_UP就會從上往下(通過dispatchTouchEvent())做事件分發往下傳,就只會傳到這個控制元件,不會繼續往下傳。就像前面提到的一樣,down事件像一個先行的開路者,到達後再把目的地告訴後面的事件。

5、在ViewGroup1的onTouchEvent()方法中返回true,同時在Button1中的dispatchTouchEvent()中返回false。結果如下圖:
demo8

跟前面提到的分析結果一致,同時我們可以看到在dispatchTouchEvent()返回false,會呼叫它上一級的onTouchEvent()方法,從而跳過自己的方法。

6、在ViewGroup1的onTouchEvent()方法中返回true,同時在ViewGroup2的onInterceptTouchEvent()方法中返回ture。圖如下所示:
demo9

講了這麼多,最後也畫了很多的圖,我想大致的Anroid事件的大致流程應該講解清楚了。歡迎,留言!

相關文章