1. 前言
🎯 一句話總結:
觸摸事件(TouchEvent)會從 Activity
層開始,按從外到內的方式傳遞給每一個 ViewGroup/View,直到某個 View 消費(consume) 它,事件傳遞就會停止。
📌 事件分發三個關鍵方法
方法名 | 所在類 | 作用說明 |
---|---|---|
dispatchTouchEvent() | 所有 View/ViewGroup | 事件分發入口,決定是否繼續向下傳遞 |
onInterceptTouchEvent() | 僅 ViewGroup | 是否攔截事件,阻止傳遞給子 View |
onTouchEvent() | 所有 View/ViewGroup | 事件的最終處理者(消費者) |
DecorView
是一個應用窗口的根容器,它本質上是一個FrameLayout
。DecorView
有唯一一個子View,它是一個垂直LinearLayout
,包含兩個子元素,一個是TitleView
(ActionBar的容器),另一個是ContentView
(窗口內容的容器)。
Activity.dispatchTouchEvent()↓Window.superDispatchTouchEvent()↓DecorView.dispatchTouchEvent()↓ViewGroup.dispatchTouchEvent()↓- onInterceptTouchEvent() → 是否攔截?↓ ↓攔截自己處理 不攔截繼續往下↓子View.dispatchTouchEvent()↓View.dispatchTouchEvent()↓- onTouchListener.onTouch()- onTouchEvent()
Activity.dispatchTouchEvent()
- 觸摸事件從系統層傳入,Activity 先接收。
- 通常會把事件傳給當前的
DecorView
(根 View)。
ViewGroup.dispatchTouchEvent()
- 嘗試調用
onInterceptTouchEvent()
判斷是否攔截事件。- 返回 true:表示當前 ViewGroup 要處理,子 View 不再收到事件。
- 返回 false:繼續把事件傳給子 View。
- 嘗試調用
- 子
View.dispatchTouchEvent()
如果是 ViewGroup,會重復上面的流程(遞歸)。- 如果是普通 View,直接調用
onTouchEvent()
。
- 如果是普通 View,直接調用
onTouchEvent()
- 如果返回
true
,表示事件被消費(消費后不會再向上傳遞)。 - 如果返回
false
,當前控件不處理,事件會被傳回上層 ViewGroup 的onTouchEvent()
。
- 如果返回
2. Activity、ViewGroup、View事件分發機制分析
Activity事件分發機制
Activity.dispatchTouchEvent(MotionEvent event)
源碼(Activity.java
僅關鍵代碼):
public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);
}
onUserInteraction()
:通知用戶交互,和事件分發無關。- 調用
getWindow().superDispatchTouchEvent(ev)
:Window
是PhoneWindow
。PhoneWindow
把事件交給了DecorView
(一個 ViewGroup)處理。
- 如果
superDispatchTouchEvent(ev)
返回true
,說明事件被下面消費了。 - 否則調用
Activity.onTouchEvent(ev)
(比如點擊空白處)。
ViewGroup 事件分發機制
DecorView
是 ViewGroup
,所以它遵循 ViewGroup 的事件分發規則。
ViewGroup.dispatchTouchEvent(MotionEvent ev)
源碼(ViewGroup.java
僅關鍵代碼):
ViewGroup.dispatchTouchEvent
代碼可大致簡化為下面這個樣子
public boolean dispatchTouchEvent(MotionEvent ev) {// 1. 是否攔截事件boolean intercepted = onInterceptTouchEvent(ev);// 2. 如果沒有攔截,遍歷子 View 分發if (!intercepted) {for (int i = childrenCount - 1; i >= 0; i--) {final View child = children[i];if (child.dispatchTouchEvent(ev)) {return true;}}}// 3. 子 View 沒有消費,自己處理return super.dispatchTouchEvent(ev);
}
詳細代碼可以查看ViewGroup.dispatchTouchEvent
中 dispatchTransformedTouchEvent
方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;// Canceling motions is a special case. We don't need to perform any transformations// or filtering. The important part is the action, not the contents.final int oldAction = event.getAction();if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);if (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}// 省略....return handled;}
onInterceptTouchEvent(ev)
:決定是否攔截事件(默認返回false
)。- 如果返回
true
,自己處理,不再傳遞給子 View。 - 如果沒有攔截,會遍歷子 View,調用子 View 的
dispatchTouchEvent(ev)
。 - 如果有任何一個子 View 返回了
true
,說明消費了事件,整個流程結束。 - 如果子 View 都沒有消費,最后調用自己的
super.dispatchTouchEvent(ev)
,即作為普通 View 處理。
View 事件分發機制
View
是最終事件的接收者。
View.dispatchTouchEvent(MotionEvent ev)
源碼(View.java
僅關鍵代碼):
public boolean dispatchTouchEvent(MotionEvent event) {boolean result = false;// 1. 先判斷是否需要觸發 OnTouchListenerListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}// 2. 如果 OnTouchListener 沒消費,再走 onTouchEventif (!result && onTouchEvent(event)) {result = true;}return result;
}
- 優先執行
onTouchListener.onTouch()
。- 如果返回
true
,表示事件被消費,不繼續往下傳遞。
- 如果返回
- 否則走
onTouchEvent(event)
。
因為點擊事件是在 onTouchEvent
中的 case MotionEvent.ACTION_UP:
中判斷調用的,具體查看 View.performClickInternal()
方法。
這里就是,如果你設置了某個 View
的 OnTouchListener
并且在 onTouch
方法中返回 true
,那么這個 View
的 onClick
方法不會執行的原因。
3. 理解 ViewGroup 的遞歸式事件分發?
核心理解:遞歸式分發
- 父 ViewGroup 收到事件,先問自己:“要不要攔截?”(
onInterceptTouchEvent
)** - 如果 不攔截,就 找出被點擊的子 View
- 然后 把事件遞給子 View 的
dispatchTouchEvent()
方法 - 子 View 又可以是一個
ViewGroup
(比如 LinearLayout),于是子 View 又重復上面的流程:onInterceptTouchEvent()
- 再分發給自己的子 View。
- 就這樣,一層一層遞歸下去,直到遇到一個普通 View(沒有子 View 的 Button、TextView),最后交給
onTouchEvent()
來消費。
打個通俗比喻:
- 一個 ViewGroup 就像一個"村長",負責分發任務。
- 它收到任務(MotionEvent)后,會問:
- 我要自己干?(攔截)
- 還是派給手下某個小村民?(子 View)
- 村民又是個小村長(嵌套 ViewGroup)的話,繼續往下派。
- 最后一個真正干活的是普通農民(Button/TextView)。
補充個知識點:
onInterceptTouchEvent() 只在 “ACTION_DOWN” 開始時有意義!!
- 因為一旦一個手指 ACTION_DOWN 被攔截了,后續的 ACTION_MOVE / ACTION_UP 事件都跟著這個處理鏈走。
- 如果
DOWN
沒攔截,后面的MOVE/UP
也不會隨便切換到攔截。
這叫做 事件的捕獲(capture)機制,Android 保證事件流動的一致性。
4. 最后
Android 事件分發是一層層向下傳,遇到攔截或者消費就停;否則事件會向上傳遞,直到有人消費或者丟棄。
還有一個問題,對于沒了解過事件分發機制的同學來說,對于事件分發:由外到內,事件消費:由內到外 的理解,可能有些困惑,包括我自己,其實就可以簡單理解為 ViewGroup 一個方法把事件分發機制寫完,方法中間就是挨個遍歷子 View 挨個問,你要不要這個事件(由外到內),都不要的話,我就要了(又回到了 由內到外 ),帶著這個理解,去看源碼就很容易理解事件分發機制了。