一句话总结:
事件分发就像 快递接力赛 (父传子,子不接就父自己拿),滑动冲突就是 爷孙抢包裹(上下左右乱滑打架)
一、事件分发机制(快递接力赛)
1. 三个核心角色:
dispatchTouchEvent
(快递站站长) :决定把包裹(事件)分给哪个快递员onInterceptTouchEvent
(站长的小心思) :半路截胡包裹(只在 ViewGroup 存在)onTouchEvent
(快递员) :真正处理包裹的人,如果快递员拒收(返回 false),包裹退回给站长
2. 事件分发流程:
复制
sql
用户按下屏幕 → 事件从 Activity 开始传递 → 自上而下问各级 ViewGroup:"谁要处理?"
↓
如果某个 ViewGroup 的 `onInterceptTouchEvent` 返回 true → 半路截胡,自己处理(调用自己的 onTouchEvent)
↓
如果没人截胡,事件传递到最底层子 View → 子 View 的 onTouchEvent 处理
↓
如果子 View 不处理(返回 false),事件会 **冒泡回传** 给上层处理
通俗比喻:
快递包裹从北京总部(Activity)发到上海分部(DecorView),途经华东区中转站(ViewGroupA)、上海浦东站点(ViewGroupB),最后派送员(TextView)签收。如果派送员不在家,包裹一层层退回,直到有人签收。
二、滑动冲突(爷孙抢包裹)
1. 冲突场景:
-
场景1:方向不同(爷爷要横向滑,孙子要竖向滑)
比如:外层 ViewPager(左右滑)嵌套内层 RecyclerView(上下滑)
-
场景2:方向相同(爷孙都要上下滑)
比如:ScrollView 嵌套 RecyclerView
-
场景3:混合嵌套(既有同向又有异向)
2. 解决方案:
方案1:外部拦截法(爷爷说了算)
- 核心思想 :在父容器的
onInterceptTouchEvent
决定是否截胡 - 代码模板(Kotlin):
kotlin
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
// 按下时不拦截,让子 View 有机会处理
isIntercept = false
}
MotionEvent.ACTION_MOVE -> {
// 根据滑动距离判断是否拦截
val dx = abs(ev.x - lastX)
val dy = abs(ev.y - lastY)
if (dx > dy && dx > touchSlop) {
// 横向滑动距离大,父容器拦截处理
isIntercept = true
}
}
}
return isIntercept
}
方案2:内部拦截法(孙子耍心机)
- 核心思想 :子 View 通过
requestDisallowInterceptTouchEvent
阻止父容器拦截 - 代码模板(Kotlin):
kotlin
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 按下时禁止父容器拦截
parent.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_MOVE -> {
val dx = abs(event.x - lastX)
val dy = abs(event.y - lastY)
if (dy > dx && dy > touchSlop) {
// 如果是竖向滑动,允许父容器拦截
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
return super.onTouchEvent(event)
}
方案3:使用 NestedScrolling 机制(官方和事佬)
- 适用场景:RecyclerView 嵌套 RecyclerView
- 核心原理:子 View 滑动前先问父容器:"我要滑了,您还滑吗?"
- 代码示例:
arduino
// 父容器实现 NestedScrollingParent
// 子 View 使用 NestedScrollView 或 RecyclerView(默认支持嵌套滑动)
三、避坑指南
- 别乱拦截 ACTION_DOWN:如果父容器拦截了 ACTION_DOWN,后续所有事件都会直接交给父容器,子 View 收不到任何事件!
- 滑动方向判断 :建议使用
ViewConfiguration.get(context).scaledTouchSlop
获取最小滑动距离阈值 - 冲突调试 :在
onInterceptTouchEvent
和onTouchEvent
中打印日志,观察事件传递路径
四、总结口诀
事件分发像快递,层层传递到手里
滑动冲突爷孙抢,外部拦截最省力
内部拦截要权限,Nested机制更给力
方向判断加日志,问题迎刃不费力!