编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

    • 库的解读

    • 专栏博客

      • 系统启动Zygote
      • Binder通信原理
      • Handler消息机制
      • Activity启动原理
      • 四大组件原理分析
      • AMS与组件管理
      • View绑制与渲染
      • 事件分发机制
        • 一、引言:触摸屏幕后发生了什么
        • 二、输入事件从硬件到应用的传递链路
          • 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 滑动冲突解决方案
        • 十五、总结
      • Surface渲染原理
      • 自定义View设计
      • WMS窗口管理
      • PMS与APK安装
      • 虚拟机与类加载
      • 内存管理与GC
      • 线程与并发编程
      • 性能优化与监控
      • 序列化与数据存储
      • 组件化与路由设计
      • 插件化与热修复
      • NDK开发实践
      • WebView核心设计
      • ADB常见使用操作
    • 智能硬件

  • iOS开发和进阶

  • Web开发和进阶

  • Linux应用开发

  • Apps
  • Android提升进阶
  • 专栏博客
杨充
2026-04-14
目录

事件分发机制

# 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.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

# 三、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

# 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

# 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

# 四、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

# 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

# 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

# 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

# 五、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

# 六、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

# 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

# 七、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

# 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

# 八、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

# 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

# 九、事件分发的核心伪代码

# 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

# 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

# 十、滑动冲突的解决方案

# 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

# 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

# 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

# 十一、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

# 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

# 十二、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

# 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

# 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

# 十三、面试高频问题与深度分析

# 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

# 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

# 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

# 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

# 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

# 十四、事件分发案例分析

# 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

注意:虽然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

# 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

# 14.5 拦截后续事件(MOVE、UP)

假设ViewGroup B没有拦截DOWN事件(View C处理),但拦截了MOVE事件:

  1. DOWN事件传递到C的onTouchEvent,返回true
  2. MOVE事件被B的onInterceptTouchEvent拦截,但该MOVE事件不会传递给B
  3. 系统将该MOVE事件变成一个CANCEL事件传递给C的onTouchEvent
  4. 后续的MOVE事件才会直接传递给B的onTouchEvent()
  5. 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
上次更新: 2026/06/10, 11:13:41
View绑制与渲染
Surface渲染原理

← View绑制与渲染 Surface渲染原理→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式