前言:不論你在什么時候開始,重要的是開始之後就不要停止。
前面幾篇文章,算是對自定義 View 這個知識點有了認識。我們知道,掌握了 onMeasure 、onLayout 之后也能開發一些自定義控件,但是通常情況下,一個自定義控件還需要加入用戶觸摸反饋,這就引入了另一個自定義 View 基礎知識——事件分發機制。
事件分發機制涉及兩個知識點:1、事件分發傳遞流程;2、滑動沖突問題。本篇介紹第一個知識點事件分發傳遞流程,第二個知識點會在下一篇再做詳細介紹。
本文不打算分析源碼,源碼分析筆者會在恰當的時期再寫的,本文是對事件分發傳遞流程的總結性文章。你不必再去別的地方參考文章浪費時間了,因為這里已經涵蓋到了幾乎所有的事件分發機制的理論知識,掌握本篇和下一篇文章,再結合后面兩個自定義控件的實例加以鞏固,那么這個知識點也就吸收了。廢話不講了,開更——
Android 事件分發流
一張圖來解釋和說明會清晰很多,下面根據畫的一張事件分發流程圖,說明的事件從用戶點擊之后,在不同函數不同返回值的情況的最終走向。(這張圖在網上很流行,但是本質上是錯誤的,源碼不是這么走向,知識為了幫助理解事件分發流程)
仔細看的話,圖分為3層,從上往下依次是 Activity、ViewGroup、View
事件從左上角那個白色箭頭開始,由 Activity 的 dispatchTouchEvent 做分發
箭頭的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是調用父類實現。
dispatchTouchEvent 和 onTouchEvent 的框里有個【true---->消費】的字,表示的意思是如果方法返回 true,那么代表事件就此消費,不會繼續往別的地方傳了,事件終止。
目前所有的圖的事件是針對 ACTION_DOWN 的,對于 ACTION_MOVE 和 ACTION_UP我們最后做分析。
只有 return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消費了(終止傳遞)。
仔細看整個圖,我們得出事件流 走向的幾個結論(希望讀者專心的看下圖 1,多看幾遍,腦子有比較清晰的概念。)
1、如果事件不被中斷,整個事件流向是一個類U型圖,我們來看下這張圖,可能更能理解U型圖的意思。
所以如果我們沒有對控件里面的方法進行重寫或更改返回值,而直接用super調用父類的默認實現,那么整個事件流向應該是從 Activity---->ViewGroup--->View 從上往下調用 dispatchTouchEvent方法,一直到葉子節點(View)的時候,再由 View--->ViewGroup--->Activity 從下往上調用 onTouchEvent 方法。
2、dispatchTouchEvent 和 onTouchEvent 一旦 return true, 事件就停止傳遞了(到達終點)(沒有誰能再收到這個事件)。看下圖中只要 return true 事件就沒再繼續傳下去了,對于 return true 我們經常說事件被消費了,消費了的意思就是事件走到這里就是終點,不會往下傳,沒有誰能再收到這個事件了。
3、dispatchTouchEvent 和 onTouchEvent return false 的時候事件都回傳給父控件的onTouchEvent 處理。
看上圖深藍色的線,對于返回 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 的作用
Intercept 的意思就攔截,每個 ViewGroup 每次在做分發的時候,問一問攔截器要不要攔截(也就是問問自己這個事件要不要自己來處理)如果要自己處理那就在 onInterceptTouchEvent 方法中 return true 就會交給自己的 onTouchEvent 的處理,如果不攔截就是繼續往子控件往下傳。默認是不會去攔截的,因為子 View 也需要這個事件,所以 onInterceptTouchEvent 攔截器 return super.onInterceptTouchEvent() 和r eturn false 是一樣的,是不會攔截的,事件會繼續往子 View 的 dispatchTouchEvent 傳遞。
6、ViewGroup 和View 的dispatchTouchEvent方法返回super.dispatchTouchEvent()的時候事件流走向。
首先看下 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_DOWN
ViewGroup1 | dispatchTouchEvent --> ACTION_DOWN
---->消費
在這種場景下ACTION_MOVE和ACTION_UP 將如何呢,看下面的打出來的日志
Activity | dispatchTouchEvent --> ACTION_MOVE
ViewGroup1 | dispatchTouchEvent --> ACTION_MOVE
----
Activity | dispatchTouchEvent --> ACTION_UP
ViewGroup1 | dispatchTouchEvent --> ACTION_UP
----
下圖中
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
2、我們在ViewGroup2 的dispatchTouchEvent 返回true消費這次事件
Activity | dispatchTouchEvent --> ACTION_DOWN
ViewGroup1 | dispatchTouchEvent --> ACTION_DOWN
ViewGroup1 | onInterceptTouchEvent --> ACTION_DOWN
ViewGroup2 | dispatchTouchEvent --> ACTION_DOWN
---->消費
Activity | dispatchTouchEvent --> ACTION_MOVE
ViewGroup1 | dispatchTouchEvent --> ACTION_MOVE
ViewGroup1 | onInterceptTouchEvent --> ACTION_MOVE
ViewGroup2 | dispatchTouchEvent --> ACTION_MOVE
----
TouchEventActivity | dispatchTouchEvent --> ACTION_UP
ViewGroup1 | dispatchTouchEvent --> ACTION_UP
ViewGroup1 | onInterceptTouchEvent --> ACTION_UP
ViewGroup2 | dispatchTouchEvent --> ACTION_UP
----
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
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 消費,那么事件到此為止停止傳遞(MOVE 和 UP也會傳到這里),如果ACTION_DOWN事件是在 onTouchEvent 消費的,那么會把ACTION_MOVE或ACTION_UP事件傳給該控件的 onTouchEvent 處理并結束傳遞。
事件分發機制是一個很難理解的東西,先記住結論以及會怎么使用。對于具體的原理,會在后面的源碼分析文章中介紹到。