Android View 触摸事件分发机制
基于 AOSP 源码(Android 15/V)的深入分析
目录
- [1. 概述](#1. 概述)
- [1.1 Android 输入事件体系的整体架构](#1.1 Android 输入事件体系的整体架构)
- [1.2 触摸事件(MotionEvent)类型说明](#1.2 触摸事件(MotionEvent)类型说明)
- [2. 事件分发的设计架构](#2. 事件分发的设计架构)
- [2.1 责任链模式的应用](#2.1 责任链模式的应用)
- [2.2 完整事件传递链路](#2.2 完整事件传递链路)
- [2.3 核心类关系图](#2.3 核心类关系图)
- [3. 事件分发详细流程](#3. 事件分发详细流程)
- [3.1 ViewRootImpl 接收事件的流程(InputStage 管道)](#3.1 ViewRootImpl 接收事件的流程(InputStage 管道))
- [3.2 Activity.dispatchTouchEvent() 的流程](#3.2 Activity.dispatchTouchEvent() 的流程)
- [3.3 ViewGroup.dispatchTouchEvent() 的详细流程](#3.3 ViewGroup.dispatchTouchEvent() 的详细流程)
- [3.4 View.dispatchTouchEvent() 的流程](#3.4 View.dispatchTouchEvent() 的流程)
- [3.5 View.onTouchEvent() 的流程](#3.5 View.onTouchEvent() 的流程)
- [3.6 ViewGroup.dispatchTouchEvent 流程图](#3.6 ViewGroup.dispatchTouchEvent 流程图)
- [3.7 完整触摸事件时序图](#3.7 完整触摸事件时序图)
- [4. 事件冲突的解决方案](#4. 事件冲突的解决方案)
- [4.1 外部拦截法](#4.1 外部拦截法)
- [4.2 内部拦截法](#4.2 内部拦截法)
- [4.3 FLAG_DISALLOW_INTERCEPT 的工作原理](#4.3 FLAG_DISALLOW_INTERCEPT 的工作原理)
- [4.4 嵌套滑动机制(NestedScrolling)简介](#4.4 嵌套滑动机制(NestedScrolling)简介)
- [4.5 常见冲突场景及解决方案](#4.5 常见冲突场景及解决方案)
- [4.6 外部拦截法与内部拦截法对比流程图](#4.6 外部拦截法与内部拦截法对比流程图)
- [5. 关键源码解析](#5. 关键源码解析)
- [5.1 ViewRootImpl.WindowInputEventReceiver](#5.1 ViewRootImpl.WindowInputEventReceiver)
- [5.2 ViewRootImpl.ViewPostImeInputStage.processPointerEvent](#5.2 ViewRootImpl.ViewPostImeInputStage.processPointerEvent)
- [5.3 View.dispatchPointerEvent](#5.3 View.dispatchPointerEvent)
- [5.4 DecorView.dispatchTouchEvent](#5.4 DecorView.dispatchTouchEvent)
- [5.5 Activity.dispatchTouchEvent](#5.5 Activity.dispatchTouchEvent)
- [5.6 PhoneWindow.superDispatchTouchEvent](#5.6 PhoneWindow.superDispatchTouchEvent)
- [5.7 View.dispatchTouchEvent](#5.7 View.dispatchTouchEvent)
- [5.8 View.performOnTouchCallback](#5.8 View.performOnTouchCallback)
- [5.9 ViewGroup.dispatchTouchEvent](#5.9 ViewGroup.dispatchTouchEvent)
- [5.10 ViewGroup.dispatchTransformedTouchEvent](#5.10 ViewGroup.dispatchTransformedTouchEvent)
- [5.11 ViewGroup.onInterceptTouchEvent](#5.11 ViewGroup.onInterceptTouchEvent)
- [5.12 View.onTouchEvent](#5.12 View.onTouchEvent)
- [5.13 TouchTarget 内部类](#5.13 TouchTarget 内部类)
- [5.14 ViewGroup.requestDisallowInterceptTouchEvent](#5.14 ViewGroup.requestDisallowInterceptTouchEvent)
- [6. 多点触控的事件分发](#6. 多点触控的事件分发)
- [6.1 MotionEvent 中 Pointer 的概念](#6.1 MotionEvent 中 Pointer 的概念)
- [6.2 ACTION_POINTER_DOWN/UP 的分发逻辑](#6.2 ACTION_POINTER_DOWN/UP 的分发逻辑)
- [6.3 多个 TouchTarget 对应不同子 View](#6.3 多个 TouchTarget 对应不同子 View)
1. 概述
1.1 Android 输入事件体系的整体架构
Android 的输入事件处理贯穿从硬件驱动到应用层的完整软件栈,整体分为以下层次:
-
硬件层 :触摸屏控制器产生原始触摸数据,通过 Linux 内核的 input 子系统(
/dev/input/eventX)暴露给用户空间。 -
Native 层(InputManagerService / InputDispatcher):
InputReader:从EventHub读取原始事件,将其转换为RawEvent,再经过InputMapper映射为NotifyMotionArgs。InputDispatcher:负责将事件分发到正确的窗口。它通过InputChannel(基于 Unix socket pair)与应用进程通信。
-
Framework Java 层(ViewRootImpl):
InputEventReceiver:应用进程端接收事件的底层入口,通过 JNI 回调从 native 层接收事件。ViewRootImpl:管理 View 树的根节点,内部通过 InputStage 管道 处理事件。- 事件最终通过
mView.dispatchPointerEvent()进入 View 树。
-
View 体系(DecorView -> Activity -> ViewGroup -> View):
DecorView:窗口的根 View,继承自 FrameLayout(ViewGroup)。Activity:作为Window.Callback,参与事件分发链。ViewGroup:通过dispatchTouchEvent()和onInterceptTouchEvent()实现事件的递归分发与拦截。View:通过dispatchTouchEvent()和onTouchEvent()实现事件的最终处理。
1.2 触摸事件(MotionEvent)类型说明
触摸事件定义在 MotionEvent.java 中,关键常量如下:
| 常量 | 值 | 说明 |
|---|---|---|
ACTION_DOWN |
0 | 第一个手指按下,一个触摸手势的开始 |
ACTION_UP |
1 | 最后一个手指抬起,一个触摸手势的结束 |
ACTION_MOVE |
2 | 手指在屏幕上滑动 |
ACTION_CANCEL |
3 | 当前手势被取消,不应执行任何动作 |
ACTION_OUTSIDE |
4 | 触摸发生在 UI 元素的正常边界之外 |
ACTION_POINTER_DOWN |
5 | 非第一个手指按下(多点触控) |
ACTION_POINTER_UP |
6 | 非最后一个手指抬起(多点触控) |
源码位置:
frameworks/base/core/java/android/view/MotionEvent.java第 219-289 行
一次完整的单指触摸手势的事件序列为:ACTION_DOWN -> ACTION_MOVE(0..N) -> ACTION_UP。
一次多指触摸手势的事件序列为:ACTION_DOWN -> ACTION_POINTER_DOWN -> ACTION_MOVE(0..N) -> ACTION_POINTER_UP -> ACTION_UP。
2. 事件分发的设计架构
2.1 责任链模式的应用
Android 触摸事件分发机制采用了**责任链模式(Chain of Responsibility)**的变体。其核心特征:
- 自顶向下传递:事件从 Activity 开始,经过 ViewGroup 递归传递到子 View。
- 自底向上消费:如果子 View 不消费事件,事件逐级回传给父 ViewGroup 处理。
- 拦截机制 :ViewGroup 可以通过
onInterceptTouchEvent()在传递过程中截断事件链。 - 一次决定、后续绑定 :
ACTION_DOWN时确定事件的目标 View(TouchTarget),后续同一手势的ACTION_MOVE/ACTION_UP直接发送给该目标。
这种设计将"谁来处理事件"的决策分散在 View 树的各个节点上,每个 ViewGroup 都可以独立决定是否拦截事件,每个 View 都可以独立决定是否消费事件。
2.2 完整事件传递链路
事件从底层到应用层的完整路径:
InputManagerService (Native)
|
v (通过 InputChannel / socket pair)
InputEventReceiver.onInputEvent() (Java)
|
v
ViewRootImpl.enqueueInputEvent()
|
v
ViewRootImpl.InputStage 管道 (NativePreIme -> ViewPreIme -> Ime -> EarlyPostIme -> NativePostIme -> ViewPostIme -> Synthetic)
|
v
ViewPostImeInputStage.processPointerEvent()
|
v
mView.dispatchPointerEvent() // mView 就是 DecorView
|
v
DecorView.dispatchTouchEvent() // 获取 Window.Callback(即 Activity)
|
v
Activity.dispatchTouchEvent() // Window.Callback.dispatchTouchEvent
|
v
PhoneWindow.superDispatchTouchEvent()
|
v
DecorView.superDispatchTouchEvent() // 调用 super.dispatchTouchEvent,即 ViewGroup.dispatchTouchEvent
|
v
ViewGroup.dispatchTouchEvent() // 递归分发给子 View
|
v
View.dispatchTouchEvent() -> onTouchEvent()
2.3 核心类关系图
继承
继承(FrameLayout)
继承
继承
继承
实现 Window.Callback
包含(链表)
包含(管道)
包含
mView(DecorView)
mWindow
mDecor
mWindow
View
-mOnTouchListener: OnTouchListener
+dispatchTouchEvent(MotionEvent) : boolean
+onTouchEvent(MotionEvent) : boolean
+dispatchPointerEvent(MotionEvent) : boolean
+setOnTouchListener(OnTouchListener)
-performOnTouchCallback(MotionEvent) : boolean
ViewGroup
-mFirstTouchTarget: TouchTarget
-mGroupFlags: int
+FLAG_DISALLOW_INTERCEPT: int
+dispatchTouchEvent(MotionEvent) : boolean
+onInterceptTouchEvent(MotionEvent) : boolean
+requestDisallowInterceptTouchEvent(boolean)
-dispatchTransformedTouchEvent(MotionEvent, boolean, View, int) : boolean
-addTouchTarget(View, int) : TouchTarget
-cancelAndClearTouchTargets(MotionEvent)
-resetTouchState()
-buildTouchDispatchChildList() : ArrayList<View>
-isTransformedTouchPointInView(float, float, View, PointF) : boolean
TouchTarget
+child: View
+pointerIdBits: int
+next: TouchTarget
+ALL_POINTER_IDS: int
+obtain(View, int) : TouchTarget
+recycle()
ViewRootImpl
-mView: View
-mFirstInputStage: InputStage
-mFirstPostImeInputStage: InputStage
-mInputEventReceiver: WindowInputEventReceiver
+enqueueInputEvent(InputEvent, InputEventReceiver, int, boolean)
-doProcessInputEvents()
<<abstract>>
InputStage
-mNext: InputStage
#FORWARD: int
#FINISH_HANDLED: int
#FINISH_NOT_HANDLED: int
+deliver(QueuedInputEvent)
#onProcess(QueuedInputEvent) : int
ViewPostImeInputStage
#onProcess(QueuedInputEvent) : int
-processPointerEvent(QueuedInputEvent) : int
WindowInputEventReceiver
+onInputEvent(InputEvent)
+onBatchedInputEventPending(int)
<<abstract>>
InputEventReceiver
-mInputChannel: InputChannel
-mMessageQueue: MessageQueue
+onInputEvent(InputEvent)
Activity
-mWindow: Window
+dispatchTouchEvent(MotionEvent) : boolean
+onTouchEvent(MotionEvent) : boolean
<<abstract>>
Window
+superDispatchTouchEvent(MotionEvent) : boolean
+setCallback(Callback)
+getCallback() : Callback
PhoneWindow
-mDecor: DecorView
+superDispatchTouchEvent(MotionEvent) : boolean
DecorView
-mWindow: PhoneWindow
+dispatchTouchEvent(MotionEvent) : boolean
+superDispatchTouchEvent(MotionEvent) : boolean
+onInterceptTouchEvent(MotionEvent) : boolean
<<interface>>
WindowCallback
+dispatchTouchEvent(MotionEvent) : boolean
3. 事件分发详细流程
3.1 ViewRootImpl 接收事件的流程(InputStage 管道)
ViewRootImpl 在其 setView() 方法中构建了一条 InputStage 管道,事件按以下顺序经过各个 Stage:
NativePreImeInputStage -> ViewPreImeInputStage -> ImeInputStage
-> EarlyPostImeInputStage -> NativePostImeInputStage
-> ViewPostImeInputStage -> SyntheticInputStage
源码位置:
frameworks/base/core/java/android/view/ViewRootImpl.java第 1742-1754 行
java
// 第 1742-1754 行
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
事件进入流程:
WindowInputEventReceiver.onInputEvent()被 native 层回调(第 10440 行)- 调用
enqueueInputEvent()将事件加入队列(第 10461 行) doProcessInputEvents()从队列取出事件,调用deliverInputEvent()- 事件经过 InputStage 管道,在
ViewPostImeInputStage.onProcess()中处理触摸事件 processPointerEvent()调用mView.dispatchPointerEvent(event)(第 8031 行)View.dispatchPointerEvent()判断是触摸事件后调用dispatchTouchEvent()(第 16742-16743 行)
InputStage 基类的核心机制(第 7151-7186 行):
每个 InputStage 通过 deliver() 方法处理事件,内部调用 onProcess() 获取处理结果:
FORWARD:不处理,转发给下一个 StageFINISH_HANDLED:已处理FINISH_NOT_HANDLED:未处理但也不转发
3.2 Activity.dispatchTouchEvent() 的流程
源码位置:
frameworks/base/core/java/android/app/Activity.java第 4535-4543 行
java
// Activity.java 第 4535-4543 行
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
流程说明:
- 如果是
ACTION_DOWN事件,先调用onUserInteraction()通知有用户交互(可用于重置屏幕超时等)。 - 调用
getWindow().superDispatchTouchEvent(ev),即PhoneWindow.superDispatchTouchEvent(),将事件传递给 View 树。 - 如果 View 树没有消费事件(返回 false),则调用
Activity.onTouchEvent()作为最后的处理机会。
Activity.onTouchEvent() 的默认实现(第 4285-4292 行):
java
// Activity.java 第 4285-4292 行
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
默认实现仅处理一种情况:如果触摸发生在窗口范围外且窗口是可关闭的(如 Dialog 风格的 Activity),则关闭窗口。
3.3 ViewGroup.dispatchTouchEvent() 的详细流程
这是整个事件分发机制中最核心、最复杂的方法。
源码位置:
frameworks/base/core/java/android/view/ViewGroup.java第 2635-2853 行
整个方法分为以下关键阶段:
阶段一:ACTION_DOWN 时的初始化(第 2652-2658 行)
java
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
当收到 ACTION_DOWN(新手势的开始),清空之前所有的 TouchTarget 并重置触摸状态。这确保每次新的手势都从干净的状态开始。resetTouchState() 还会清除 FLAG_DISALLOW_INTERCEPT 标志。
阶段二:检查是否拦截(第 2660-2678 行)
java
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept || isBackGestureInProgress) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // 恢复 action,防止在 onInterceptTouchEvent 中被修改
} else {
intercepted = false;
}
} else {
intercepted = true;
}
关键逻辑:
- 只有在
ACTION_DOWN或者已有子 View 在处理事件(mFirstTouchTarget != null)时,才调用onInterceptTouchEvent()。 - 如果子 View 调用了
requestDisallowInterceptTouchEvent(true)设置了FLAG_DISALLOW_INTERCEPT,则跳过拦截检查(除非有返回手势正在进行)。 - 如果没有 TouchTarget(即之前没有子 View 消费 DOWN 事件),并且当前事件不是 DOWN,直接设置
intercepted = true,事件由当前 ViewGroup 自己处理。
阶段三:安全策略检查(第 2647 行)
java
if (onFilterTouchEventForSecurity(ev)) {
// ... 正常分发逻辑
}
onFilterTouchEventForSecurity() 检查事件是否被遮挡(obscured),如果窗口设置了 filterTouchesWhenObscured 且事件来自被遮挡的窗口,则过滤该事件。
阶段四:子 View 的遍历和命中测试(第 2705-2796 行)
仅在 ACTION_DOWN、ACTION_POINTER_DOWN(且启用了 split)、或 ACTION_HOVER_MOVE 时执行:
java
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
遍历流程:
- 通过
buildTouchDispatchChildList()获取按 Z-order 排序的子 View 列表。 - 从前到后 (
i = childrenCount - 1向下遍历)扫描子 View。 - 对每个子 View 进行命中测试 (hit test):
child.canReceivePointerEvents():检查 View 是否可见且无动画。isTransformedTouchPointInView(x, y, child, null):将触摸坐标转换到子 View 的局部坐标系,检查是否在其范围内。
- 如果命中,调用
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)将事件分发给该子 View。 - 如果子 View 消费了事件(返回 true),则调用
addTouchTarget(child, idBitsToAssign)将其添加到 TouchTarget 链表头部。
阶段五:TouchTarget 链表的构建(第 2975-2980 行)
java
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
TouchTarget 以单链表形式组织,新的 target 插入到链表头部。每个 TouchTarget 持有对子 View 的引用和对应的 pointer ID 位掩码。
阶段六:事件的实际分发(第 2799-2835 行)
java
if (mFirstTouchTarget == null) {
// 没有任何子 View 消费事件,当作普通 View 处理
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
// 遍历 TouchTarget 链表进行分发
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true; // DOWN 阶段已经分发过了
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
// 如果需要取消,从链表中移除该 target
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
关键点:
- 如果
mFirstTouchTarget == null(没有子 View 消费事件),调用dispatchTransformedTouchEvent(ev, canceled, null, ...),其中child = null表示调用super.dispatchTouchEvent(),即当作普通 View 处理。 - 如果被拦截(
intercepted = true),之前的 TouchTarget 会收到ACTION_CANCEL事件,然后从链表中移除。
阶段七:ACTION_UP/ACTION_CANCEL 时的清理(第 2837-2846 行)
java
if (canceled || actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
ACTION_UP/ACTION_CANCEL:完全重置触摸状态。ACTION_POINTER_UP:仅移除对应 pointer 的 ID 位。
3.4 View.dispatchTouchEvent() 的流程
源码位置:
frameworks/base/core/java/android/view/View.java第 16391-16431 行
java
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll(); // 新手势开始时停止嵌套滚动
}
if (onFilterTouchEventForSecurity(event)) {
result = performOnTouchCallback(event);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
核心逻辑在 performOnTouchCallback() 中(第 16437-16465 行):
java
private boolean performOnTouchCallback(MotionEvent event) {
boolean handled = false;
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
handled = true;
}
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) {
handled = li.mOnTouchListener.onTouch(this, event);
}
if (handled) {
return true;
}
return onTouchEvent(event);
}
优先级顺序:
- 滚动条拖拽处理
- OnTouchListener.onTouch():如果设置了 OnTouchListener 且 View 是 ENABLED 的,先调用 listener
- 如果 listener 返回 true,不再 调用
onTouchEvent() - 否则调用 onTouchEvent() 处理
这个优先级设计意味着:OnTouchListener 的优先级高于 onTouchEvent(),开发者可以通过设置 listener 来拦截事件而不需要继承 View。
3.5 View.onTouchEvent() 的流程
源码位置:
frameworks/base/core/java/android/view/View.java第 17898-18097 行
这是 View 对触摸事件的默认处理逻辑,涵盖了点击、长按等手势的识别。
首先检查 clickable 状态(第 17904-17906 行):
java
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
DISABLED 状态处理(第 17908-17917 行):
java
if ((viewFlags & ENABLED_MASK) == DISABLED
&& (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
// ...
return clickable; // disabled 但 clickable 的 View 仍然消费事件,只是不响应
}
关键设计 :即使 View 是 DISABLED 的,只要它是 clickable 的,就仍然消费(consume)触摸事件。这意味着事件不会穿透到下面的 View。
各事件类型的处理:
-
ACTION_DOWN(第 17991-18032 行):
- 设置
mHasPerformedLongPress = false - 如果在滚动容器中,设置
PFLAG_PREPRESSED标志并延迟tapTimeout毫秒后显示按下状态 - 如果不在滚动容器中,立即设置按下状态,并启动长按检测计时器
- 设置
-
ACTION_MOVE(第 18046-18097 行):
- 更新 drawable 热点位置
- 如果手指移出 View 边界(考虑 touchSlop),取消长按检测和按下状态
-
ACTION_UP(第 17926-17989 行):
- 如果不是长按,执行点击操作:
performClickInternal() - 通过
post(mPerformClick)异步执行点击,让 View 的视觉状态先更新 - 清除按下状态
- 如果不是长按,执行点击操作:
-
ACTION_CANCEL(第 18034-18044 行):
- 清除按下状态
- 移除所有回调
3.6 ViewGroup.dispatchTouchEvent 流程图
否
是
是
否
是
否
是
否
是
否
是
否
是
否
未命中
是
否
命中
是
否
否
是
是
否
是
否
是
否
是
否
是
否
dispatchTouchEvent 开始
onFilterTouchEventForSecurity
安全检查通过?
返回 false
actionMasked ==
ACTION_DOWN?
cancelAndClearTouchTargets
resetTouchState
清空所有 TouchTarget
ACTION_DOWN 或
mFirstTouchTarget != null?
FLAG_DISALLOW_INTERCEPT
已设置?
intercepted = true
没有 target 也不是 DOWN
自己处理
intercepted = false
子 View 禁止拦截
调用 onInterceptTouchEvent
onInterceptTouchEvent
返回 true?
intercepted = true
intercepted = false
未取消 且
未拦截?
ACTION_DOWN 或
ACTION_POINTER_DOWN?
遍历子 View
从前到后扫描
子 View 命中测试
canReceivePointerEvents
isTransformedTouchPointInView
还有下一个
子 View?
dispatchTransformedTouchEvent
分发给命中的子 View
子 View
消费了事件?
addTouchTarget
加入 TouchTarget 链表
alreadyDispatched = true
mFirstTouchTarget
!= null?
dispatchTransformedTouchEvent
child = null
调用 super.dispatchTouchEvent
当作普通 View 处理
遍历 TouchTarget 链表
是刚分发过的
newTouchTarget?
handled = true
跳过重复分发
intercepted 为 true?
发送 ACTION_CANCEL
给该 target
从链表移除
dispatchTransformedTouchEvent
分发给 target.child
还有下一个 target?
ACTION_UP 或
ACTION_CANCEL?
resetTouchState
清空所有状态
ACTION_POINTER_UP
且 split 模式?
removePointersFromTouchTargets
移除对应 pointer
返回 handled
3.7 完整触摸事件时序图
View (子View/Button) ViewGroup (ContentView) PhoneWindow Activity DecorView ViewRootImpl (InputStage管道) WindowInputEventReceiver View (子View/Button) ViewGroup (ContentView) PhoneWindow Activity DecorView ViewRootImpl (InputStage管道) WindowInputEventReceiver === ACTION_DOWN 事件 === dispatchPointerEvent ->> dispatchTouchEvent onUserInteraction() super.dispatchTouchEvent = ViewGroup.dispatchTouchEvent ViewGroup.dispatchTouchEvent View.dispatchTouchEvent addTouchTarget(V, idBits) mFirstTouchTarget = V === ACTION_MOVE 事件 === mFirstTouchTarget != null 直接分发给已有target 不重新遍历子View 直接发给已有target === ACTION_UP 事件 === performClick() 触发点击 resetTouchState() 清空TouchTarget resetTouchState() onInputEvent(event) enqueueInputEvent() InputStage管道处理 (NativePreIme->>...->>ViewPostIme) mView.dispatchPointerEvent(DOWN) Window.Callback.dispatchTouchEvent(DOWN) getWindow().superDispatchTouchEvent(DOWN) mDecor.superDispatchTouchEvent(DOWN) cancelAndClearTouchTargets() resetTouchState() onInterceptTouchEvent(DOWN) ->> false dispatchTransformedTouchEvent(DOWN, child=VG) onInterceptTouchEvent(DOWN) ->> false dispatchTransformedTouchEvent(DOWN, child=V) OnTouchListener.onTouch(DOWN)? onTouchEvent(DOWN) return true (消费事件) return true return true (View树消费了) return true onInputEvent(MOVE) dispatchPointerEvent(MOVE) dispatchTouchEvent(MOVE) superDispatchTouchEvent(MOVE) superDispatchTouchEvent(MOVE) onInterceptTouchEvent(MOVE) ->> false dispatchTransformedTouchEvent(MOVE, child=VG) onInterceptTouchEvent(MOVE) ->> false dispatchTransformedTouchEvent(MOVE, child=V) onTouchEvent(MOVE) return true return true return true return true onInputEvent(UP) dispatchPointerEvent(UP) dispatchTouchEvent(UP) superDispatchTouchEvent(UP) superDispatchTouchEvent(UP) onInterceptTouchEvent(UP) ->> false dispatchTransformedTouchEvent(UP, child=VG) onInterceptTouchEvent(UP) ->> false dispatchTransformedTouchEvent(UP, child=V) onTouchEvent(UP) return true return true return true return true
4. 事件冲突的解决方案
4.1 外部拦截法
核心思想 :由父 ViewGroup 决定是否拦截事件。重写父 ViewGroup 的 onInterceptTouchEvent() 方法。
实现模板:
java
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
intercepted = false; // DOWN 事件不能拦截,否则子 View 收不到任何事件
break;
case MotionEvent.ACTION_MOVE:
if (/* 满足父容器的拦截条件,如水平滑动距离 > 垂直滑动距离 */) {
intercepted = true;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false; // UP 事件一般不拦截
break;
}
return intercepted;
}
原理 :在 ViewGroup.dispatchTouchEvent() 第 2663-2678 行,onInterceptTouchEvent() 在每个 MOVE 事件到来时都会被调用(只要 mFirstTouchTarget != null)。一旦返回 true,当前 TouchTarget 的子 View 会收到 ACTION_CANCEL,后续事件直接由父 ViewGroup 处理。
4.2 内部拦截法
核心思想 :由子 View 决定父 ViewGroup 是否可以拦截事件。子 View 通过调用 requestDisallowInterceptTouchEvent() 来控制父容器的拦截行为。
实现模板(子 View):
java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// DOWN 时请求父容器不要拦截
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (/* 不需要自己处理,应该让父容器处理 */) {
// 允许父容器拦截
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
return super.dispatchTouchEvent(ev);
}
配合要求 :父 ViewGroup 的 onInterceptTouchEvent() 在 ACTION_DOWN 时必须返回 false(这是默认行为),并在非 DOWN 事件时返回 true:
java
// 父 ViewGroup
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
return false; // DOWN 不拦截
}
return true; // 其他事件默认拦截
}
4.3 FLAG_DISALLOW_INTERCEPT 的工作原理
源码位置:
frameworks/base/core/java/android/view/ViewGroup.java第 393 行, 第 3244-3261 行
java
// 第 393 行
protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
// 第 3244-3261 行
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
return; // 状态未变,直接返回
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// 向上传递给父容器
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
关键特性:
- 该标志会向上递归传播到所有父级 ViewGroup。
- 在
ACTION_DOWN时,resetTouchState()会清除 该标志(第 2901 行)。因此FLAG_DISALLOW_INTERCEPT对ACTION_DOWN无效。 - 在
dispatchTouchEvent()第 2664-2673 行,如果该标志被设置,则跳过onInterceptTouchEvent()调用。
4.4 嵌套滑动机制(NestedScrolling)简介
FLAG_DISALLOW_INTERCEPT 虽然能解决简单的冲突,但对于嵌套滑动场景(如 CoordinatorLayout + RecyclerView)还不够灵活。Android 5.0 引入了 NestedScrolling 机制:
- NestedScrollingChild :子 View 实现该接口,在滑动前通过
dispatchNestedPreScroll()询问父容器是否要先消费一部分滑动距离。 - NestedScrollingParent :父容器实现该接口,在
onNestedPreScroll()中决定要消费多少滑动距离。
这种机制的优势在于:父子 View 可以协同消费同一个滑动事件,而不是简单的二选一。
在 View.java 中,ACTION_DOWN 时调用 stopNestedScroll() 来结束之前的嵌套滑动(第 16410 行)。
4.5 常见冲突场景及解决方案
| 冲突场景 | 方向冲突 | 推荐方案 | 说明 |
|---|---|---|---|
| ScrollView 嵌套 RecyclerView | 同方向(垂直+垂直) | 内部拦截法或使用 NestedScrollView | NestedScrollView 实现了 NestedScrollingParent,天然支持嵌套 |
| ViewPager 嵌套 ListView | 交叉方向(水平+垂直) | 外部拦截法 | ViewPager 已内置处理,在水平滑动时拦截事件 |
| ViewPager2 嵌套 ViewPager2 | 同方向(水平+水平) | 内部拦截法 | 子 ViewPager2 在自身可滑动时禁止父容器拦截 |
| RecyclerView 嵌套 RecyclerView | 同方向 | NestedScrolling | RecyclerView 同时实现了 Parent 和 Child 接口 |
| DrawerLayout + 内容区域滑动 | 水平冲突 | DrawerLayout 内置处理 | 边缘滑动触发抽屉,中间区域滑动传递给内容 |
4.6 外部拦截法与内部拦截法对比流程图
内部拦截法
是
否
ACTION_DOWN
子View调用
requestDisallowIntercept
true
父ViewGroup
跳过onInterceptTouchEvent
ACTION_MOVE
子View是否
需要自己处理?
保持禁止拦截
自己处理事件
调用requestDisallow
InterceptTouchEvent false
允许父容器拦截
下次MOVE
父ViewGroup
onInterceptTouchEvent
返回true拦截事件
外部拦截法
是
否
ACTION_DOWN
父ViewGroup
onInterceptTouchEvent
返回 false
事件传递给子View
ACTION_MOVE
父ViewGroup
onInterceptTouchEvent
需要拦截?
返回 true
子View收到CANCEL
后续事件由父处理
返回 false
事件继续传给子View
5. 关键源码解析
5.1 ViewRootImpl.WindowInputEventReceiver
文件:
frameworks/base/core/java/android/view/ViewRootImpl.java第 10434-10463 行
java
// ViewRootImpl.java 第 10434-10463 行
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
List<InputEvent> processedEvents;
try {
processedEvents =
mInputCompatProcessor.processInputEventForCompatibility(event);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (processedEvents != null) {
if (processedEvents.isEmpty()) {
finishInputEvent(event, true);
} else {
for (int i = 0; i < processedEvents.size(); i++) {
enqueueInputEvent(
processedEvents.get(i), this,
QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
}
}
} else {
enqueueInputEvent(event, this, 0, true); // 正常路径
}
}
// ...
}
注释说明:
WindowInputEventReceiver继承InputEventReceiver,是应用进程接收输入事件的入口。onInputEvent()由 native 层通过 JNI 回调。mInputCompatProcessor处理兼容性转换(如旧版本 API 的事件格式差异)。- 最终通过
enqueueInputEvent()将事件放入处理队列。
5.2 ViewRootImpl.ViewPostImeInputStage.processPointerEvent
文件:
frameworks/base/core/java/android/view/ViewRootImpl.java第 8011-8060 行
java
// ViewRootImpl.java 第 8011-8031 行(ViewPostImeInputStage 内部)
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
final int action = event.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mIsPressedGesture = false;
}
boolean handled = false;
if (!disableHandwritingInitiatorForIme()
|| mWindowAttributes.type != TYPE_INPUT_METHOD) {
handled = mHandwritingInitiator.onTouchEvent(event);
}
// ...
mAttachInfo.mHandlingPointerEvent = true;
// 关键调用:将事件分发给 View 树的根节点(DecorView)
handled = handled || mView.dispatchPointerEvent(event);
// ...
mAttachInfo.mHandlingPointerEvent = false;
// ...
}
注释说明:
mView即DecorView,是 View 树的根节点。dispatchPointerEvent()是事件进入 View 树的桥梁方法。mHandwritingInitiator是 Android 14+ 的手写输入检测器,优先处理手写识别。
5.3 View.dispatchPointerEvent
文件:
frameworks/base/core/java/android/view/View.java第 16741-16747 行
java
// View.java 第 16741-16747 行
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event); // 触摸事件走这里
} else {
return dispatchGenericMotionEvent(event); // 鼠标悬停等走这里
}
}
注释说明:
- 这是一个
final方法,不可被重写。 - 根据事件类型分流:触摸事件调用
dispatchTouchEvent(),其他指针事件调用dispatchGenericMotionEvent()。
5.4 DecorView.dispatchTouchEvent
文件:
frameworks/base/core/java/com/android/internal/policy/DecorView.java第 475-479 行
java
// DecorView.java 第 475-479 行
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
注释说明:
mWindow.getCallback()返回的就是Activity(Activity 实现了Window.Callback接口)。- 如果 Callback 存在、窗口未销毁、且是主窗口(
mFeatureId < 0),则将事件发送给Activity.dispatchTouchEvent()。 - 否则直接调用
super.dispatchTouchEvent()(即ViewGroup.dispatchTouchEvent())。
5.5 Activity.dispatchTouchEvent
文件:
frameworks/base/core/java/android/app/Activity.java第 4535-4543 行
java
// Activity.java 第 4535-4543 行
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); // 通知有用户交互
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true; // View 树消费了事件
}
return onTouchEvent(ev); // 所有 View 都不消费,Activity 最后处理
}
注释说明:
onUserInteraction()在每个手势的 DOWN 事件时调用,可用于重置屏幕超时。getWindow().superDispatchTouchEvent()将事件回传给 View 树(PhoneWindow -> DecorView -> ViewGroup)。- 如果 View 树不消费,
Activity.onTouchEvent()是最后的处理机会。
5.6 PhoneWindow.superDispatchTouchEvent
文件:
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java第 1980-1982 行
java
// PhoneWindow.java 第 1980-1982 行
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
文件:
frameworks/base/core/java/com/android/internal/policy/DecorView.java第 519-521 行
java
// DecorView.java 第 519-521 行
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event); // 调用 ViewGroup.dispatchTouchEvent
}
注释说明:
- PhoneWindow 简单地将调用委托给 DecorView。
- DecorView 的
superDispatchTouchEvent()调用super.dispatchTouchEvent(),即跳过 DecorView 自己的dispatchTouchEvent()(避免再次回到 Activity),直接调用ViewGroup.dispatchTouchEvent()开始在 View 树中递归分发。
5.7 View.dispatchTouchEvent
文件:
frameworks/base/core/java/android/view/View.java第 16391-16431 行
java
// View.java 第 16391-16431 行
public boolean dispatchTouchEvent(MotionEvent event) {
// 无障碍焦点处理
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll(); // 新手势开始,停止嵌套滚动
}
if (onFilterTouchEventForSecurity(event)) {
result = performOnTouchCallback(event); // 核心处理逻辑
}
// 手势结束或 DOWN 未被消费时,停止嵌套滚动
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
5.8 View.performOnTouchCallback
文件:
frameworks/base/core/java/android/view/View.java第 16437-16465 行
java
// View.java 第 16437-16465 行
private boolean performOnTouchCallback(MotionEvent event) {
boolean handled = false;
// 1. 滚动条拖拽处理
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
handled = true;
}
// 2. OnTouchListener 优先处理
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) {
handled = li.mOnTouchListener.onTouch(this, event);
}
if (handled) {
return true; // listener 消费了,不再调用 onTouchEvent
}
// 3. 调用 onTouchEvent
return onTouchEvent(event);
}
注释说明:
- 优先级:
OnTouchListener.onTouch()>onTouchEvent() - 只有 View 是
ENABLED状态时才会调用OnTouchListener - 如果
OnTouchListener返回 true,onTouchEvent()不会被调用
5.9 ViewGroup.dispatchTouchEvent
文件:
frameworks/base/core/java/android/view/ViewGroup.java第 2635-2853 行
(由于此方法代码量较大,这里列出核心结构,完整代码见第 3.3 节的逐段分析)
java
// ViewGroup.java 第 2635-2853 行(简化结构)
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// 阶段1:ACTION_DOWN 时清空状态
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 阶段2:拦截检查
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
// 阶段3:遍历子 View(仅 DOWN/POINTER_DOWN)
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN || ...) {
// 从前到后扫描子 View,命中测试,分发事件
// 消费则 addTouchTarget
}
}
// 阶段4:分发给 TouchTarget 或自己
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null, ALL_POINTER_IDS);
} else {
// 遍历 TouchTarget 链表分发
}
// 阶段5:清理
if (canceled || actionMasked == MotionEvent.ACTION_UP) {
resetTouchState();
}
}
return handled;
}
5.10 ViewGroup.dispatchTransformedTouchEvent
文件:
frameworks/base/core/java/android/view/ViewGroup.java第 3094-3164 行
java
// ViewGroup.java 第 3094-3164 行
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final int oldAction = event.getAction();
try {
final boolean handled;
if (cancel) {
event.setAction(MotionEvent.ACTION_CANCEL); // 强制设为 CANCEL
}
// 计算要传递的 pointer
final int oldPointerIdBits = event.getPointerIdBits();
int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
if (newPointerIdBits == 0) {
if (event.getAction() != MotionEvent.ACTION_CANCEL) {
return false; // 非 CANCEL 且无匹配 pointer,丢弃
} else {
newPointerIdBits = oldPointerIdBits; // CANCEL 保留所有 pointer
}
}
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
// pointer 数量相同,可以复用或简单偏移
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event); // 自己处理
} else {
// 坐标偏移后分发给子 View
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY); // 恢复坐标
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits); // 多点触控分离
}
// 应用矩阵变换后分发
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (!child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix()); // 逆矩阵变换
}
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
} finally {
event.setAction(oldAction); // 恢复原始 action
}
}
注释说明:
child == null时调用super.dispatchTouchEvent(),即View.dispatchTouchEvent(),将 ViewGroup 当作普通 View 处理。- 坐标变换:先偏移(
offsetLocation),如果有矩阵变换(如旋转、缩放),还需要应用逆矩阵。 - 多点触控时,通过
event.split(newPointerIdBits)将事件拆分为只包含特定 pointer 的子事件。
5.11 ViewGroup.onInterceptTouchEvent
文件:
frameworks/base/core/java/android/view/ViewGroup.java第 3300-3308 行
java
// ViewGroup.java 第 3300-3308 行
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getXDispatchLocation(0), ev.getYDispatchLocation(0))) {
return true; // 鼠标点击滚动条时拦截
}
return false;
}
注释说明:
- 默认实现几乎不拦截任何事件。
- 唯一例外:鼠标左键点击滚动条时拦截(为了支持鼠标拖拽滚动条)。
- 自定义 ViewGroup 需要重写此方法来实现拦截逻辑。
5.12 View.onTouchEvent
文件:
frameworks/base/core/java/android/view/View.java第 17898-18097 行
java
// View.java 第 17898-18097 行(关键片段)
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
// clickable 判断:CLICKABLE || LONG_CLICKABLE || CONTEXT_CLICKABLE
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// DISABLED 状态:clickable 的 View 仍消费事件
if ((viewFlags & ENABLED_MASK) == DISABLED
&& (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
return clickable;
}
// TouchDelegate 处理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
// ... 执行点击:performClickInternal()
break;
case MotionEvent.ACTION_DOWN:
// ... 设置按下状态,启动长按检测
break;
case MotionEvent.ACTION_CANCEL:
// ... 清除所有状态
break;
case MotionEvent.ACTION_MOVE:
// ... 检查是否移出边界,更新热点
break;
}
return true; // clickable 的 View 总是消费事件
}
return false;
}
5.13 TouchTarget 内部类
文件:
frameworks/base/core/java/android/view/ViewGroup.java第 8964-9023 行
java
// ViewGroup.java 第 8964-9023 行
private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // 所有位都为1
public View child; // 被触摸的子 View
public int pointerIdBits; // 该 target 处理的 pointer ID 位掩码
public TouchTarget next; // 链表的下一个节点
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
// 从对象池获取或新建
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
target = new TouchTarget();
} else {
target = sRecycleBin;
sRecycleBin = target.next;
sRecycledCount--;
target.next = null;
}
}
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
public void recycle() {
// 回收到对象池
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
next = sRecycleBin;
sRecycleBin = this;
sRecycledCount += 1;
}
child = null;
}
}
}
注释说明:
- TouchTarget 使用对象池模式(Object Pool),避免频繁 GC。最多缓存 32 个对象。
pointerIdBits是位掩码,每一位对应一个 pointer ID。ALL_POINTER_IDS = -1表示所有 pointer。- 链表结构通过
next字段实现,mFirstTouchTarget是链表头。
5.14 ViewGroup.requestDisallowInterceptTouchEvent
文件:
frameworks/base/core/java/android/view/ViewGroup.java第 3244-3261 行
java
// ViewGroup.java 第 3244-3261 行
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// 优化:状态未变时直接返回
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// 递归向上传递
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
6. 多点触控的事件分发
6.1 MotionEvent 中 Pointer 的概念
在 Android 中,每一个触摸点(手指)被称为一个 Pointer。MotionEvent 可以同时包含多个 Pointer 的信息。
关键概念:
- Pointer Index:Pointer 在当前 MotionEvent 中的索引(0, 1, 2, ...),每次事件可能变化。
- Pointer ID :Pointer 的唯一标识符,在整个手势过程中保持不变。通过
event.getPointerId(index)获取。 - Action Index :
ACTION_POINTER_DOWN/ACTION_POINTER_UP事件中,发生变化的 Pointer 的索引。通过event.getActionIndex()获取。
源码位置:
frameworks/base/core/java/android/view/MotionEvent.java
ACTION_POINTER_DOWN = 5 (第 278 行)
ACTION_POINTER_UP = 6 (第 289 行)
事件编码方式:action = ACTION_POINTER_DOWN | (pointerIndex << ACTION_POINTER_INDEX_SHIFT)
6.2 ACTION_POINTER_DOWN/UP 的分发逻辑
在 ViewGroup.dispatchTouchEvent() 中,多点触控的关键处理在第 2705-2710 行:
java
// ViewGroup.java 第 2705-2710 行
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
分发规则:
-
启用 split 模式 (
FLAG_SPLIT_MOTION_EVENTS,Android 3.0+ 默认启用)时:ACTION_DOWN:第一个手指按下,进行正常的子 View 遍历和命中测试。ACTION_POINTER_DOWN:新手指按下时,再次进行子 View 遍历和命中测试。这意味着新手指可能命中不同的子 View。- 每个 Pointer 的 ID 被记录在
TouchTarget.pointerIdBits中。
-
不启用 split 模式时:
idBitsToAssign = TouchTarget.ALL_POINTER_IDS,所有 pointer 都分配给同一个 TouchTarget。- 所有手指的事件都发送给同一个子 View。
-
ACTION_POINTER_UP的处理(第 2842-2846 行):java} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); }当一个非最后的手指抬起时,从 TouchTarget 中移除对应的 pointer ID 位。如果某个 TouchTarget 的所有 pointer 都被移除,该 target 也会被移除。
6.3 多个 TouchTarget 对应不同子 View
在 split 模式下,一个 ViewGroup 可以同时拥有多个 TouchTarget,每个 target 对应不同的子 View 和不同的 pointer 集合。
示例场景:
假设一个 LinearLayout 中有两个 Button(ButtonA 和 ButtonB):
-
第一个手指(pointer 0)按在 ButtonA 上:
ACTION_DOWN触发- 命中 ButtonA,创建
TouchTarget(child=ButtonA, pointerIdBits=0b01) mFirstTouchTarget->{ButtonA, 0b01}
-
第二个手指(pointer 1)按在 ButtonB 上:
ACTION_POINTER_DOWN触发- 命中 ButtonB,创建
TouchTarget(child=ButtonB, pointerIdBits=0b10) mFirstTouchTarget->{ButtonB, 0b10}->{ButtonA, 0b01}
-
后续的
ACTION_MOVE事件:- 遍历 TouchTarget 链表
- 对 ButtonA 的 target:通过
event.split(0b01)提取只包含 pointer 0 的事件,分发给 ButtonA - 对 ButtonB 的 target:通过
event.split(0b10)提取只包含 pointer 1 的事件,分发给 ButtonB
-
第二个手指抬起(pointer 1):
ACTION_POINTER_UP触发removePointersFromTouchTargets(0b10)移除 pointer 1- ButtonB 的 target 被移除
mFirstTouchTarget->{ButtonA, 0b01}
-
如果新手指按在已有 target 的 View 上(第 2750-2756 行):
javanewTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // 子 View 已经在接收触摸 // 将新 pointer 追加到已有 target newTouchTarget.pointerIdBits |= idBitsToAssign; break; }
dispatchTransformedTouchEvent 中的 split 处理(第 3139-3141 行):
java
if (newPointerIdBits == oldPointerIdBits) {
// pointer 数量相同,直接转发
// ...
} else {
transformedEvent = event.split(newPointerIdBits); // 拆分事件
}
event.split() 方法会创建一个新的 MotionEvent,只包含指定 pointer ID 的触摸数据。这确保每个子 View 只收到属于自己的 pointer 信息。
附录:关键源码文件索引
| 文件 | 路径 | 关键行号 |
|---|---|---|
| View.java | frameworks/base/core/java/android/view/View.java |
dispatchTouchEvent: 16391, performOnTouchCallback: 16437, onTouchEvent: 17898, dispatchPointerEvent: 16741 |
| ViewGroup.java | frameworks/base/core/java/android/view/ViewGroup.java |
dispatchTouchEvent: 2635, onInterceptTouchEvent: 3300, dispatchTransformedTouchEvent: 3094, requestDisallowInterceptTouchEvent: 3244, TouchTarget: 8964, addTouchTarget: 2975, resetTouchState: 2898, cancelAndClearTouchTargets: 2935 |
| ViewRootImpl.java | frameworks/base/core/java/android/view/ViewRootImpl.java |
InputStage管道: 1742, InputStage基类: 7151, ViewPostImeInputStage: 7779, processPointerEvent: 8011, WindowInputEventReceiver: 10434, enqueueInputEvent: 10203 |
| Activity.java | frameworks/base/core/java/android/app/Activity.java |
dispatchTouchEvent: 4535, onTouchEvent: 4285 |
| DecorView.java | frameworks/base/core/java/com/android/internal/policy/DecorView.java |
dispatchTouchEvent: 475, superDispatchTouchEvent: 519, onInterceptTouchEvent: 546 |
| PhoneWindow.java | frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java |
superDispatchTouchEvent: 1980 |
| Window.java | frameworks/base/core/java/android/view/Window.java |
Callback接口: 354, superDispatchTouchEvent(抽象): 该文件中声明 |
| InputEventReceiver.java | frameworks/base/core/java/android/view/InputEventReceiver.java |
基类定义: 38 |
| MotionEvent.java | frameworks/base/core/java/android/view/MotionEvent.java |
ACTION常量: 219-289 |