一句话总结:
Android事件分发的对象就像击鼓传花的游戏:
- 花是触摸事件(手指动作)
- 传花人是Activity、ViewGroup、View组成的传递链
- 决定权在
dispatchTouchEvent()方法手里
一、事件分发核心对象
1. 事件本体:MotionEvent(传递的花)
event.action // 动作类型(种花姿势):
ACTION_DOWN → 手指按下(开始传花)
ACTION_MOVE → 手指滑动(花在传递中)
ACTION_UP → 手指抬起(花传完了)
2. 传递链成员(传花人)
| 对象 | 角色 | 比喻 |
|---|---|---|
| Activity | 游戏发起者 | 班级老师(第一个拿到花) |
| Window | 传递中介 | 班长(帮老师分发) |
| ViewGroup | 小组长 | 课代表(决定给哪个组员) |
| View | 最终处理者 | 普通同学(真正接花的人) |
二、完整传递流程(击鼓传花全过程)
阶段1:自上而下传递(老师→班长→课代表→同学)
Activity → Window → DecorView → ViewGroup → ... → View
关键方法:
public boolean dispatchTouchEvent(MotionEvent ev)
阶段2:拦截判断(课代表的特权)
只有ViewGroup有这个特殊能力:
public boolean onInterceptTouchEvent(MotionEvent ev)
举个栗子:
当手指在ScrollView里滑动时:
- 纵向滑动 → ScrollView拦截事件(自己处理滚动)
- 横向滑动 → 不拦截(传给子View处理)
阶段3:最终处理(同学接到花)
public boolean onTouchEvent(MotionEvent event)
处理优先级:
onTouchListener → onTouchEvent → onClickListener
三、典型场景分析
场景1:按钮点击
Activity → DecorView → FrameLayout → Button
↓ ↓
不拦截 onTouch返回true(成功接花)
场景2:列表滑动
Activity → DecorView → RecyclerView → ItemView
↓ ↓
不拦截 onIntercept返回true(拦截给子View的事件)
场景3:事件无人处理
Activity → DecorView → ViewGroup → View
↓
onTouch返回false(花掉地上了)
↓
事件反向传递:View → ViewGroup → Activity(最后没人接就销毁花)
四、常见问题解答
Q1:为什么我的点击事件没反应?
可能原因:
- 父ViewGroup拦截了事件(课代表扣下了花)
- 子View的
onTouchEvent返回了false(同学拒绝接花) - 视图被遮挡(花传不过去)
Q2:onTouch和onClick有什么区别?
对比表:
| 特性 | onTouch | onClick |
|---|---|---|
| 触发时机 | 任何触摸动作 | 完整点击(DOWN→UP) |
| 返回值 | 需要return true才能接收后续事件 | 无返回值 |
| 信息量 | 能获取详细坐标数据 | 只有点击事件 |
Q3:如何自定义事件处理?
技巧三连:
- 重写
onInterceptTouchEvent控制是否拦截 - 在
onTouchEvent处理复杂手势 - 使用
GestureDetector识别双击/长按等操作
五、避坑指南
坑1:事件冲突(多个View抢花)
案例:ViewPager嵌套ScrollView
// 解决思路:根据滑动方向决定谁处理
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
when(判断滑动方向) {
水平 → 自己处理(ViewPager翻页)
垂直 → 不拦截(给ScrollView滚动)
}
}
坑2:内存泄漏
错误示范:
// 在Activity中直接创建Handler处理事件
val handler = Handler() // 隐式持有Activity引用
正确姿势:
// 使用弱引用或View自带的Handler
private class SafeHandler(activity: WeakReference<Activity>) : Handler()
坑3:事件延迟
症状:滑动列表时有卡顿感
优化方案:
// 在自定义View中减少onDraw的复杂度
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.action) {
ACTION_MOVE → 只更新必要数据,调用postInvalidate()
}
}
终极口诀:
事件分发如传花,Activity是发起家
ViewGroup可拦截,View是最终处理侠
onTouch优先执行,onClick最后到达
冲突解决靠判断,方向距离两手抓!