掃盲細節,Android 的事件分發機制
事件都是從使用者按下(ACTION_DOWN)的那一刻產生的,三個與事件相關的方法:
- dispatchTouchEvent()
- onTouchEvent()
- onInterceptTouchEvent()
Activity 的事件分發機制
從單詞含義已經很明顯的知道,dispatchTouchEvent()
是負責事件分發的。當點選事件產生後,事件首先會傳遞給當前的 Activity,這會呼叫 Activity 的 dispatchTouchEvent()
方法,看看原始碼中是怎麼處理的。
由於我們一般產生點選事件都是 MotionEvent.ACTION_DOWN
onUserInteraction()
這個方法。很遺憾,這個方法實現是空的,不過可以從註釋和其他途徑可以瞭解到,該方法主要的作用是實現屏保功能,並且當此 Activity 在棧頂的時候,觸屏點選 Home、Back等都會觸發這個方法。
再來看看第二個 if 語句,getWindow().superDispatchTouchEvent()
,getWindow()
明顯是獲取 Window
,由於 Window
是一個抽象類,所以我們能拿到其子類 PhoneWindow
,直接看看 PhoneWindows.superDispatchTouchEvent()
直接呼叫了 DecorView
的 superDispatchTrackballEvent()
方法。DecorView
繼承於 FrameLayout
,而 FrameLayout
作為 ViewGroup
的子類,所以直接呼叫了 ViewGroup
的 dispatchTouchEvent()
。
ViewGroup 的事件分發機制
檢視 ViewGroup
的 dispatchTouchEvent()
:
注意其中紅框裡面的程式碼,看註釋也能知道,定義了一個 boolean 值變數 intercept
來表示是否要攔截事件。
其中採用到了 onInterceptTouchEvent(ev)
對 intercept
進行賦值。大多數情況下,onInterceptTouchEvent()
返回值為 false,但我們完全可以通過重寫 onInterceptTouchEvent(ev)
來改變它的返回值,繼續往下看後面對這個 intercept
做了什麼處理。
暫時忽略 判斷的 canceled
,該值同樣大多數時候都返回 false,所以當我們沒有重寫 onInterceptTouchEvent()
並使它的返回值為 true 時,一般情況下都是可以進入到該方法的。
繼續閱讀原始碼可以發現,裡面做了一個 for 迴圈,通過倒序遍歷 ViewGroup
下面的所有子 View,然後一個一個判斷點選位置是否是該子 View 的佈局區域,當然還有一些其他的,由於篇幅原因,這裡就不細講了。
View 的事件分發機制
ViewGroup
說到底還是一個 View,所以我們不得不繼續看看 View 的 dispatchTouchEvent()
。
-
(mViewFlags & ENABLED_MASK) == ENABLED
判斷當前點選的控制元件是否為 enable,但由於基本 View 都是 enable 的,所以這個條件基本都返回 true。 -
mOnTouchListener.onTouch(this, event)
即我們呼叫setOnTouchListener()
時必須覆蓋的方法onTouch()
的返回值。
從上述的分析,終於知道「onTouch()
方法優先順序高於 onTouchEvent(event)
方法」是怎麼來的了!!
再看 onTouchEvent()
從上面的程式碼可以明顯地看到,只要 View 的 CLICKABLE 和 LONG_CLICKABLE 有一個為 true,那麼 onTouchEvent()
就會返回 true 消耗這個事件。CLICKABLE 和 LONG_CLICKABLE 代表 View 可以被點選和長按點選,我們通常都會採用 setOnClickListener()
和 setOnLongClickListener()
做設定。接著在 ACTION_UP 事件中會呼叫 performClick()
方法。
從截圖中可以看到,如果 mOnClickListener
不為空,那麼它的 onClick()
方法就會呼叫。
總結
-
Activity 的事件分發示意圖
-
ViewGroup 事件分發示意圖
-
View 的事件分發示意圖
-
事件分發工作流程總結