[TOC]
![](https://img.kancloud.cn/0c/bb/0cbb39141c0d7e0c72c72f8263f90a49_2190x976.png)
## 事件分发如何通过递归实现(dispatchTouchEvent)
递归分为递过程和归过程。
![](https://img.kancloud.cn/0a/12/0a121156904f49a2905d897d6952183f_624x859.png)
当 UI 事件来临时,它首先被分发到 activity,activity 分发给 window,window 再分发给 view 树。分发过程 **通过 dispatchTouchEvent 函数完成。它的返回值是 Boolean,如果为 true,表示事件被消费;为 false,表示事件未被消费。** dispatchTouchEvent 在不同的分发阶段有不同的作用
对于 View 而言,它没有子 View,**View 自己的 dispatchTouchEvent 实际上会执行到 onTouchEvent** 来进行事件的消费
![](https://img.kancloud.cn/20/56/2056958795824cf83ee0899b0f037b7b_1256x932.png)
对于 ViewGroup 而言,它重写了 dispatchTouchEvent, 先将事件分发给自己的子 View。
如果子 View 可以消费事件, dispatchTouchEvent 直接返回 true;如果子 View 都不消费事件,就调用自己的 onTouchEvent 来进行事件的消费。**(ViewGroup 的 onTouchEvent 实际上就是 super.dispatchTouchEvent)**
![](https://img.kancloud.cn/08/75/08753c8dd12310624204b0010f472814_1248x774.png)
这样,Activity 通过调用 Window#superDispatchTouchEvent 方法将事件分发给 Window,Window 调用 DecorView#dispatchTouchEvent 将事件分发给 decorView。DecorView 是一个 ViewGroup,它又将事件分发给子 ViewGroup 和子 View。当有一个 View 消费掉事件时,他就会向上返回,通过递归链返回到 Activity#dispatchTouchEvent。最终完成事件的分发,消费和返回。
## 如何避免每一个事件的分发都需要递归吗?
ViewGroup 里面用了一个成员变量 mFirstTouchTarget 来保存消费事件的子 View 信息,因为安卓是支持多指操作的,所以这个 mFirstTouchTarget 是一个 TouchTarget 的链表。在View 的 dispatchTouchEvent 可以分为三个阶段:判断是否需要拦截; 分发事件找到消费事件的子 View,更新到 mFirstTouchTarget;根据是否拦截和 mFirstTouchTarget 再次分发事件
所以 Android 为了避免每个事件都递归遍历,定义了一个 **【事件序列】** 的概念:将用户每一次触摸屏幕 --> 移动屏幕-->抬起手指称为一个事件序列。
一个事件序列必然包含 ACTION\_DOWN,ACTION\_MOVE,ACTION\_UP 等多个事件。其中 ACTION\_MOVE 数量不确定,ACTION\_DOWN 和 ACTION\_UP 数量则为 1
当接收到 ACTION\_DOMN 事件时,意味着一次完成事件序列的开始。ViewGroup 会通过递归遍历找到 View 树中真正对事件进行消费的子 View,并将其保存。这之后接收到 ACTION\_MOVE 和 ACTION\_DOWN 事件时,则跳过递归遍历的过程,直接交给之前保存的消费者。当下一次 ACTION\_DOWN 事件来临时重置整个过程
![](https://img.kancloud.cn/6d/1a/6d1a9ed0d8c1d0babfb57d3e76d8dc40_1252x1128.png)
## 如何在事件被分发前拦截
> 嗯,了解的还挺仔细的嘛。那如果有一个 view 可以消费事件,但我想在事件分发给它之前进行拦截,该怎么做?
ViewGroup 提供了一个拦截事件的函数**onInterceptTouchEvent,返回值为 Boolean。** 表示是否拦截事件。如果拦截,则会调用 onTouchEvent 进行事件的进一步处理。
![](https://img.kancloud.cn/7a/92/7a92379febeeedcfad5c372b8a8b2508_1258x576.png)
## 如何处理滑动冲突
当父 View 和子 View 都有机会消费事件,但消费的时机不符合业务的需要(比如需要子 View 消费事件但父 View 先消费了),就会产生滑动冲突问题
解决办法一般分为**内部拦截法**和**外部拦截法**。
**内部拦截法**就是通过重写底层 View 的 dispatchTouchEvent 和 onTouchEvent 方法来决定是否消费事件。
**外部拦截法**就是重写 ViewGroup 的 dispatchTouchEvent 和 onInterceptTouchEvent 方法决定是否把事件分发给 View。
两种方法实际上就是对分发事件的 View 和被分发事件的 View 做不同的逻辑判断。
## 讲讲ACTION\_CANCEL事件?
> 好的,看来平时没少解决滑动冲突的问题哈。刚才你提到了事件序列对吧,你说说 ACTION\_CANCEL 事件是用来干嘛的?
前面我们提到在一个事件序列中,如果有 View 能够消费事件,那么该事件序列所有的后续事件都会交给这个 View 处理。但如果不希望它处理全部的后续事件怎么办?比如手指点击一个 Button 然后滑出它的边界。在这个事件序列中,我只希望 Button 处理它边界内的 move 事件。对于边界外的 move 事件,虽然它们都在一个事件序列中,但理论上不应该再传递给 Button 了。
ACTION\_CANCEL 就是用来解决这个问题的。当 Button 判断 move 事件已经超出 view 的边界时,会将自己的 mPrivateFlags 置为 cancel 状态。等下次事件分发来临,Button 的父 ViewGroup 会检测 Button 的 mPrivateFlags,如果为 cancel 则将之前保存的 mFirstTouchTarget(也就是指向 Button 的引用) 设为 null,并向 Button 发送一个 ACTION\_CANCEL 事件,表示以后不会再接受事件了。
![](https://img.kancloud.cn/96/50/9650037b3eff9754d20f30fea1f00950_1254x918.png)
# 参考资料
[【面试官爸爸】唠唠Android事件分发?](https://juejin.cn/post/6984432840880422949)
[必问的事件分发,你答得上来吗](https://juejin.cn/post/6844903823824158733)
- Android
- 四大组件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介绍
- MessageQueue详细
- 启动流程
- 系统启动流程
- 应用启动流程
- Activity启动流程
- View
- view绘制
- view事件传递
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大数据
- Binder小结
- Android组件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 迁移与修复
- Sqlite内核
- Sqlite优化v2
- sqlite索引
- sqlite之wal
- sqlite之锁机制
- 网络
- 基础
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP进化图
- HTTP小结
- 实践
- 网络优化
- Json
- ProtoBuffer
- 断点续传
- 性能
- 卡顿
- 卡顿监控
- ANR
- ANR监控
- 内存
- 内存问题与优化
- 图片内存优化
- 线下内存监控
- 线上内存监控
- 启动优化
- 死锁监控
- 崩溃监控
- 包体积优化
- UI渲染优化
- UI常规优化
- I/O监控
- 电量监控
- 第三方框架
- 网络框架
- Volley
- Okhttp
- 网络框架n问
- OkHttp原理N问
- 设计模式
- EventBus
- Rxjava
- 图片
- ImageWoker
- Gilde的优化
- APT
- 依赖注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 协程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 运行期Java-hook技术
- 编译期hook
- ASM
- Transform增量编译
- 运行期Native-hook技术
- 热修复
- 插件化
- AAB
- Shadow
- 虚拟机
- 其他
- UI自动化
- JavaParser
- Android Line
- 编译
- 疑难杂症
- Android11滑动异常
- 方案
- 工业化
- 模块化
- 隐私合规
- 动态化
- 项目管理
- 业务启动优化
- 业务架构设计
- 性能优化case
- 性能优化-排查思路
- 性能优化-现有方案
- 登录
- 搜索
- C++
- NDK入门
- 跨平台
- H5
- Flutter
- Flutter 性能优化
- 数据跨平台