一句话说透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机制更给力

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

相关推荐
私人珍藏库1 小时前
[Android] APK提取器(1.3.7)版本
android
m0_748232641 小时前
mysql的主从配置
android·mysql·adb
秋长愁2 小时前
Android监听应用前台的实现方案解析
android
胖虎12 小时前
2025 新版Android Studio创建Java语言项目
android·java·android studio·创建java项目
JabamiLight4 小时前
Lineageos 22.1(Android 15)Launcer简单调整初始化配置
android·android 15·lineageos 22.1·launcer
敲代码的鱼哇5 小时前
设备唯一ID获取,支持安卓/iOS/鸿蒙Next(uni-device-id)UTS插件
android·ios·uniapp·harmonyos
太空漫步117 小时前
android滑动看新闻
android
KdanMin7 小时前
“让App玩捉迷藏:Android教育平板的‘隐身术’开发实录”
android
韩曙亮7 小时前
【Android】Android 悬浮窗开发 ( 动态权限请求 | 前台服务和通知 | 悬浮窗创建 )
android·service·悬浮窗·服务·前台服务·动态权限·通知通道
双鱼大猫7 小时前
一句话说透Android里面的BufferQueue机制
android·程序员