圖解 Android 事件分發機制

在Android開發中,事件分發機制是一塊Android比較重要的知識體系,了解并熟悉整套的分發機制有助于更好的分析各種點擊滑動失效問題,更好去擴展控件的事件功能和開發自定義控件,同時事件分發機制也是Android面試必問考點之一,如果你能把下面的一些事件分發圖當場畫出來肯定加分不少。廢話不多說,總結一句:事件分發機制很重要。

Android 事件分發流

關于Android 事件分發機制網上的博文很多,但是很多都是寫個Demo然后貼一下輸出的Log或者拿源碼分析,然后一堆的注釋和說明,如果用心的去看肯定是收獲不少但是確實很難把整個流程說清和記住。曾經也是拼命想記住整個流程,但是一段時間又忘了,最后覺得分析這種問題和事件流的走向,一張圖來解釋和說明會清晰很多,下面我根據畫的一張事件分發流程圖,說明的事件從用戶點擊之后,在不同函數不同返回值的情況的最終走向。

圖?1

注:仔細看的話,圖分為3層,從上往下依次是Activity、ViewGroup、View

事件從左上角那個白色箭頭開始,由Activity的dispatchTouchEvent做分發

箭頭的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是調用父類實現。

dispatchTouchEvent和 onTouchEvent的框里有個【true---->消費】的字,表示的意思是如果方法返回true,那么代表事件就此消費,不會繼續往別的地方傳了,事件終止。

目前所有的圖的事件是針對ACTION_DOWN的,對于ACTION_MOVE和ACTION_UP我們最后做分析。

之前圖中的Activity 的dispatchTouchEvent 有誤(圖已修復),只有return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消費了(終止傳遞)。

仔細看整個圖,我們得出事件流 走向的幾個結論(希望讀者專心的看下圖 1,多看幾遍,腦子有比較清晰的概念。)

1、如果事件不被中斷,整個事件流向是一個類U型圖,我們來看下這張圖,可能更能理解U型圖的意思。

圖 2

所以如果我們沒有對控件里面的方法進行重寫或更改返回值,而直接用super調用父類的默認實現,那么整個事件流向應該是從Activity---->ViewGroup--->View 從上往下調用dispatchTouchEvent方法,一直到葉子節點(View)的時候,再由View--->ViewGroup--->Activity從下往上調用onTouchEvent方法。

2、dispatchTouchEvent 和 onTouchEvent 一旦return true,事件就停止傳遞了(到達終點)(沒有誰能再收到這個事件)??聪聢D中只要return true事件就沒再繼續傳下去了,對于return true我們經常說事件被消費了,消費了的意思就是事件走到這里就是終點,不會往下傳,沒有誰能再收到這個事件了

圖 3.

3、dispatchTouchEvent 和 onTouchEvent return false的時候事件都回傳給父控件的onTouchEvent處理。

圖 4.

看上圖深藍色的線,對于返回false的情況,事件都是傳給父控件onTouchEvent處理。

對于dispatchTouchEvent 返回 false 的含義應該是:事件停止往子View傳遞和分發同時開始往父控件回溯(父控件的onTouchEvent開始從下往上回傳直到某個onTouchEvent return true),事件分發機制就像遞歸,return false 的意義就是遞歸停止然后開始回溯。

對于onTouchEvent return false 就比較簡單了,它就是不消費事件,并讓事件繼續往父控件的方向從下往上流動。

4、dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent

ViewGroup 和View的這些方法的默認實現就是會讓整個事件安裝U型完整走完,所以 return super.xxxxxx() 就會讓事件依照U型的方向的完整走完整個事件流動路徑),中間不做任何改動,不回溯、不終止,每個環節都走到。


所以如果看到方法return super.xxxxx() 那么事件的下一個流向就是走U型下一個目標,稍微記住上面這張圖,你就能很快判斷出下一個走向是哪個控件的哪個函數。

5、onInterceptTouchEvent 的作用

圖 5.

Intercept 的意思就攔截,每個ViewGroup每次在做分發的時候,問一問攔截器要不要攔截(也就是問問自己這個事件要不要自己來處理)如果要自己處理那就在onInterceptTouchEvent方法中 return true就會交給自己的onTouchEvent的處理,如果不攔截就是繼續往子控件往下傳。默認是不會去攔截的,因為子View也需要這個事件,所以onInterceptTouchEvent攔截器return super.onInterceptTouchEvent()和return false是一樣的,是不會攔截的,事件會繼續往子View的dispatchTouchEvent傳遞。

6、ViewGroup 和View 的dispatchTouchEvent方法返回super.dispatchTouchEvent()的時候事件流走向。

圖 6

首先看下ViewGroup 的dispatchTouchEvent,之前說的return true是終結傳遞。return false 是回溯到父View的onTouchEvent,然后ViewGroup怎樣通過dispatchTouchEvent方法能把事件分發到自己的onTouchEvent處理呢,return true和false 都不行,那么只能通過Interceptor把事件攔截下來給自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super默認實現就是去調用onInterceptTouchEvent,記住這一點

那么對于View的dispatchTouchEvent return super.dispatchTouchEvent()的時候呢事件會傳到哪里呢,很遺憾View沒有攔截器。但是同樣的道理return true是終結。return false 是回溯會父類的onTouchEvent,怎樣把事件分發給自己的onTouchEvent 處理呢,那只能return super.dispatchTouchEvent,View類的dispatchTouchEvent()方法默認實現就是能幫你調用View自己的onTouchEvent方法的。

說了這么多,不知道有說清楚沒有,我這邊最后總結一下:

對于 dispatchTouchEvent,onTouchEvent,return true是終結事件傳遞。return false 是回溯到父View的onTouchEvent方法。

ViewGroup 想把自己分發給自己的onTouchEvent,需要攔截器onInterceptTouchEvent方法return true 把事件攔截下來。

ViewGroup 的攔截器onInterceptTouchEvent 默認是不攔截的,所以return super.onInterceptTouchEvent()=return false;

View 沒有攔截器,為了讓View可以把事件分發給自己的onTouchEvent,View的dispatchTouchEvent默認實現(super)就是把事件分發給自己的onTouchEvent。

ViewGroup和View 的dispatchTouchEvent 是做事件分發,那么這個事件可能分發出去的四個目標

注:------> 后面代表事件目標需要怎么做。

1、 自己消費,終結傳遞。------->return true ;

2、 給自己的onTouchEvent處理-------> 調用super.dispatchTouchEvent()系統默認會去調用 onInterceptTouchEvent,在onInterceptTouchEvent return true就會去把事件分給自己的onTouchEvent處理。

3、 傳給子View------>調用super.dispatchTouchEvent()默認實現會去調用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就會把事件傳給子類。

4、 不傳給子View,事件終止往下傳遞,事件開始回溯,從父View的onTouchEvent開始事件從下到上回歸執行每個控件的onTouchEvent------->return false;

注:由于View沒有子View所以不需要onInterceptTouchEvent 來控件是否把事件傳遞給子View還是攔截,所以View的事件分發調用super.dispatchTouchEvent()的時候默認把事件傳給自己的onTouchEvent處理(相當于攔截),對比ViewGroup的dispatchTouchEvent 事件分發,View的事件分發沒有上面提到的4個目標的第3點。

ViewGroup和View的onTouchEvent方法是做事件處理的,那么這個事件只能有兩個處理方式:

1、自己消費掉,事件終結,不再傳給誰----->return true;

2、繼續從下往上傳,不消費事件,讓父View也能收到到這個事件----->return false;View的默認實現是不消費的。所以super==false。

ViewGroup的onInterceptTouchEvent方法對于事件有兩種情況:

1、攔截下來,給自己的onTouchEvent處理--->return true;

2、不攔截,把事件往下傳給子View---->return false,ViewGroup默認是不攔截的,所以super==false;

關于ACTION_MOVE 和 ACTION_UP

上面講解的都是針對ACTION_DOWN的事件傳遞,ACTION_MOVE和ACTION_UP在傳遞的過程中并不是和ACTION_DOWN 一樣,你在執行ACTION_DOWN的時候返回了false,后面一系列其它的action就不會再得到執行了。簡單的說,就是當dispatchTouchEvent在進行事件分發的時候,只有前一個事件(如ACTION_DOWN)返回true,才會收到ACTION_MOVE和ACTION_UP的事件。具體這句話很多博客都說了,但是具體含義是什么呢?我們來看一下下面的具體分析。

上面提到過了,事件如果不被打斷的話是會不斷往下傳到葉子層(View),然后又不斷回傳到Activity,dispatchTouchEvent 和 onTouchEvent 可以通過return true 消費事件,終結事件傳遞,而onInterceptTouchEvent 并不能消費事件,它相當于是一個分叉口起到分流導流的作用,ACTION_MOVE和ACTION_UP 會在哪些函數被調用,之前說了并不是哪個函數收到了ACTION_DOWN,就會收到 ACTION_MOVE 等后續的事件的。

下面通過幾張圖看看不同場景下,ACTION_MOVE事件和ACTION_UP事件的具體走向并總結一下規律。

1、我們在ViewGroup1 的dispatchTouchEvent 方法返回true消費這次事件

ACTION_DOWN 事件從(Activity的dispatchTouchEvent)--------> (ViewGroup1 的dispatchTouchEvent) 后結束傳遞,事件被消費(如下圖紅色的箭頭代碼ACTION_DOWN 事件的流向)。

//打印日志Activity | dispatchTouchEvent--> ACTION_DOWNViewGroup1 | dispatchTouchEvent--> ACTION_DOWN---->消費

在這種場景下ACTION_MOVE和ACTION_UP 將如何呢,看下面的打出來的日志

Activity | dispatchTouchEvent--> ACTION_MOVEViewGroup1 | dispatchTouchEvent--> ACTION_MOVE----TouchEventActivity | dispatchTouchEvent--> ACTION_UPViewGroup1 | dispatchTouchEvent--> ACTION_UP----

下圖中

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

2、我們在ViewGroup2 的dispatchTouchEvent 返回true消費這次事件

Activity | dispatchTouchEvent--> ACTION_DOWNViewGroup1 | dispatchTouchEvent--> ACTION_DOWNViewGroup1 | onInterceptTouchEvent--> ACTION_DOWNViewGroup2 | dispatchTouchEvent--> ACTION_DOWN---->消費Activity | dispatchTouchEvent--> ACTION_MOVEViewGroup1 | dispatchTouchEvent--> ACTION_MOVEViewGroup1 | onInterceptTouchEvent--> ACTION_MOVEViewGroup2 | dispatchTouchEvent--> ACTION_MOVE----TouchEventActivity | dispatchTouchEvent--> ACTION_UPViewGroup1 | dispatchTouchEvent--> ACTION_UPViewGroup1 | onInterceptTouchEvent--> ACTION_UPViewGroup2 | dispatchTouchEvent--> ACTION_UP----

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

Paste_Image.png

3、我們在View 的dispatchTouchEvent 返回true消費這次事件

這個我不就畫圖了,效果和在ViewGroup2 的dispatchTouchEvent return true的差不多,同樣的收到ACTION_DOWN 的dispatchTouchEvent函數都能收到 ACTION_MOVE和ACTION_UP。

所以我們就基本可以得出結論如果在某個控件的dispatchTouchEvent 返回true消費終結事件,那么收到ACTION_DOWN 的函數也能收到 ACTION_MOVE和ACTION_UP。

4、我們在View 的onTouchEvent 返回true消費這次事件

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

5、我們在ViewGroup 2 的onTouchEvent 返回true消費這次事件

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

6、我們在ViewGroup 1 的onTouchEvent 返回true消費這次事件

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

7、我們在Activity 的onTouchEvent 返回true消費這次事件

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

8、我們在View的dispatchTouchEvent 返回false并且Activity 的onTouchEvent 返回true消費這次事件

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

9、我們在View的dispatchTouchEvent 返回false并且ViewGroup 1 的onTouchEvent 返回true消費這次事件

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

10、我們在View的dispatchTouchEvent 返回false并且在ViewGroup 2 的onTouchEvent 返回true消費這次事件

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

11、我們在ViewGroup2的dispatchTouchEvent 返回false并且在ViewGroup1 的onTouchEvent返回true消費這次事件

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

12、我們在ViewGroup2的onInterceptTouchEvent 返回true攔截此次事件并且在ViewGroup 1 的onTouchEvent返回true消費這次事件。

紅色的箭頭代表ACTION_DOWN 事件的流向

藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

一下子畫了好多圖,還有好幾種情況就不再畫了,相信你也看出規律了,對于在onTouchEvent消費事件的情況:在哪個View的onTouchEvent 返回true,那么ACTION_MOVE和ACTION_UP的事件從上往下傳到這個View后就不再往下傳遞了,而直接傳給自己的onTouchEvent 并結束本次事件傳遞過程。

對于ACTION_MOVE、ACTION_UP總結:ACTION_DOWN事件在哪個控件消費了(return true), 那么ACTION_MOVE和ACTION_UP就會從上往下(通過dispatchTouchEvent)做事件分發往下傳,就只會傳到這個控件,不會繼續往下傳,如果ACTION_DOWN事件是在dispatchTouchEvent消費,那么事件到此為止停止傳遞,如果ACTION_DOWN事件是在onTouchEvent消費的,那么會把ACTION_MOVE或ACTION_UP事件傳給該控件的onTouchEvent處理并結束傳遞。

tips : 最近剛做了一個自定義控件,里面做了不少事件分發的處理和交互,個人是覺得可以當做本篇文章的一個實踐,大家可以看下源碼事件分發相關的的部分代碼,可能更有體會,鏈接地址:CalendarListView

注:【轉載請注明,問題可提問,喜歡可打賞,博客持續更新,歡迎關注】

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容