Android事件分发机制的详解

发布时间 - 2026-01-11 03:21:28    点击率:

Android事件分发机制

我们只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL。一个手势(gesture)是一个事件列,以一个DOWN事件开始(当用户触摸屏幕时产生),后跟0个或多个MOVE事件(当用户四处移动手指时产生),最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)由于其他原因结束时产生)。当我们说到“手势剩余部分”时指的是手势后续的MOVE事件和最后的UP或CANCEL事件。

在这里我也不考虑多点触摸手势(我们只假设用一个手指)并且忽略多个MOVE事件可以被归为一组这一实际情况。最后,我们假设文中的view都没有注册onTouchListener。

我们将要讨论的视图层次是这样的:最外层是一个ViewGroup A,包含一个或多个子view(children),其中一个子view是ViewGroup B,ViewGroupB中又包含一个或多个子view,其中一个子view是 View C,C不是一个ViewGroup。这里我们忽略同层级view之间可能的交叉叠加。


假设用户首先触摸到的屏幕上的点是C上的某个点,该点被标记为触摸点(touch point),DOWN事件就在该点产生。然后用户移动手指并最后离开屏幕,此过程中手指是否离开C的区域无关紧要,关键是手势(gesture)是从哪里开始的。

默认情况

假设上面的A,B,C都没有覆写默认的事件传播行为,那么下面就是事件传播的过程:

  1. DOWN事件被传到C的onTouchEvent方法中,该方法返回false,表示“我不关心这个手势(gesture)”。
  2. 因此,DOWN事件被传到B的onTouchEvent方法中,该方法同样返回false,表示B也不关心这个手势。
  3. 同样,因为B不关心这个手势,DOWN事件被传到A的onTouchEvent方法中,该方法也返回false。

由于没有view关心这个手势(gesture),它们将不再会从“手势剩余部分”中接收任何事件。

处理事件

现在,让我们假设C实际上是关心这个手势(gesture)的,原因可能是C被设置成可点击的(clickable)或者你覆写了C的onTouchEvent方法。

  • DOWN事件被传递给C的onTouchEvent方法,该方法可以做任何它想做的事情,最后返回true。
  • 因为C说它正在处理这个手势(gesture),则DOWN事件将不再被传递给B和A的onTouchEvent方法。
  • 因为C说它正在处理这个手势(gesture),所以“手势剩余部分”的事件也将传递给C的onTouchEvent方法,此时该方法返回true或false都无关紧要了,但是为保持一致最好还是返回true。

个人理解:从这里可以看出,各个View的onTouchEvent方法对DOWN事件的处理,代表了该View对以此DOWN开始的整个手势(gesture)的处理意愿,返回true代表愿意处理该gesture,返回false代表不愿意处理该gesture。

onInterceptTouchEvent

现在我们将讨论一个新的方法:onInterceptTouchEvent,它只存在于ViewGroup中,普通的View中没有这个方法。在任何一个view的onTouchEvent被调用之前,它的父辈们(ancestors)将先获得拦截这个事件的一次机会,换句话说,它们可以窃取该事件。在刚才的“处理事件”部分中,我们遗漏了这一过程,现在,让我们把它加上:

  • DOWN事件被传给A的onInterceptTouchEvent,该方法返回false,表示它不想拦截。
  • DOWN又被传递给B的onInterceptTouchEvent,它也不想拦截,因此该方法也返回false。
  • 现在,DOWN事件被传递到C的onTouchEvent方法,该方法返回true,因为它想处理以该事件为首的手势(gesture)。
  • 现在,该手势的下一个事件MOVE到来了。这个MOVE事件再一次被传递给A的onInterceptTouchEvent方法,该方法再一次返回false,B也同样如此。
  • 然后,MOVE事件被传递给C的onTouchEvent,就像在前一部分中一样。
  • “手势剩余部分”中其他事件的处理过程和上面一样,假如A和B的onInterceptTouchEvent方法继续返回false的话。       这里有两点需要注意:
  • 虽然ViewGroup A和B的onInterceptTouchEvent方法对DOWN事件返回了false,后续的事件依然会传递给它们的onInterceptTouchEvent方法,这一点与onTouchEvent的行为是不一样的。
  • 假如DOWN事件传给C的onTouchEvent方法时,它返回了false,DOWN事件会继续向上传递给B和A的onTouchEvent,即使它们在onInterceptTouchEvent方法中说它们不想拦截这个DOWN事件,但没办法,没有子View愿意处理该事件。

个人理解:感谢@编程世界的孩子 的提醒,由此可见,DOWN事件的处理实际上经历了一下一上两个过程,下是指A->B的onInterceptTouchEvent,上是指C->B->A的onTouchEvent,当然,任意一步的方法中返回true,都能阻止它继续传播。

拦截事件

现在,让我们更进一步,假设B没有拦截DOWN事件,但它拦截了接下来的MOVE事件。原因可能是B是一个scrolling view。当用户仅仅在它的区域内点击(tap)时,被点击到的元素应当能处理该点击事件。但是当用户手指移动了一定的距离后,就不能再视该手势(gesture)为点击了——很明显,用户是想scroll。这就是为什么B要接管该手势(gesture)。
下面是事件被处理的顺序:

  • DOWN事件被依次传到A和B的onInterceptTouchEvent方法中,它们都返回的false,因为它们目前还不想拦截。
  • DOWN事件传递到C的onTouchEvent方法,返回了true。
  • 在后续到来MOVE事件时,A的onInterceptTouchEvent方法仍然返回false。
  • B的onInterceptTouchEvent方法收到了该MOVE事件,此时B注意到用户手指移动距离已经超过了一定的threshold(或者称为slop)。因此,B的onInterceptTouchEvent方法决定返回true,从而接管该手势(gesture)后续的处理。
  • 然后,这个MOVE事件将会被系统变成一个CANCEL事件,这个CANCEL事件将会传递给C的onTouchEvent方法。
  • 现在,又来了一个MOVE事件,它被传递给A的onInterceptTouchEvent方法,A还是不关心该事件,因此onInterceptTouchEvent方法继续返回false。
  • 此时,该MOVE事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。事实上,该MOVE以及“手势剩余部分”都将传递给B的onTouchEvent方法(除非A决定拦截“手势剩余部分”)。
  • C再也不会收到该手势(gesture)产生的任何事件了。

下面的一些小事情可能会令你感到吃惊:

  • 如果一个ViewGroup拦截了最初的DOWN事件,该事件仍然会传递到该ViewGroup的onTouchEvent方法中。
  • 另一方面,如果ViewGroup拦截了一个半路的事件(比如,MOVE),这个事件将会被系统变成一个CANCEL事件,并传递给之前处理该手势(gesture)的子View,而且不会再传递(无论是被拦截的MOVE还是系统生成的CANCEL)给ViewGroup的onTouchEvent方法。只有再到来的事件才会传递到ViewGroup的onTouchEvent方法中。

从此开始,你可以更进一步。比如对mouthful-method (实在不知道该怎么翻译啦!)requestDisallowInterceptTouchEvent,C可以用该方法阻止B窃取事件。如果你想更加疯狂一点,你可以在你自己的ViewGroup中直接覆写dispatchTouchEvent方法,并对传递进来的事件做任何你想做的处理。但这样的话你可能会破坏一些约定,所以应当小心。

如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!


# Android事件分发机制  # Android事件分发机制的实例详解  # Android事件  # Android之事件分发机制与冲突详解  # 聊聊Android中的事件分发机制  # Android事件分发机制全面解析  # 解析Android点击事件分发机制  # android事件分发机制的实现原理  # Android View的事件分发机制  # 谈谈对Android View事件分发机制的理解  # Android事件分发机制示例分析  # 是一个  # 让我们  # 将会  # 这一  # 也不  # 你可以  # 多个  # 是指  # 无关紧要  # 其中一个  # 会再  # 将不  # 想做  # 说它  # 不关心  # 再也不会  # 自己的  # 或多  # 多点  # 来了 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel如何构建RESTful API_Laravel标准化API接口开发指南  如何用已有域名快速搭建网站?  大连网站制作费用,大连新青年网站,五年四班里的视频怎样下载啊?  Laravel怎么实现搜索高亮功能_Laravel结合Scout与Algolia全文检索【实战】  专业型网站制作公司有哪些,我设计专业的,谁给推荐几个设计师兼职类的网站?  Laravel如何实现事件和监听器?(Event & Listener实战)  Laravel如何处理JSON字段_Eloquent原生JSON字段类型操作教程  如何在云服务器上快速搭建个人网站?  Firefox Developer Edition开发者版本入口  如何快速搭建个人网站并优化SEO?  JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)  怎么用AI帮你设计一套个性化的手机App图标?  安克发布新款氮化镓充电宝:体积缩小 30%,支持 200W 输出  JS中页面与页面之间超链接跳转中文乱码问题的解决办法  公司门户网站制作流程,华为官网怎么做?  Laravel Docker环境搭建教程_Laravel Sail使用指南  夸克浏览器网页跳转延迟怎么办 夸克浏览器跳转优化  WordPress 子目录安装中正确处理脚本路径的完整指南  python中快速进行多个字符替换的方法小结  Laravel如何使用Collections进行数据处理?(实用方法示例)  厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?  Gemini怎么用新功能实时问答_Gemini实时问答使用【步骤】  Laravel如何实现全文搜索功能?(Scout和Algolia示例)  html5audio标签播放结束怎么触发事件_onended回调方法【教程】  如何快速建站并高效导出源代码?  郑州企业网站制作公司,郑州招聘网站有哪些?  如何用好域名打造高点击率的自主建站?  如何在景安服务器上快速搭建个人网站?  Laravel如何生成和使用数据填充?(Seeder和Factory示例)  如何撰写建站申请书?关键要点有哪些?  Claude怎样写约束型提示词_Claude约束提示词写法【教程】  清除minerd进程的简单方法  如何挑选优质建站一级代理提升网站排名?  如何在阿里云ECS服务器部署织梦CMS网站?  php嵌入式断网后怎么恢复_php检测网络重连并恢复硬件控制【操作】  Windows10电脑怎么设置虚拟光驱_Win10右键装载ISO镜像文件  如何在万网自助建站平台快速创建网站?  Laravel如何记录自定义日志?(Log频道配置)  Laravel怎么在Blade中安全地输出原始HTML内容  Laravel Eloquent模型如何创建_Laravel ORM基础之Model创建与使用教程  HTML 中动态设置元素 name 属性的正确语法详解  Android使用GridView实现日历的简单功能  iOS验证手机号的正则表达式  浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】  Laravel如何创建自定义Facades?(详细步骤)  Laravel的Blade指令怎么自定义_创建你自己的Laravel Blade Directives  Laravel如何实现用户注册和登录?(Auth脚手架指南)  Laravel Session怎么存储_Laravel Session驱动配置详解  Gemini手机端怎么发图片_Gemini手机端发图方法【步骤】  如何彻底卸载建站之星软件?