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

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

相关推荐
前行的小黑炭4 小时前
Android 协程的使用:结合一个环境噪音检查功能的例子来玩玩
android·java·kotlin
阿华的代码王国4 小时前
【Android】内外部存储的读写
android·内外存储的读写
堆栈future6 小时前
我的个人网站上线了,AI再一次让我站起来了
程序员·llm·aigc
大模型教程7 小时前
AI Agent 发展趋势与架构演进
程序员·llm·agent
inmK18 小时前
蓝奏云官方版不好用?蓝云最后一版实测:轻量化 + 不限速(避更新坑) 蓝云、蓝奏云第三方安卓版、蓝云最后一版、蓝奏云无广告管理工具、安卓网盘轻量化 APP
android·工具·网盘工具
giaoho8 小时前
Android 热点开发的相关api总结
android
AI大模型9 小时前
无所不能的Embedding(01) - 词向量三巨头之Word2vec模型详解&代码实现
程序员·llm·agent
程序员鱼皮9 小时前
扒了下 Cursor 的提示词,被狠狠惊艳到了!
计算机·ai·程序员·大模型·互联网·编程
咖啡の猫10 小时前
Android开发-常用布局
android·gitee