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

安卓入门三十五 事件分发机制详解

本篇的核心问题是:正确理解在实际场景中事件分发机制的作用

常见事件

既然是事件分发,总要有事件才能分发吧,所以我们先了解一下常见的几种事件。

根据面向对象思想,事件被封装成 MotionEvent 对象,由于本篇重点不在于此,所以只会涉及到几个与手指触摸相关的常见事件:

对于单指触控来说,一次简单的交互流程是这样的:

手指落下(ACTION_DOWN) -> 移动(ACTION_MOVE) -> 离开(ACTION_UP)

  • 本次事例中 ACTION_MOVE 有多次触发。
  • 如果仅仅是单击(手指按下再抬起),不会触发 ACTION_MOVE。

事件分发、拦截与消费

关于这一部分内容,上一篇文章 事件分发机制原理 已经将流程整理的比较清楚了,本文会深入细节来研究这些内容。之所以分开讲,是为了防止大家被细节所迷惑而忽略了整体逻辑。

√ 表示有该方法。

X 表示没有该方法。

View 相关

dispatchTouchEvent 是事件分发机制中的核心,所有的事件调度都归它管。不过我细看表格, ViewGroup 有 dispatchTouchEvent 也就算了,毕竟人家有一堆 ChildView 需要管理,但为啥 View 也有?这就引出了我们的第一个疑问。

Q: 为什么 View 会有 dispatchTouchEvent ?

A: 我们知道 View 可以注册很多事件监听器,例如:单击事件(onClick)、长按事件(onLongClick)、触摸事件(onTouch),并且View自身也有 onTouchEvent 方法,那么问题来了,这么多与事件相关的方法应该由谁管理?毋庸置疑就是 dispatchTouchEvent,所以 View 也会有事件分发。

相信看到这里很多小伙伴会产生第二个疑问,View 有这么多事件监听器,到底哪个先执行?

Q: 与 View 事件相关的各个方法调用顺序是怎样的?

A: 如果不去看源码,想一下让自己设计会怎样?

  • 单击事件(onClickListener) 需要两个两个事件(ACTION_DOWN 和 ACTION_UP )才能触发,如果先分配给onClick判断,等它判断完,用户手指已经离开屏幕,黄花菜都凉了,定然造成 View 无法响应其他事件,应该最后调用。(最后)
  • 长按事件(onLongClickListener) 同理,也是需要长时间等待才能出结果,肯定不能排到前面,但因为不需要ACTION_UP,应该排在 onClick 前面。(onLongClickListener > onClickListener)
  • 触摸事件(onTouchListener) 如果用户注册了触摸事件,说明用户要自己处理触摸事件了,这个应该排在最前面。(最前)
  • View自身处理(onTouchEvent) 提供了一种默认的处理方式,如果用户已经处理好了,也就不需要了,所以应该排在 onTouchListener 后面。(onTouchListener > onTouchEvent)

所以事件的调度顺序应该是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener


1. 不论 View 自身是否注册点击事件,只要 View 是可点击的就会消费事件。
2. 事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关。

关于 View 的事件分发先说这么多,下面我们来看一下 ViewGroup 的事件分发。

ViewGroup 相关

ViewGroup(通常是各种Layout) 的事件分发相对来说就要麻烦一些,因为 ViewGroup 不仅要考虑自身,还要考虑各种 ChildView,一旦处理不好就容易引起各种事件冲突,正所谓养儿方知父母难啊。

VIewGroup 的事件分发流程又是如何的呢?

上一篇文章 事件分发机制原理 中我们了解到事件是通过ViewGroup一层一层传递的,最终传递给 View,ViewGroup 要比它的 ChildView 先拿到事件,并且有权决定是否告诉要告诉 ChildView。在默认的情况下 ViewGroup 事件分发流程是这样的。

  • 1.判断自身是否需要(询问 onInterceptTouchEvent 是否拦截),如果需要,调用自己的 onTouchEvent。
  • 2.自身不需要或者不确定,则询问 ChildView ,一般来说是调用手指触摸位置的 ChildView。
  • 3.如果子 ChildView 不需要则调用自身的 onTouchEvent。

当手指点击有重叠区域时,分如下几种情况:

  • 只有 View1 可点击时,事件将会分配给 View1,即使被 View2 遮挡,这一部分仍是 View1 的可点击区域。
  • 只有 View2 可点击时,事件将会分配给 View2。
  • View1 和 View2 均可点击时,事件会分配给后加载的 View2,View2 将事件消费掉,View1接收不到事件。

注意:

  • 上面说的是可点击,可点击包括很多种情况,只要你给View注册了 onClickListener、onLongClickListener、OnContextClickListener 其中的任何一个监听器或者设置了 android:clickable=”true” 就代表这个 View 是可点击的。
    另外,某些 View 默认就是可点击的,例如,Button,CheckBox 等。
  • 给 View 注册 OnTouchListener 不会影响 View 的可点击状态。即使给 View 注册 OnTouchListener ,只要不返回 true 就不会消费事件。

3. ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),哪个会执行?

事件优先给 ChildView,会被 ChildView消费掉,ViewGroup 不会响应。

4. 所有事件都应该被同一 View 消费

在上面的例子中我们分析后可以了解到,同一次点击事件只能被一个 View 消费,这是为什呢?主要是为了防止事件响应混乱,如果再一次完整的事件中分别将不同的事件分配给了不同的 View 容易造成事件响应混乱。

View 中 onClick 事件需要同时接收到 ACTION_DOWN 和 ACTION_UP 才能触发,如果分配给了不同的 View,那么 onClick 将无法被正确触发。

安卓为了保证所有的事件都是被一个 View 消费的,对第一次的事件( ACTION_DOWN )进行了特殊判断,View 只有消费了 ACTION_DOWN 事件,才能接收到后续的事件(可点击控件会默认消费所有事件),并且会将后续所有事件传递过来,不会再传递给其他 View,除非上层 View 进行了拦截。
如果上层 View 拦截了当前正在处理的事件,会收到一个 ACTION_CANCEL,表示当前事件已经结束,后续事件不会再传递过来。

核心要点

  • 事件分发原理: 责任链模式,事件层层传递,直到被消费。
  • View 的 dispatchTouchEvent 主要用于调度自身的监听器和 onTouchEvent。
  • View的事件的调度顺序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。
  • 不论 View 自身是否注册点击事件,只要 View 是可点击的就会消费事件。
  • 事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关。
  • ViewGroup 中可能有多个 ChildView 时,将事件分配给包含点击位置的 ChildView。
  • ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),由 ChildView 消费。
  • 一次触摸流程中产生事件应被同一 View 消费,全部接收或者全部拒绝。
  • 只要接受 ACTION_DOWN 就意味着接受所有的事件,拒绝 ACTION_DOWN 则不会收到后续内容。
  • 如果当前正在处理的事件被上层 View 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。


http://www.kler.cn/a/517329.html

相关文章:

  • Baklib如何推动企业知识管理的创新与转型探讨
  • 怎么样把pdf转成图片模式(不能复制文字)
  • SPP蓝牙串口
  • 网易Android开发面试题200道及参考答案 (上)
  • 网络安全大模型和人工智能场景及应用理解
  • javaSE.类的继承
  • Visual Studio 2022 GitHub Copilot聊天使用
  • 软件测试入门—软件缺陷 Bug 详解
  • 如何将IP地址修改为海外IP:详细操作指南
  • linux网络 | TCP报头之六个标记位与部分可靠性策略
  • 【无标题】mysql python 连接
  • qml Dialog详解
  • 达梦拷贝DM_HOME的复制安装
  • Python Numpy 数组的条件筛选
  • ubuntu 把安装好的 wine软件 打包 到另外一台机器
  • R语言学习笔记之高效数据操作
  • 第17篇:python进阶:详解数据分析与处理
  • SpringCloud之服务间通信超时:突破微服务的“时间枷锁”
  • 如何在Windows系统上安装和配置Node.js及Node版本管理器(nvm)
  • 基于微信阅读网站小程序的设计与实现(LW+源码+讲解)
  • HarmonyOS基于ArkTS卡片服务
  • # AI绘图中的Embedding、CLIP、Flux中的Clip与LCM SDXL加速生成解析
  • Vue 2 + Element UI 实现密码显示、隐藏切换功能
  • rust学习-宏的定义与使用
  • flutter入门系列教程<三>:tabbar的高度自适用,支持无限滚动
  • UDP/TCP ⑤-KCP || QUIC || 应用场景