当前位置: 首页 > article >正文

漫画Android:事件分发的过程是怎样的?

1
2
3
4
5
当用户触摸屏幕时,硬件层会捕获触摸信号,并将其转化为内核事件。
Android系统会通过InputManagerService和WindowManagerService等服务将这些事件包装成MotionEvent对象,并将其传递给Activity的dispatchTouchEvent()方法中,Activity会先将事件分发给Window处理,Window调用superDispatchTouchEvent()方法,将事件交给 PhoneWindow处理,然后 PhoneWindow将事件传递给当前窗口的根视图(通常是DecorView,一个FrameLayout)。
DecorView是PhoneWindow的顶级视图,它是所有应用UI的容器。从这里开始,事件分发就进入了应用程序的视图层级

即,事件收集之后最先传递给Activity,随后依次向下传递:

Activity ——> Window ——> …… ——> DecorView ——> ViewGroup ——> …… ——> View

事件分发的流程

整个事件分发过程主要围绕MotionEvent对象展开,并且涉及三个关键方法:dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent()

  1. 事件产生:用户触摸屏幕,系统生成MotionEvent
  2. 根视图分发MotionEvent首先传递给当前Activity,然后依次传递到当前窗口的根视图(DecorView)。
  3. dispatchTouchEvent()DecorView调用自己的dispatchTouchEvent(),决定是否将事件向下分发。
  4. onInterceptTouchEvent() (ViewGroup)
    • 如果DecorViewViewGroup(通常是),它会调用onInterceptTouchEvent()来判断是否拦截事件。
    • 如果返回true(拦截),事件将直接传递给DecorViewonTouchEvent()
    • 如果返回false(不拦截),事件将继续向下分发给子View。
  5. 向下分发DecorView遍历其子View,找到触摸区域内的子View,并调用该子View的dispatchTouchEvent()。这个过程会递归地重复步骤4和5,直到事件到达最底层的View或者被某个ViewGroup拦截。
  6. onTouchEvent() (View/ViewGroup)
    • 如果事件被某个View或ViewGroup拦截(onInterceptTouchEvent()返回true),或者事件一直分发到了最底层的View且没有被任何父ViewGroup拦截,那么该View/ViewGroup的onTouchEvent()方法将被调用。
    • 如果onTouchEvent()返回true,表示该View/ViewGroup消费了事件,事件传递流程结束。
    • 如果onTouchEvent()返回false,表示该View/ViewGroup不处理事件,事件会回溯到其父ViewGroup的onTouchEvent()方法(如果父ViewGroup之前没有拦截该事件)。
  7. 事件未被处理:如果事件最终没有被任何View或ViewGroup处理(即所有onTouchEvent()都返回false),那么该事件会沿着View树向上回溯,最终可能会被Activity的onTouchEvent()方法处理。如果连Activity的onTouchEvent()也返回false,则该事件将被丢弃。

Android事件分发是一个自上而下分发、自下而上处理(如果未被处理)的过程。

6
7
8
9

ViewGroup事件分发流程

依葫芦画瓢!ViewGroup的事件分发流程围绕刚刚提到的三个核心方法展开:dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent()

  1. 事件到达:当一个MotionEvent到达ViewGroup时,首先调用其dispatchTouchEvent()方法。
  2. 是否拦截?:在dispatchTouchEvent()内部,首先调用onInterceptTouchEvent()来判断ViewGroup是否要拦截这个事件。
    • 如果onInterceptTouchEvent()返回true (拦截)
      • 事件不再向下分发给子View
      • ViewGroup自身的onTouchEvent()方法会被调用,以处理该事件。
      • 如果onTouchEvent()返回true,表示事件被ViewGroup消费。
      • 如果onTouchEvent()返回false,表示事件未被ViewGroup消费,事件会回溯到其父ViewGrouponTouchEvent()(如果父ViewGroup之前没有拦截该事件)。
    • 如果onInterceptTouchEvent()返回false (不拦截)
      • ViewGroup会遍历其子View
      • 它会判断触摸点是否在某个子View的范围内。
      • 如果找到合适的子View,就调用该子ViewdispatchTouchEvent()方法,将事件继续向下传递。
      • 如果子ViewdispatchTouchEvent()返回true (子View消费了事件):整个事件分发流程结束。
      • 如果子ViewdispatchTouchEvent()返回false (子View未消费事件)ViewGroup会继续尝试将事件分发给下一个合适的子View
      • 如果所有子View都遍历完了,但都没有消费事件,或者根本没有子View:那么事件会回传给当前ViewGroup,调用其onTouchEvent()方法来处理。
        • 如果ViewGrouponTouchEvent()返回true,事件被ViewGroup消费。
        • 如果ViewGrouponTouchEvent()返回false,事件未被ViewGroup消费,会继续回溯到父ViewGroup

关键点:

  • 优先拦截onInterceptTouchEvent()优先于子ViewdispatchTouchEvent()被调用,它有“一票否决权”。
  • 消费即止:一旦某个ViewViewGrouponTouchEvent()返回true,表示它消费了该事件序列,后续事件将直接传递给它,不再进行分发。
  • 回溯机制:如果一个事件沿着分发路径一直没有被消费(所有onTouchEvent()都返回false),它会沿着调用链向上回溯,最终可能由ActivityonTouchEvent()处理。

内部逻辑(伪代码表示):

public boolean dispatchTouchEvent(MotionEvent event) {boolean handled = false; // 标记事件是否被处理// 1. 调用 onInterceptTouchEvent() 判断是否拦截if (onInterceptTouchEvent(event)) {// 2. 如果 onInterceptTouchEvent() 返回 true (拦截)//    则事件不再向下分发给子View,而是直接交给当前ViewGroup的 onTouchEvent() 处理handled = onTouchEvent(event);} else {// 3. 如果 onInterceptTouchEvent() 返回 false (不拦截)//    则遍历子View,尝试将事件分发给它们// 查找触摸点所在的子View,并调用其 dispatchTouchEvent()// 伪代码:// for (int i = 0; i < getChildCount(); i++) {//     View child = getChildAt(i);//     if (child.isTouchedInBounds(event)) { // 检查触摸点是否在子View范围内//         if (child.dispatchTouchEvent(event)) { // 递归调用子View的dispatchTouchEvent()//             handled = true; // 子View消费了事件//             break; // 停止遍历,事件已被处理//         }//     }// }// 4. 如果所有子View都没有消费事件 (handled 仍为 false)//    或者根本没有子View//    则将事件交给当前ViewGroup的 onTouchEvent() 处理if (!handled) {handled = onTouchEvent(event);}}return handled; // 返回事件是否被处理
}

10

http://www.lryc.cn/news/2393456.html

相关文章:

  • 2022 RoboCom 世界机器人开发者大赛-本科组(省赛)解题报告 | 珂学家
  • 什么是MCP技术,跟http技术有什么区别
  • 如何用ChatGPT提升学术长文质量
  • BKP(备份寄存器)和 RTC(实时时钟)
  • springboot配置cors拦截器与cors解释
  • 【EdgeYOLO】《EdgeYOLO: An Edge-Real-Time Object Detector》
  • Python打卡 DAY 38
  • 调试技巧总结
  • ubuntu安装blender并配置应用程序图标
  • 基于LBS的上门代厨APP开发全流程解析
  • Redisson学习专栏(三):高级特性与实战(Spring/Spring Boot 集成,响应式编程,分布式服务,性能优化)
  • 华为欧拉系统中部署FTP服务与Filestash应用:实现高效文件管理和共享
  • 基于Docker和YARN的大数据环境部署实践最新版
  • 【大模型】Bert
  • 《Go小技巧易错点100例》第三十四篇
  • vue3+element-plus el-date-picker日期、年份筛选设置本周、本月、近3年等快捷筛选
  • Vue 技术文档
  • 3 分钟学会使用 Puppeteer 将 HTML 转 PDF
  • 速通《Sklearn 与 TensorFlow 机器学习实用指南》
  • Ubuntu 下搭建ESP32 ESP-IDF开发环境,并在windows下用VSCode通过SSH登录Ubuntu开发ESP32应用
  • [FreeRTOS- 野火] - - - 临界段
  • 【洛谷P9303题解】AC代码- [CCC 2023 J5] CCC Word Hunt
  • NodeMediaEdge接入NodeMediaServer
  • 【Java基础-环境搭建-创建项目】IntelliJ IDEA创建Java项目的详细步骤
  • WebSocket指数避让与重连机制
  • DrissionPage WebPage模式:动态交互与高效爬取的完美平衡术
  • adb查看、设置cpu相关信息
  • PHP7+MySQL5.6 查立得源码授权系统DNS验证版
  • 68元开发板,开启智能硬件新篇章——明远智睿SSD2351深度解析
  • 【QQ音乐】sign签名| data参数加密 | AES-GCM加密 | webpack (下)