漫画Android:事件分发的过程是怎样的?
当用户触摸屏幕时,硬件层会捕获触摸信号,并将其转化为内核事件。
Android系统会通过InputManagerService和WindowManagerService等服务将这些事件包装成MotionEvent
对象,并将其传递给Activity的dispatchTouchEvent()
方法中,Activity会先将事件分发给Window处理,Window调用superDispatchTouchEvent()
方法,将事件交给 PhoneWindow
处理,然后 PhoneWindow
将事件传递给当前窗口的根视图(通常是DecorView,一个FrameLayout)。
DecorView是PhoneWindow的顶级视图,它是所有应用UI的容器。从这里开始,事件分发就进入了应用程序的视图层级。
即,事件收集之后最先传递给Activity
,随后依次向下传递:
Activity ——> Window ——> …… ——> DecorView ——> ViewGroup ——> …… ——> View
事件分发的流程
整个事件分发过程主要围绕MotionEvent
对象展开,并且涉及三个关键方法:dispatchTouchEvent()
、onInterceptTouchEvent()
和onTouchEvent()
。
- 事件产生:用户触摸屏幕,系统生成
MotionEvent
。 - 根视图分发:
MotionEvent
首先传递给当前Activity
,然后依次传递到当前窗口的根视图(DecorView
)。 dispatchTouchEvent()
:DecorView
调用自己的dispatchTouchEvent()
,决定是否将事件向下分发。onInterceptTouchEvent()
(ViewGroup):- 如果
DecorView
是ViewGroup
(通常是),它会调用onInterceptTouchEvent()
来判断是否拦截事件。 - 如果返回
true
(拦截),事件将直接传递给DecorView
的onTouchEvent()
。 - 如果返回
false
(不拦截),事件将继续向下分发给子View。
- 如果
- 向下分发:
DecorView
遍历其子View,找到触摸区域内的子View,并调用该子View的dispatchTouchEvent()
。这个过程会递归地重复步骤4和5,直到事件到达最底层的View或者被某个ViewGroup拦截。 onTouchEvent()
(View/ViewGroup):- 如果事件被某个View或ViewGroup拦截(
onInterceptTouchEvent()
返回true
),或者事件一直分发到了最底层的View且没有被任何父ViewGroup拦截,那么该View/ViewGroup的onTouchEvent()
方法将被调用。 - 如果
onTouchEvent()
返回true
,表示该View/ViewGroup消费了事件,事件传递流程结束。 - 如果
onTouchEvent()
返回false
,表示该View/ViewGroup不处理事件,事件会回溯到其父ViewGroup的onTouchEvent()
方法(如果父ViewGroup之前没有拦截该事件)。
- 如果事件被某个View或ViewGroup拦截(
- 事件未被处理:如果事件最终没有被任何View或ViewGroup处理(即所有
onTouchEvent()
都返回false
),那么该事件会沿着View树向上回溯,最终可能会被Activity的onTouchEvent()
方法处理。如果连Activity的onTouchEvent()
也返回false
,则该事件将被丢弃。
Android事件分发是一个自上而下分发、自下而上处理(如果未被处理)的过程。
ViewGroup事件分发流程
依葫芦画瓢!ViewGroup
的事件分发流程围绕刚刚提到的三个核心方法展开:dispatchTouchEvent()
、onInterceptTouchEvent()
和onTouchEvent()
。
- 事件到达:当一个
MotionEvent
到达ViewGroup
时,首先调用其dispatchTouchEvent()
方法。 - 是否拦截?:在
dispatchTouchEvent()
内部,首先调用onInterceptTouchEvent()
来判断ViewGroup
是否要拦截这个事件。- 如果
onInterceptTouchEvent()
返回true
(拦截):- 事件不再向下分发给子
View
。 ViewGroup
自身的onTouchEvent()
方法会被调用,以处理该事件。- 如果
onTouchEvent()
返回true
,表示事件被ViewGroup
消费。 - 如果
onTouchEvent()
返回false
,表示事件未被ViewGroup
消费,事件会回溯到其父ViewGroup
的onTouchEvent()
(如果父ViewGroup
之前没有拦截该事件)。
- 事件不再向下分发给子
- 如果
onInterceptTouchEvent()
返回false
(不拦截):ViewGroup
会遍历其子View
。- 它会判断触摸点是否在某个子
View
的范围内。 - 如果找到合适的子
View
,就调用该子View
的dispatchTouchEvent()
方法,将事件继续向下传递。 - 如果子
View
的dispatchTouchEvent()
返回true
(子View
消费了事件):整个事件分发流程结束。 - 如果子
View
的dispatchTouchEvent()
返回false
(子View
未消费事件):ViewGroup
会继续尝试将事件分发给下一个合适的子View
。 - 如果所有子
View
都遍历完了,但都没有消费事件,或者根本没有子View
:那么事件会回传给当前ViewGroup
,调用其onTouchEvent()
方法来处理。- 如果
ViewGroup
的onTouchEvent()
返回true
,事件被ViewGroup
消费。 - 如果
ViewGroup
的onTouchEvent()
返回false
,事件未被ViewGroup
消费,会继续回溯到父ViewGroup
。
- 如果
- 如果
关键点:
- 优先拦截:
onInterceptTouchEvent()
优先于子View
的dispatchTouchEvent()
被调用,它有“一票否决权”。 - 消费即止:一旦某个
View
或ViewGroup
的onTouchEvent()
返回true
,表示它消费了该事件序列,后续事件将直接传递给它,不再进行分发。 - 回溯机制:如果一个事件沿着分发路径一直没有被消费(所有
onTouchEvent()
都返回false
),它会沿着调用链向上回溯,最终可能由Activity
的onTouchEvent()
处理。
内部逻辑(伪代码表示):
public boolean dispatchTouchEvent(MotionEvent event) {boolean handled = false; // 标记事件是否被处理// 1. 调用 onInterceptTouchEvent() 判断是否拦截if (onInterceptTouchEvent(event)) {// 2. 如果 onInterceptTouchEvent() 返回 true (拦截)// 则事件不再向下分发给子View,而是直接交给当前ViewGroup的 onTouchEvent() 处理handled = onTouchEvent(event);} else {// 3. 如果 onInterceptTouchEvent() 返回 false (不拦截)// 则遍历子View,尝试将事件分发给它们// 查找触摸点所在的子View,并调用其 dispatchTouchEvent()// 伪代码:// for (int i = 0; i < getChildCount(); i++) {// View child = getChildAt(i);// if (child.isTouchedInBounds(event)) { // 检查触摸点是否在子View范围内// if (child.dispatchTouchEvent(event)) { // 递归调用子View的dispatchTouchEvent()// handled = true; // 子View消费了事件// break; // 停止遍历,事件已被处理// }// }// }// 4. 如果所有子View都没有消费事件 (handled 仍为 false)// 或者根本没有子View// 则将事件交给当前ViewGroup的 onTouchEvent() 处理if (!handled) {handled = onTouchEvent(event);}}return handled; // 返回事件是否被处理
}