一句话说透Android里面的事件分发机制、滑动冲突

一句话总结:

事件分发就像 快递接力赛 (父传子,子不接就父自己拿),滑动冲突就是 爷孙抢包裹(上下左右乱滑打架)


一、事件分发机制(快递接力赛)

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(默认支持嵌套滑动)  

三、避坑指南

  1. 别乱拦截 ACTION_DOWN:如果父容器拦截了 ACTION_DOWN,后续所有事件都会直接交给父容器,子 View 收不到任何事件!
  2. 滑动方向判断 :建议使用 ViewConfiguration.get(context).scaledTouchSlop 获取最小滑动距离阈值
  3. 冲突调试 :在 onInterceptTouchEventonTouchEvent 中打印日志,观察事件传递路径

四、总结口诀

事件分发像快递,层层传递到手里

滑动冲突爷孙抢,外部拦截最省力

内部拦截要权限,Nested机制更给力

方向判断加日志,问题迎刃不费力!

相关推荐
zzb15802 小时前
Fragment 生命周期深度图解:从 onAttach 到 onDetach 完整流程(面试必备)
android·java·面试·安卓
众少成多积小致巨2 小时前
Android 源码查看笔记
android·源码
angerdream2 小时前
Android手把手编写儿童手机远程监控App之前台服务
android
图图玩ai3 小时前
SSH 命令管理工具怎么选?从命令收藏到批量执行一次讲清
linux·nginx·docker·ai·程序员·ssh·可视化·gmssh·批量命令执行
敲代码的瓦龙4 小时前
Android?Activity!!!
android
重生之我在安卓搞音频5 小时前
二、Android 音频框架
android·音视频
SamDeepThinking5 小时前
程序员懂业务,到底要懂到什么程度
后端·程序员·团队管理
studyForMokey6 小时前
【Android面试】Java专题 todo
android·java·面试
代码改善世界6 小时前
【MATLAB初阶】矩阵操作(二):矩阵的运算
android·matlab·矩阵
九皇叔叔6 小时前
MySQL实操指南:复制表及数据复制全解析
android·数据库·mysql