一句话说透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. 冲突调试 :在 onInterceptTouchEvent 和 onTouchEvent 中打印日志,观察事件传递路径

四、总结口诀

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

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

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

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

相关推荐
小贺儿开发10 小时前
《流光绘忆》Unity 沉浸式光影互动
科技·unity·ai·程序员·工具·互动·光影
拥小抱15 小时前
Compose 知识点
android
文章永久免费只为良心15 小时前
Web应用安全与防护_第三章_日志文件包含_从一道 CTFshow 题目讲透「日志投毒型 LFI → RCE」:原理、时序陷阱与全变种攻防手册
android·网络安全·渗透测试·漏洞危害·漏洞描述·日志投毒·红队指南
AIsoft_868915 小时前
学术讲座录音如何整理?录音APP可行吗
android·iphone
深蓝AI16 小时前
一个版本号失守:Claude Code 2.1.313 扩展后门事件,开发者机器如何变成别人的 SSH 入口
安全·程序员
Patrick在香港18 小时前
一个斜杠,把毫秒变成十几秒
android·数据库·python·性能优化·正则表达式·回溯·redos
海雅达手持终端PDA19 小时前
工业手持终端的“去手机化”演进:看海雅达如何以“同轴扫摄”终结工程妥协,重塑工业PDA新范式
android·物联网·智能手机·硬件工程·能源·制造·iot
深蓝AI19 小时前
Ghostty 1.4 七个月打磨:ASCII 解析快 5.7 倍、终端能放动画,SSH 乱码终结
程序员·开源
Solara20 小时前
绿灯连亮 6 天、今天我真写了 4 KB:这盏灯从没看过我写的那一层
人工智能·程序员·ai编程
booboosui20 小时前
删掉一个住在本机的 agent:8 处要清,顺序不能反
android