事件分发机制
# 08.事件分发机制
# 目录介绍
- 一、引言:触摸屏幕后发生了什么
- 二、输入事件从硬件到应用的传递链路
- 2.1 完整事件传递链
- 2.2 InputChannel通信机制
- 三、InputManagerService与事件读取
- 3.1 InputReader的工作
- 3.2 触摸事件的坐标处理
- 3.3 EventHub与设备管理
- 四、InputDispatcher的分发策略
- 4.1 查找目标窗口
- 4.2 ANR检测
- 4.3 InputDispatcher的焦点窗口管理
- 4.4 事件序列的一致性保证
- 五、ViewRootImpl接收事件
- 5.1 InputStage管道
- 六、Activity的事件分发
- 6.1 从DecorView到Activity
- 6.2 事件分发的层级
- 七、ViewGroup的事件分发
- 7.1 dispatchTouchEvent核心逻辑
- 7.2 关键变量mFirstTouchTarget
- 八、View的事件处理
- 8.1 View.dispatchTouchEvent
- 8.2 View.onTouchEvent
- 九、事件分发的核心伪代码
- 9.1 经典的三方法伪代码
- 9.2 事件序列的完整分发示例
- 十、滑动冲突的解决方案
- 10.1 常见滑动冲突场景
- 10.2 外部拦截法
- 10.3 内部拦截法
- 十一、MotionEvent与多点触控
- 11.1 MotionEvent的事件类型
- 11.2 多点触控编程
- 十二、GestureDetector与手势识别
- 12.1 GestureDetector的工作原理
- 12.2 手势识别的时间窗口
- 12.3 VelocityTracker速度追踪
- 十三、面试高频问题与深度分析
- 13.1 onTouchListener、onTouchEvent、onClickListener的优先级?
- 13.2 ACTION_DOWN返回false会怎样?
- 13.3 requestDisallowInterceptTouchEvent的原理?
- 13.4 Scroller与惯性滑动的原理
- 13.5 NestedScrolling嵌套滚动机制
- 十四、事件分发案例分析
- 14.1 典型布局案例
- 14.2 默认处理情况
- 14.3 View C处理事件
- 14.4 ViewGroup B拦截DOWN事件
- 14.5 拦截后续事件(MOVE、UP)
- 14.6 滑动冲突解决方案
- 十五、总结
# 一、引言:触摸屏幕后发生了什么
当手指触摸Android设备屏幕时,从硬件产生中断到应用层View响应点击,中间经历了一条精密的事件处理链路。理解事件分发机制,是处理滑动冲突、自定义手势交互、优化触摸响应的基础。
疑惑:为什么事件分发要经过三个方法(dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent)?为什么父View可以"拦截"子View的事件?
# 二、输入事件从硬件到应用的传递链路
# 2.1 完整事件传递链
手指触摸屏幕
↓
触摸屏控制器产生中断(I2C/SPI协议)
↓
Linux内核 Input子系统
├── 触摸屏驱动读取坐标数据
├── 生成input_event结构体
└── 写入 /dev/input/eventN
↓
InputReader(Native线程)
├── 从/dev/input/eventN读取原始事件
├── 转换为Android的InputEvent格式
└── 放入InputDispatcher的队列
↓
InputDispatcher(Native线程)
├── 确定目标窗口(哪个Window应该接收事件)
├── 通过InputChannel发送事件
└── 等待应用回复(ANR计时器启动)
↓
App进程 ViewRootImpl
├── 通过InputChannel接收事件
├── InputStage管道处理
└── 分发到View树
↓
Activity → ViewGroup → View
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 2.2 InputChannel通信机制
InputChannel是App与InputDispatcher之间的通信通道:
InputDispatcher(system_server) App进程
┌──────────────────┐ ┌──────────────────┐
│ InputChannel │ │ InputChannel │
│ (Server端) │ │ (Client端) │
│ │ socketpair │ │
│ fd[0] ──────────│──────────────────│── fd[1] │
│ │ │ │
│ 写入InputMessage │─→ Unix Socket ─→│ 读取InputMessage │
│ │ │ │
│ 读取回复信号 │←─ Unix Socket ←─│ 写入finish信号 │
└──────────────────┘ └──────────────────┘
创建时机:WMS.addWindow()时创建InputChannel对
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 三、InputManagerService与事件读取
# 3.1 InputReader的工作
// InputReader运行在独立线程,持续从设备文件读取事件
void InputReader::loopOnce() {
// 1. 从EventHub读取原始事件
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
// 2. 处理原始事件
if (count) {
processEventsLocked(mEventBuffer, count);
// 将Linux input_event转换为Android RawEvent
// 多点触控处理(协议A/B)
// 坐标映射(触摸屏坐标→屏幕坐标)
}
// 3. 刷新到InputDispatcher
mQueuedListener->flush();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.2 触摸事件的坐标处理
触摸屏原始数据处理流程:
1. 驱动层:产生原始坐标(rawX, rawY)
→ 触摸屏分辨率可能与屏幕分辨率不同
2. InputReader:坐标映射
screenX = rawX * displayWidth / touchWidth
screenY = rawY * displayHeight / touchHeight
→ 加上旋转变换(横屏/竖屏)
3. InputDispatcher:窗口坐标转换
localX = screenX - window.left
localY = screenY - window.top
→ 转换为目标窗口的本地坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.3 EventHub与设备管理
EventHub是InputReader的底层实现,负责管理所有输入设备:
EventHub的工作机制:
┌─────────────────────────────────────┐
│ EventHub │
│ │
│ 使用epoll监控: │
│ ├── /dev/input/event0 (触摸屏) │
│ ├── /dev/input/event1 (物理按键) │
│ ├── /dev/input/event2 (加速度传感器) │
│ ├── inotify fd (监控设备热插拔) │
│ └── wake pipe (唤醒用) │
│ │
│ epoll_wait() 阻塞等待 │
│ → 有事件到来时返回 │
│ → 读取struct input_event │
│ ├── type: EV_ABS (触摸) │
│ ├── code: ABS_MT_POSITION_X │
│ └── value: 坐标值 │
└─────────────────────────────────────┘
多点触控协议:
Protocol A(旧设备):
EV_ABS ABS_MT_POSITION_X x1
EV_ABS ABS_MT_POSITION_Y y1
EV_SYN SYN_MT_REPORT (分隔点)
EV_ABS ABS_MT_POSITION_X x2
EV_ABS ABS_MT_POSITION_Y y2
EV_SYN SYN_MT_REPORT (分隔点)
EV_SYN SYN_REPORT (帧结束)
Protocol B(主流设备,支持tracking ID):
EV_ABS ABS_MT_SLOT 0 (切换到触摸点0)
EV_ABS ABS_MT_TRACKING_ID 100
EV_ABS ABS_MT_POSITION_X x1
EV_ABS ABS_MT_POSITION_Y y1
EV_ABS ABS_MT_SLOT 1 (切换到触摸点1)
EV_ABS ABS_MT_TRACKING_ID 101
EV_ABS ABS_MT_POSITION_X x2
EV_SYN SYN_REPORT
→ Protocol B只发送变化的属性,带宽更低
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 四、InputDispatcher的分发策略
# 4.1 查找目标窗口
// InputDispatcher确定哪个窗口应该接收事件
int32_t InputDispatcher::findTouchedWindowTargetsLocked(...) {
// 1. 获取所有可见窗口列表(按Z轴排序,从前到后)
// 2. 对于DOWN事件,进行命中测试
for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
const WindowInfo& info = *windowHandle->getInfo();
// 检查坐标是否在窗口范围内
if (info.frameContainsPoint(x, y)) {
// 检查窗口是否可以接收触摸事件
if (info.inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE)) {
continue; // 不可触摸,继续查找下层窗口
}
// 找到了目标窗口
touchedWindow = windowHandle;
break;
}
}
// 3. 对于MOVE/UP事件,使用DOWN时确定的目标窗口
// (同一个手势序列始终发送到同一个窗口)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 4.2 ANR检测
InputDispatcher的ANR检测机制:
1. 发送事件到App → 启动5秒定时器
2. App处理完毕 → 发送finish信号 → 取消定时器
3. 5秒内未收到finish → 触发ANR
时序:
InputDispatcher App
│ dispatch event + start timer │
│──────────────────────────────→ │
│ │ 处理事件...
│ finish signal │
│←────────────────────────────── │
│ cancel timer │
超时情况:
│ dispatch event + start 5s timer │
│──────────────────────────────→ │
│ │ 主线程被阻塞...
│ 5s timeout → ANR! │
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 4.3 InputDispatcher的焦点窗口管理
InputDispatcher维护两类窗口状态:
1. 触摸窗口(Touch Window)
→ 通过坐标命中测试确定
→ 同一事件序列固定发给同一窗口
→ TouchState记录每个Display的触摸状态
2. 焦点窗口(Focused Window)
→ 接收按键事件(KEY_EVENT)
→ 由WMS通过InputMonitor设置
→ 一个Display只有一个焦点窗口
按键事件(如返回键)的分发:
InputDispatcher.dispatchKeyLocked()
→ findFocusedWindowTargetLocked()
→ 获取当前焦点窗口
→ 如果焦点窗口还没准备好 → 等待(可能ANR)
→ dispatchEventLocked(focusedWindow)
触摸事件的分发:
InputDispatcher.dispatchMotionLocked()
→ findTouchedWindowTargetsLocked()
→ 坐标命中测试
→ 考虑FLAG_NOT_TOUCHABLE、FLAG_NOT_TOUCH_MODAL等标志
→ dispatchEventLocked(touchedWindow)
窗口的输入特性标志:
FLAG_NOT_FOCUSABLE → 不接收焦点(如Toast)
FLAG_NOT_TOUCHABLE → 不接收触摸事件
FLAG_NOT_TOUCH_MODAL → 允许事件穿透到下层窗口
FLAG_WATCH_OUTSIDE_TOUCH → 接收窗口外的触摸事件(ACTION_OUTSIDE)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 4.4 事件序列的一致性保证
InputDispatcher保证事件序列的完整性:
一个完整的事件序列:
DOWN → MOVE → MOVE → ... → UP
└── 必须发给同一个窗口
保证机制:
1. DOWN事件确定目标窗口 → 记录到TouchState
2. 后续MOVE/UP事件 → 直接从TouchState获取目标窗口
3. 即使窗口移动或层级变化 → 不影响正在进行的事件序列
4. 只有新的DOWN事件才会重新查找目标窗口
异常处理:
目标窗口被销毁:
→ InputDispatcher检测到窗口不再存在
→ 发送CANCEL事件给已消失的窗口(通过GC清理)
→ 新的事件序列会查找新的目标窗口
目标App无响应:
→ InputDispatcher等待finish信号
→ 5秒超时后触发ANR
→ 但不会丢弃事件,事件在队列中等待
→ 用户可以选择等待或关闭App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 五、ViewRootImpl接收事件
# 5.1 InputStage管道
// ViewRootImpl中的事件处理管道(责任链模式)
// 事件依次经过多个Stage处理
void setView(View view, ...) {
// 构建InputStage管道
mSyntheticInputStage = new SyntheticInputStage(); // 合成事件(如轨迹球)
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, ...);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage, ...); // 输入法
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, ...);
mFirstInputStage = nativePreImeStage;
}
// 事件流向:
// NativePreIme → ViewPreIme → IME → EarlyPostIme → NativePostIme → ViewPostIme
// → Synthetic
// ViewPostImeInputStage 是触摸事件分发到View树的入口
final class ViewPostImeInputStage extends InputStage {
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof MotionEvent) {
// 触摸事件分发到View树
boolean handled = mView.dispatchPointerEvent(event);
// mView = DecorView
return handled ? FINISH_HANDLED : FORWARD;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 六、Activity的事件分发
# 6.1 从DecorView到Activity
// DecorView.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
// Window.Callback就是Activity(Activity实现了Window.Callback)
return cb != null ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
// Activity.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); // 通知用户交互(可用于重置屏幕超时)
}
// 先让Window(PhoneWindow)处理
if (getWindow().superDispatchTouchEvent(ev)) {
return true; // View树处理了事件
}
// View树没有处理 → Activity自己处理
return onTouchEvent(ev);
}
// PhoneWindow.superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
// 调用DecorView的super.dispatchTouchEvent → ViewGroup.dispatchTouchEvent
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 6.2 事件分发的层级
事件分发顺序:
Activity.dispatchTouchEvent()
→ PhoneWindow.superDispatchTouchEvent()
→ DecorView.superDispatchTouchEvent()
→ ViewGroup.dispatchTouchEvent() (DecorView继承自FrameLayout)
→ 子ViewGroup.dispatchTouchEvent()
→ 子View.dispatchTouchEvent()
→ View.onTouchEvent()
← 未消费,返回false
← 未消费,返回false
← 未消费,返回false
← 未消费,返回false
← 未消费
→ Activity.onTouchEvent() ← 最后的兜底处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 七、ViewGroup的事件分发
# 7.1 dispatchTouchEvent核心逻辑
// ViewGroup.dispatchTouchEvent() 简化版
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
final int action = ev.getActionMasked();
// 1. 检查是否拦截
final boolean intercepted;
if (action == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// 检查子View是否请求不拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev); // 是否拦截?
} else {
intercepted = false; // 子View要求不拦截
}
} else {
// 非DOWN且无子View处理 → 自己拦截
intercepted = true;
}
// 2. 如果不拦截,分发给子View
if (!intercepted) {
if (action == MotionEvent.ACTION_DOWN) {
// 遍历子View,找到能接收事件的
for (int i = childrenCount - 1; i >= 0; i--) { // 从上层到下层
final View child = getAndVerifyPreorderedView(preorderedList, children, i);
// 检查坐标是否在子View范围内
if (!child.canReceivePointerEvents() ||
!isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
// 分发给子View
if (dispatchTransformedTouchEvent(ev, child)) {
// 子View消费了事件
mFirstTouchTarget = addTouchTarget(child, idBitsToAssign);
handled = true;
break;
}
}
}
}
// 3. 没有子View处理,自己处理
if (mFirstTouchTarget == null) {
// 调用View.dispatchTouchEvent → onTouchEvent
handled = dispatchTransformedTouchEvent(ev, null);
} else {
// 4. MOVE/UP事件分发给之前接收DOWN的子View
TouchTarget target = mFirstTouchTarget;
while (target != null) {
if (intercepted) {
// 被拦截了,给子View发CANCEL
dispatchTransformedTouchEvent(ev, true, target.child, ...);
} else {
handled = dispatchTransformedTouchEvent(ev, false, target.child, ...);
}
target = target.next;
}
}
// 5. UP或CANCEL时清理状态
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
resetTouchState();
}
return handled;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 7.2 关键变量mFirstTouchTarget
mFirstTouchTarget的作用:记录消费了DOWN事件的子View
ACTION_DOWN时:
1. 遍历子View → 找到消费DOWN的子View
2. mFirstTouchTarget = 该子View
后续MOVE/UP事件时:
if (mFirstTouchTarget != null) {
// 直接发给mFirstTouchTarget,不再遍历
}
如果没有子View消费DOWN:
mFirstTouchTarget == null
→ 所有后续事件都由ViewGroup自己的onTouchEvent处理
→ 且不再调用onInterceptTouchEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 八、View的事件处理
# 8.1 View.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
// 优先级1:OnTouchListener
if (mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnTouchListener.onTouch(this, event)) {
result = true; // OnTouchListener消费了事件
}
// 优先级2:onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
return result;
}
// 消费优先级:OnTouchListener.onTouch() > onTouchEvent() > OnClickListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 8.2 View.onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
// 可点击的View默认消费所有事件
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_DOWN:
// 检查是否在滚动容器中
mHasPerformedLongPress = false;
// 发送长按检测(500ms后触发)
checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
break;
case MotionEvent.ACTION_MOVE:
// 如果移出了View范围,取消长按
if (!pointInView(x, y, touchSlop)) {
removeLongPressCallback();
removeTapCallback();
}
break;
case MotionEvent.ACTION_UP:
if (!mHasPerformedLongPress) {
// 未触发长按 → 执行点击
removeLongPressCallback();
performClickInternal();
// → OnClickListener.onClick()
}
break;
case MotionEvent.ACTION_CANCEL:
// 清理所有状态
removeLongPressCallback();
removeTapCallback();
break;
}
return true; // clickable的View总是消费事件
}
return false; // 不可点击的View不消费事件
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 九、事件分发的核心伪代码
# 9.1 经典的三方法伪代码
// 这段伪代码高度概括了Android事件分发机制
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)) {
// 拦截:自己处理
consume = onTouchEvent(event);
} else {
// 不拦截:交给子View
consume = child.dispatchTouchEvent(event);
}
return consume;
}
// 注意:这是简化版,实际逻辑更复杂:
// 1. onInterceptTouchEvent只有ViewGroup有,View没有
// 2. 只有DOWN事件会遍历子View,MOVE/UP直接发给mFirstTouchTarget
// 3. 子View可以通过requestDisallowInterceptTouchEvent阻止父View拦截
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 9.2 事件序列的完整分发示例
手指按下(DOWN)、移动(MOVE)、抬起(UP)的分发过程:
假设View层级:Activity → ViewGroupA → ViewGroupB → ViewC
ACTION_DOWN分发:
Activity.dispatch
→ ViewGroupA.dispatch
→ ViewGroupA.onIntercept → false(不拦截)
→ ViewGroupB.dispatch
→ ViewGroupB.onIntercept → false(不拦截)
→ ViewC.dispatch
→ ViewC.onTouch → true(消费了!)
← return true
← return true(记录mFirstTouchTarget=ViewC)
← return true(记录mFirstTouchTarget=ViewGroupB)
← return true
后续ACTION_MOVE分发:
Activity.dispatch
→ ViewGroupA.dispatch
→ ViewGroupA.onIntercept → false
→ ViewGroupB.dispatch(直接发给mFirstTouchTarget=ViewC)
→ ViewGroupB.onIntercept → false
→ ViewC.dispatch
→ ViewC.onTouch → true
← return true
← return true
← return true
← return true
注意:MOVE不再遍历所有子View,直接发给DOWN时确定的目标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 十、滑动冲突的解决方案
# 10.1 常见滑动冲突场景
场景1:外部横向滑动 + 内部横向滑动(如ViewPager嵌套ViewPager)
→ 判断依据:根据滑动方向或速度区分哪一层应该响应
→ 典型方案:外部ViewPager处理页面级滑动,内部处理细粒度滑动
场景2:外部竖向滑动 + 内部横向滑动(如ScrollView嵌套ViewPager)
→ 判断依据:比较水平滑动距离和垂直滑动距离
→ dx > dy → 内部处理(横向滑动)
→ dy > dx → 外部处理(纵向滚动)
→ 这是最常见且最容易解决的场景
场景3:外部竖向滑动 + 内部竖向滑动(如ScrollView嵌套RecyclerView)
→ 判断依据:内部View是否滑到边界
→ 内部没滑到底 → 内部处理
→ 内部已滑到底 → 外部处理
→ NestedScrolling协议专门为此设计
滑动冲突的本质:
一个事件序列(DOWN→MOVE→UP)只能被一个View消费
多个可滑动View嵌套时,系统无法自动判断用户意图
→ 需要开发者通过拦截/不拦截来指定消费者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 10.2 外部拦截法
// 在父ViewGroup中重写onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false; // DOWN一定不能拦截(否则子View收不到事件序列)
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastInterceptX;
int deltaY = y - mLastInterceptY;
if (needIntercept(deltaX, deltaY)) {
intercepted = true; // 根据滑动方向决定是否拦截
}
break;
case MotionEvent.ACTION_UP:
intercepted = false; // UP一般不拦截
break;
}
mLastInterceptX = x;
mLastInterceptY = y;
return intercepted;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 10.3 内部拦截法
// 在子View中通过requestDisallowInterceptTouchEvent控制
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 请求父View不要拦截
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (parentNeedEvent(deltaX, deltaY)) {
// 允许父View拦截
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
// 父ViewGroup需要配合:
public boolean onInterceptTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
return false; // DOWN不拦截
}
return true; // 其他事件默认拦截(但子View可以通过disallow阻止)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 十一、MotionEvent与多点触控
# 11.1 MotionEvent的事件类型
单点触控事件:
ACTION_DOWN → 第一个手指按下
ACTION_MOVE → 手指移动
ACTION_UP → 最后一个手指抬起
ACTION_CANCEL → 事件被取消(被父View拦截时发送给子View)
多点触控事件:
ACTION_POINTER_DOWN → 第二个及以后的手指按下
ACTION_POINTER_UP → 非最后一个手指抬起
事件中包含的信息:
├── action → 事件类型
├── x, y → 坐标
├── pressure → 触摸压力
├── size → 触摸面积
├── pointerCount → 当前触摸点数量
├── pointerId → 触摸点ID(从按下到抬起保持不变)
└── pointerIndex → 触摸点索引(在事件中的位置,会变化)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 11.2 多点触控编程
// 处理双指缩放的示例
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
if (event.getPointerCount() == 2) {
// 两指按下,计算初始距离
mInitialDistance = getDistance(event);
}
break;
case MotionEvent.ACTION_MOVE:
if (event.getPointerCount() == 2) {
float currentDistance = getDistance(event);
float scale = currentDistance / mInitialDistance;
applyScale(scale);
}
break;
}
return true;
}
private float getDistance(MotionEvent event) {
float dx = event.getX(0) - event.getX(1);
float dy = event.getY(0) - event.getY(1);
return (float) Math.sqrt(dx * dx + dy * dy);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 十二、GestureDetector与手势识别
# 12.1 GestureDetector的工作原理
// GestureDetector内部使用Handler定时器检测各种手势
public class GestureDetector {
// 关键时间阈值
private static final int TAP_TIMEOUT = 100; // 100ms
private static final int LONG_PRESS_TIMEOUT = 500; // 500ms
private static final int DOUBLE_TAP_TIMEOUT = 300; // 300ms
// 关键距离阈值
private int mTouchSlopSquare; // 最小滑动距离的平方
private int mDoubleTapTouchSlopSquare;
public boolean onTouchEvent(MotionEvent ev) {
switch (action) {
case MotionEvent.ACTION_DOWN:
// 检查是否可能是双击的第二次点击
if (mIsDoubleTapping) {
mListener.onDoubleTap(ev);
}
// 发送长按延迟消息
mHandler.sendEmptyMessageDelayed(LONG_PRESS, LONG_PRESS_TIMEOUT);
break;
case MotionEvent.ACTION_MOVE:
float distance = (deltaX * deltaX) + (deltaY * deltaY);
if (distance > mTouchSlopSquare) {
// 超过最小滑动距离 → 滚动
mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
// 取消长按检测
mHandler.removeMessages(LONG_PRESS);
}
break;
case MotionEvent.ACTION_UP:
if (isDoubleTap) {
mListener.onDoubleTapEvent(ev);
} else if (mIsLongPress) {
// 已触发长按,忽略UP
} else {
// 检查速度→判断是Fling还是单击
if (velocityX > mMinimumFlingVelocity) {
mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
} else {
mListener.onSingleTapUp(ev);
}
}
break;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 12.2 手势识别的时间窗口
各种手势的识别条件:
单击(onSingleTapConfirmed):
├── 按下后抬起
├── 移动距离 < touchSlop
├── 持续时间 < longPressTimeout
└── 300ms内没有第二次点击(排除双击)
双击(onDoubleTap):
├── 两次单击间隔 < 300ms
├── 两次点击位置距离 < doubleTapSlop
└── 第二次DOWN时立即触发
长按(onLongPress):
├── 按下后持续500ms不移动
├── 移动距离 < touchSlop
└── 通过Handler.postDelayed实现定时
Fling(onFling):
├── 按下→移动→快速抬起
├── 抬起时速度 > minimumFlingVelocity
├── 抬起时速度 < maximumFlingVelocity
└── 速度由VelocityTracker计算
Scroll(onScroll):
├── 按下后移动距离 > touchSlop
└── 每次MOVE事件时持续触发
touchSlop的作用:
→ 系统定义的最小滑动识别距离(一般8dp)
→ 消除手指按下时的微小抖动
→ ViewConfiguration.get(context).getScaledTouchSlop()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 12.3 VelocityTracker速度追踪
// 计算手指滑动速度
VelocityTracker velocityTracker = VelocityTracker.obtain();
public boolean onTouchEvent(MotionEvent event) {
velocityTracker.addMovement(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
// 计算过去1000ms内的平均速度(像素/秒)
velocityTracker.computeCurrentVelocity(1000);
float vx = velocityTracker.getXVelocity();
float vy = velocityTracker.getYVelocity();
// 判断是否是Fling
if (Math.abs(vx) > mMinFlingVelocity) {
startFling(vx, vy);
}
velocityTracker.clear();
}
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 十三、面试高频问题与深度分析
# 13.1 onTouchListener、onTouchEvent、onClickListener的优先级?
调用顺序:
View.dispatchTouchEvent()
→ onTouchListener.onTouch()
→ 返回true → 结束,不调用onTouchEvent
→ 返回false ↓
→ onTouchEvent()
→ ACTION_UP中调用 performClick()
→ onClickListener.onClick()
优先级:onTouchListener > onTouchEvent > onClickListener
注意:如果onTouchListener.onTouch()返回true
→ onTouchEvent不调用 → onClick不调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 13.2 ACTION_DOWN返回false会怎样?
如果View的onTouchEvent对ACTION_DOWN返回false:
→ 表示不消费这个事件
→ ViewGroup不会把mFirstTouchTarget设为这个View
→ 后续的MOVE/UP事件不会再发给这个View
→ ViewGroup自己处理后续事件
结论:DOWN事件的返回值决定了整个事件序列的接收者
如果DOWN不消费,就永远收不到MOVE和UP
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 13.3 requestDisallowInterceptTouchEvent的原理?
// 子View调用此方法可以阻止父View拦截后续事件
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// 向上传递
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
// ViewGroup.dispatchTouchEvent中检查:
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev); // 正常判断
} else {
intercepted = false; // 子View禁止拦截
}
// 注意:DOWN事件时会重置FLAG_DISALLOW_INTERCEPT
// 所以disallow对DOWN无效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 13.4 Scroller与惯性滑动的原理
Scroller/OverScroller的工作原理:
Scroller本身不会移动任何View,它只是一个数学计算器:
→ 根据初始速度和摩擦力计算随时间变化的位置
→ View需要配合computeScroll()方法读取位置并应用
惯性滑动(Fling)的完整流程:
1. 手指抬起时通过VelocityTracker获取速度
2. 调用Scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY)
3. Scroller记录起始状态并开始计算
4. 调用invalidate()触发重绘
5. View.draw()中调用computeScroll()
6. computeScroll()中:
if (scroller.computeScrollOffset()) {
int currX = scroller.getCurrX();
int currY = scroller.getCurrY();
scrollTo(currX, currY); // 移动到计算出的位置
postInvalidate(); // 继续下一帧
}
7. 重复5-6直到Scroller计算完成
Scroller的速度衰减模型:
SplineOverScroller使用样条曲线拟合:
匀减速阶段:
distance = v0*t - 0.5*friction*t²
velocity = v0 - friction*t
Fling总距离:
totalDistance = v0² / (2 * friction)
摩擦系数:
默认deceleration = 9.8 * 39.37 * pixelsPerInch * 0.84
(模拟物理世界的摩擦力)
实际计算使用查表法(预计算的样条曲线数据)
→ 避免每帧做三角函数/平方根运算
→ 保证计算效率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 13.5 NestedScrolling嵌套滚动机制
NestedScrolling协议(Android 5.0+):
传统事件分发的局限:
事件一旦被子View消费,父View就无法再处理
→ RecyclerView在ScrollView中无法联动滚动
NestedScrolling解决方案:
子View(NestedScrollingChild)在滚动前后通知父View
父View(NestedScrollingParent)可以消费部分滚动量
协议流程:
1. 子View.startNestedScroll(axes)
→ 沿View树向上查找支持嵌套滚动的父View
→ 父View.onStartNestedScroll() 返回true表示接受
2. 子View滚动前:dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
→ 父View.onNestedPreScroll(target, dx, dy, consumed)
→ 父View可以消费部分滚动量(修改consumed数组)
→ 子View只滚动剩余量
3. 子View滚动后:dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
→ 父View.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
→ 父View处理子View未消费的滚动量
4. Fling也有类似的预消费机制:
→ dispatchNestedPreFling / dispatchNestedFling
典型实现:
CoordinatorLayout → NestedScrollingParent
RecyclerView → NestedScrollingChild
→ AppBarLayout联动收缩就是基于此协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 十四、事件分发案例分析
# 14.1 典型布局案例
讨论以下布局层次的事件分发:
- 最外层:Activity A,包含ViewGroup B
- 中间层:ViewGroup B,包含View C
- 最内层:View C
假设用户触摸到View C上的某个点,ACTION_DOWN事件在该点产生。
# 14.2 默认处理情况
不对dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent()进行重写:
默认事件传递流程:
分发阶段(自上而下):
Activity A.dispatchTouchEvent() → ViewGroup B.dispatchTouchEvent() → View C.dispatchTouchEvent()
处理阶段(自下而上):
View C.onTouchEvent() → ViewGroup B.onTouchEvent() → Activity A.onTouchEvent()
1
2
3
4
5
6
7
2
3
4
5
6
7
注意:虽然ViewGroup B的onInterceptTouchEvent对DOWN事件返回了false,后续事件(MOVE、UP)依然会传递给它的onInterceptTouchEvent()。
# 14.3 View C处理事件
假设View C被设置成可点击或覆写了onTouchEvent返回true:
- DOWN事件传递给C的onTouchEvent,返回true表示处理
- DOWN事件不再往上传递给B和A的onTouchEvent
- 后续事件(MOVE、UP)也将传递给C的onTouchEvent
View C消费事件时的分发流程:
DOWN事件:
A.dispatchTouchEvent → B.dispatchTouchEvent → B.onInterceptTouchEvent(false)
→ C.dispatchTouchEvent → C.onTouchEvent(返回true, 消费)
→ B.dispatchTouchEvent返回true → A.dispatchTouchEvent返回true
→ B记录mFirstTouchTarget = C(后续事件直接发给C)
→ A、B的onTouchEvent不会被调用
后续MOVE/UP事件:
A.dispatchTouchEvent → B.dispatchTouchEvent
→ B.onInterceptTouchEvent(false) // 仍然先检查是否拦截
→ C.dispatchTouchEvent → C.onTouchEvent(消费)
关键原理:
ViewGroup的mFirstTouchTarget不为null
→ 说明有子View消费了DOWN事件
→ 后续事件直接转发给该子View
→ 不再遍历其他子View
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 14.4 ViewGroup B拦截DOWN事件
假设ViewGroup B覆写onInterceptTouchEvent()返回true、onTouchEvent()返回true:
- DOWN事件被B的onInterceptTouchEvent拦截,不再往下传递
- 调用B的onTouchEvent()处理事件
- 后续事件直接传递给B的onTouchEvent()
- onInterceptTouchEvent一旦返回一次true,就再也不会被调用
ViewGroup B拦截时的分发流程:
DOWN事件:
A.dispatchTouchEvent
→ B.dispatchTouchEvent
→ B.onInterceptTouchEvent() 返回true(拦截!)
→ 不再向子View C传递
→ B.onTouchEvent() 返回true(消费)
→ A收到返回值true(事件已被处理)
后续MOVE/UP事件:
A.dispatchTouchEvent
→ B.dispatchTouchEvent
→ 直接调用B.onTouchEvent()(不再调用onInterceptTouchEvent)
→ 因为FLAG_DISALLOW_INTERCEPT未设置且mFirstTouchTarget=null
关键原理:
B拦截了DOWN → mFirstTouchTarget == null
→ 后续事件走super.dispatchTouchEvent(ev)
→ 即View.dispatchTouchEvent → B.onTouchEvent()
→ 整个事件序列都由B处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 14.5 拦截后续事件(MOVE、UP)
假设ViewGroup B没有拦截DOWN事件(View C处理),但拦截了MOVE事件:
- DOWN事件传递到C的onTouchEvent,返回true
- MOVE事件被B的onInterceptTouchEvent拦截,但该MOVE事件不会传递给B
- 系统将该MOVE事件变成一个CANCEL事件传递给C的onTouchEvent
- 后续的MOVE事件才会直接传递给B的onTouchEvent()
- C再也不会收到该事件序列的后续事件
# 14.6 滑动冲突解决方案
外部拦截法:从父容器入手,重写onInterceptTouchEvent方法
- ACTION_DOWN必须返回false,否则后续事件全被父容器处理
- ACTION_MOVE中根据业务判断是否拦截
- ACTION_UP原则上返回false,否则子View的onClick无法触发
内部拦截法:从子View入手,重写dispatchTouchEvent方法
- 在ACTION_DOWN中调用parent.requestDisallowInterceptTouchEvent(true)阻止父容器拦截
- 在ACTION_MOVE中根据业务决定是否调用parent.requestDisallowInterceptTouchEvent(false)允许父容器拦截
选择建议:若两种方法都能实现,优先使用外部拦截法,因为内部拦截法不会减少事件分发层级,且更复杂。
# 十五、总结
事件分发核心知识图谱:
系统层面
├── InputReader → 从硬件读取事件
├── InputDispatcher → 确定目标窗口、ANR检测
├── InputChannel → 跨进程传递事件
└── ViewRootImpl → InputStage管道处理
分发机制
├── Activity.dispatchTouchEvent → 入口
├── ViewGroup.dispatchTouchEvent → 拦截判断、子View遍历
├── ViewGroup.onInterceptTouchEvent → 是否拦截
├── View.dispatchTouchEvent → onTouchListener/onTouchEvent
└── View.onTouchEvent → 点击/长按/滑动检测
关键设计
├── mFirstTouchTarget → DOWN确定接收者,后续直接分发
├── onIntercept → 父View可以拦截子View的事件
├── requestDisallowIntercept → 子View可以阻止父View拦截
└── ACTION_CANCEL → 拦截时通知子View事件被取消
实践应用
├── 滑动冲突 → 外部拦截法/内部拦截法
├── 多点触控 → pointerId + pointerIndex
└── 手势识别 → GestureDetector + VelocityTracker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
上次更新: 2026/06/10, 11:13:41