触摸事件分发机制
什么是触摸事件?
在 Android 中,事件(Event)是用户与设备交互的输入信号,它可能来自触摸屏、按键、轨迹球等。对于触摸操作,事件由 MotionEvent
封装,包含了动作类型(如按下、移动、抬起)以及位置、时间等信息。
从手指触摸屏幕到抬起的整个过程中会产生一系列事件,称作事件序列,通常包含:
一个
ACTION_DOWN
(手指按下,序列开始);若干个
ACTION_MOVE
(手指滑动);一个
ACTION_UP
(手指抬起,序列结束)或ACTION_CANCEL
(事件被系统中断)。
事件分发机制简述
事件分发机制是指 Android 在接收到一个输入事件后,如何将该事件从顶层容器逐级传递到最终的 View 进行处理的过程。
主要涉及三个核心方法:
dispatchTouchEvent(MotionEvent ev)
:事件分发,决定事件的去向;onInterceptTouchEvent(MotionEvent ev)
(ViewGroup 特有):事件拦截,决定是否阻止子 View 接收事件;onTouchEvent(MotionEvent ev)
:事件处理,决定是否消费事件。
分发机制的基本原则:
事件总是先传到最外层容器(Activity → Window → DecorView → ViewGroup)
ViewGroup 可以选择拦截事件,不再向子 View 分发。
View 接到事件后可以消费(返回 true)或不消费(返回 false)。
一旦某个 View 消费了
ACTION_DOWN
,后续的事件序列会直接分发给它。
事件分发的自底向上过程
事件从哪里来?
在聊具体的事件分发机制之前,先搞清楚一个问题:用户触摸屏幕后,事件是如何到达 Activity 的?
用户手指触摸屏幕 → 硬件驱动检测到坐标变化;
InputManagerService (IMS) 从底层驱动读取输入事件,并找到当前有焦点的窗口;
通过 Binder IPC 将事件传递到应用进程,对应的 ViewRootImpl 负责接收事件;
ViewRootImpl 调用
DecorView.dispatchTouchEvent()
,而 DecorView 会回调Window.Callback
接口;Window.Callback
的实现类正是 Activity,所以事件最终到达Activity.dispatchTouchEvent()
,这也是我们能接触到的第一个事件入口。
Activity
在 Activity 中,dispatchTouchEvent()
是触摸事件分发的入口方法:
// Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); // 空方法,标记一次新的触摸序列开始
}
// 交给 Window(PhoneWindow)处理
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果 Window 没消费,自己尝试处理
return onTouchEvent(ev);
}
onUserInteraction()
默认是空实现,系统在检测到新的ACTION_DOWN
时会调用它,供开发者重写(比如在锁屏、休眠逻辑中)。真正的事件分发是交给
Window
去做的,Activity 自身只在 Window 没消费事件时兜底调用onTouchEvent()
。
Window
Window
是一个抽象类,Activity 在 attach()
方法中创建的实际是 PhoneWindow
:
// Activity.java
final void attach(Context context, ...) {
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setCallback(this); // Activity 自身作为回调处理事件
}
Window
的分发实现很简单,自身并不处理事件,只是桥梁,把事件转交给它的顶层 View(DecorView):
// PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
DecorView
DecorView
是应用窗口中 View 树的根节点,继承自 FrameLayout
,它会在 Activity 第一次调用 setContentView()
时由 PhoneWindow
创建。它直接将事件传递给其父类 ViewGroup 进行向下分发:
// DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
// 调用父类 ViewGroup 的 dispatchTouchEvent 方法
return super.dispatchTouchEvent(event);
}
小结
至此,一个触摸事件的向上传递链路是:
Activity.dispatchTouchEvent()
→ Window.superDispatchTouchEvent()
→ DecorView.superDispatchTouchEvent()
→ ViewGroup.dispatchTouchEvent()
事件分发的自顶向下过程
当触摸事件从 Activity 向上传递到 ViewGroup 之后,就进入了 ViewGroup 的事件分发流程。ViewGroup 作为容器类,不仅可以处理事件,还能决定是否把事件交给子 View,这就是所谓的“自顶向下”分发。
ViewGroup 事件分发
ViewGroup.dispatchTouchEvent()
是事件分发的核心逻辑,伪代码如下:
// ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 清空之前的触摸状态,准备新一轮事件分发
cancelAndClearTouchTargets();
}
// 1. 判断是否拦截
boolean intercept = onInterceptTouchEvent(ev);
// 2. 如果不拦截,找到触摸点命中的子 View
if (!intercept) {
View child = findChildUnder(ev.getX(), ev.getY());
if (child != null) {
// 分发给子 View 处理
handled = child.dispatchTouchEvent(ev);
}
}
// 3. 如果拦截了,或者子 View 没处理,就交给自己处理
if (!handled) {
handled = onTouchEvent(ev);
}
return handled;
}
onInterceptTouchEvent()
是 ViewGroup 特有的方法,决定是否拦截当前事件序列。
返回
true
:事件不再传给子 View,改由 ViewGroup 自己处理。返回
false
:事件传递给子 View。
系统默认返回 false
,因此像 LinearLayout
这样的基础容器通常不会主动拦截。
View 分发事件
对于普通 View
,事件分发逻辑更简单。核心代码可以概括为:
// —— View:触摸事件分发(精简版)——
public boolean dispatchTouchEvent(MotionEvent event) {
boolean handled = false;
int action = event.getActionMasked();
// 1) 新手势开始,防御性清理嵌套滚动状态
if (action == MotionEvent.ACTION_DOWN) {
stopNestedScroll();
}
// 2) 安全过滤(如窗口安全标志/来源校验等),不过滤才继续处理
if (onFilterTouchEventForSecurity(event)) {
// 3) 主通路:优先 OnTouchListener;否则走 onTouchEvent()
handled = performOnTouchCallback(event);
}
// 4) 收尾:手势结束或 DOWN 未被消费 → 停止嵌套滚动,避免残留影响
if (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_CANCEL
|| (action == MotionEvent.ACTION_DOWN && !handled)) {
stopNestedScroll();
}
// 5) 返回是否已被当前 View 消费
return handled;
}
performOnTouchCallback()
的核心逻辑如下(保留重点):
// —— 优先监听器,未消费再走 onTouchEvent ——
// 说明:监听器需要 view.isEnabled() 才会触发(系统默认语义)
private boolean performOnTouchCallback(MotionEvent event) {
// A. OnTouchListener 优先
if (mOnTouchListener != null && isEnabled()
&& mOnTouchListener.onTouch(this, event)) {
return true; // 监听器已消费
}
// B. 监听器未消费 → 交给 onTouchEvent 兜底
return onTouchEvent(event);
}
由 performOnTouchCallback()
的源码可以发现:
设置了
OnTouchListener
并且返回 true → 事件就不会走到onTouchEvent
;返回 false 或没有设置
OnTouchListener
→ 事件会继续走onTouchEvent
。
小结
ViewGroup:通过
onInterceptTouchEvent
决定事件是自己处理还是交给子 View;View:通过
OnTouchListener
和onTouchEvent
来消费事件,监听器优先。