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

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

相关推荐
我是好小孩36 分钟前
Android-侧边导航栏的使用
android·gitee
吗喽对你问好37 分钟前
安卓基础布局核心知识点整理
android·gitee
安卓开发者39 分钟前
Android Material Components 全面解析:打造现代化 Material Design 应用
android
教程分享大师1 小时前
带root权限_中国移动创维DT541_S905L3融合机器改机顶盒刷机教程 当贝纯净版安卓9.0系统线刷包 刷机包
android
wuk9981 小时前
Android:UI:Drawable:View/ImageView与Drawable
android·ui
剪刀石头布啊2 小时前
数据口径
前端·后端·程序员
剪刀石头布啊2 小时前
http状态码大全
前端·后端·程序员
whysqwhw2 小时前
Kotlin 中作用域函数 let、with、run、also、apply 的核心使用指南
android
旋风菠萝3 小时前
设计模式---单例
android·java·开发语言
whysqwhw4 小时前
Android Jetpack 中 ViewModel 的全面解析
android