Android事件分发机制全面解析
作者:ezy 发布时间:2023-05-11 06:53:49
目录
事件分发机制
ViewGroup.dispatchTouchEvent 源码分析
View.dispatchTouchEvent 和 View.onTouchEvent 源码分析
事件分发机制
事件分发机制的两个阶段:
分发:事件从父视图往子视图分发,被拦截后不再传递,进入回溯阶段
回溯:事件从子视图往父视图回溯,被消费后不再回溯
关键方法:
ViewGroup.dispatchTouchEvent 往子视图分发事件
ViewGroup.onInterceptTouchEvent 返回 true 表示拦截分发事件,不再传递,进入当前视图 onTouchEvent
View.dispatchTouchEvent 默认事件分发,调用 onTouchEvent
View.onTouchEvent 通常重载此方法处理事件,返回 true 表示消费事件,不再传递,返回 false 往上回溯
ViewParent.requestDisallowInterceptTouchEvent(true) 可以确保事件分发到子视图前不被拦截
假设视图层次为 A.B.C.D,事件分发回溯默认过程为:
A.dispatchTouchEvent
B.dispatchTouchEvent
C.dispatchTouchEvent
D.dispatchTouchEvent
D.onTouchEvent
C.onTouchEvent
B.onTouchEvent
A.onTouchEvent
假设 B 拦截了事件:
A.dispatchTouchEvent
B.dispatchTouchEvent -> B.onInterceptTouchEvent
B.onTouchEvent
A.onTouchEvent
假设 C.onTouchEvent 消费了事件:
A.dispatchTouchEvent
B.dispatchTouchEvent
C.dispatchTouchEvent
D.dispatchTouchEvent
D.onTouchEvent
C.onTouchEvent
事件分发机制伪代码:
class Activity {
fun dispatchTouchEvent(ev) {
if (parent.dispatchTouchEvent(ev)) {
return true
}
return onTouchEvent(ev)
}
fun onTouchEvent(ev):Boolean {...}
}
class ViewGroup : View {
fun dispatchTouchEvent(ev) {
var handled = false
if (!onInterceptTouchEvent(ev)) {
handled = child.dispatchTouchEvent(ev)
}
return handled || super.dispatchTouchEvent(ev)
}
fun onInterceptTouchEvent(ev):Boolean {...}
fun onTouchEvent(ev):Boolean {...}
}
class View {
fun dispatchTouchEvent(ev) {
var result = false
if (handleScrollBarDragging(ev)) {
result = true
}
if (!result && mOnTouchListener.onTouch(ev)) {
result = true
}
if (!result && onTouchEvent(ev)) {
result = true
}
return result
}
fun onTouchEvent(ev):Boolean {...}
}
ViewGroup.dispatchTouchEvent 源码分析
1.开始:ACTION_DOWN 事件开始一个新的事件序列,清除之前触摸状态
2.拦截:
2.1. 非 ACTION_DOWN 事件如果当前没有子视图消费事件,表示事件序列已被拦截
2.2. 事件未被拦截且子视图未申请禁止拦截时,再通过 onInterceptTouchEvent 尝试拦截事件
3.分发:如果事件未被拦截也未被取消,就遍历子视图分发事件,并寻找当前事件的触摸目标
3.1. 在触摸目标链表中找到了可以消费当前事件的视图触摸目标 -> 将其标记为当前触摸目标,延迟到步骤4分发事件给它
3.2. 一个不在触摸目标链表中的视图消费了事件 -> 将其标记为当前触摸目标,并设置为触摸目标链表表头
3.3. 未找到消费当前事件的视图,但触摸目标链表不为空 -> 将触摸目标链表末端标记为当前触摸目标
4.分发:触摸目标链表不为空,则遍历触摸目标链尝试传递事件或取消触摸目标(事件被拦截)
5.回溯:触摸目标链表为空(当前没有子视图消耗事件序列),则将事件转发给基类 dispatchTouchEvent 处理
注:触摸目标(ViewGourp.TouchTarget) 描述一个被触摸的子视图和它捕获的指针ids
public boolean dispatchTouchEvent(MotionEvent ev) {
// 省略代码 ...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 1. `ACTION_DOWN` 事件开始一个新的事件序列,清除之前触摸状态 ...
}
// 省略代码 ...
final boolean intercepted;
// 2. 拦截
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 2.2. 事件未被拦截且子视图未申请禁止拦截时,再通过 onInterceptTouchEvent 尝试拦截事件
intercepted = onInterceptTouchEvent(ev);
// 省略代码 ...
} else {
intercepted = false;
}
} else {
// 2.1. 非 `ACTION_DOWN` 事件如果当前没有子视图消费事件,表示事件序列已被拦截
intercepted = true;
}
// 省略代码 ...
if (!canceled && !intercepted) {
// 省略代码 ...
// 3. 分发:如果事件未被拦截也未被取消,就遍历子视图分发事件,并寻找当前事件的触摸目标
for (int i = childrenCount - 1; i >= 0; i--) {
// 省略代码 ...
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 3.1. 在触摸目标链表中找到了可以消费当前事件的视图触摸目标 -> 将其标记为当前触摸目标,延迟到步骤4分发事件给它
// 省略代码 ...
break;
}
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 省略代码 ...
// 3.2. 一个不在触摸目标链表中的视图消费了事件 -> 将其标记为当前触摸目标,并设置为触摸目标链表表头
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// 省略代码 ...
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// 3.3. 未找到消费当前事件的视图,但触摸目标链表不为空 -> 将触摸目标链表末端标记为当前触摸目标
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
// 省略代码 ...
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 5. 回溯:触摸目标链表为空(当前没有子视图消耗事件序列),则将事件转发给基类 dispatchTouchEvent 处理
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
// 省略代码 ...
// 4. 分发:触摸目标链表不为空,则遍历触摸目标链尝试传递事件或取消触摸目标(事件被拦截)
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 省略代码 ...
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
// 省略代码 ...
target = next;
}
}
// 省略代码 ...
}
// 省略代码 ...
return handled;
}
View.dispatchTouchEvent 和 View.onTouchEvent 源码分析
滚动条消费鼠标事件
OnTouchListener 消费触摸事件
onTouchEvent 消费触摸事件
TouchDelegate 消费触摸事件
public boolean dispatchTouchEvent(MotionEvent event) {
// 省略代码 ...
boolean result = false;
// 省略代码 ...
if (onFilterTouchEventForSecurity(event)) {
// 滚动条消费鼠标事件
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
// OnTouchListener 消费触摸事件
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// View默认的事件处理逻辑,事件可能在其中被设置的 TouchDelegate 消费
if (!result && onTouchEvent(event)) {
result = true;
}
}
// 省略代码 ...
return result;
}
public boolean onTouchEvent(MotionEvent event) {
// 省略代码 ...
if (mTouchDelegate != null) {
// TouchDelegate 消费触摸事件
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 省略代码 ...
return false;
}
来源:https://juejin.cn/post/6943852178712821790


猜你喜欢
- 程序员讨厌写文档, 讨厌写注释, 而我还讨厌写日志, 输出一个 "Id=5, 姓名=王大锤
- 现象:安装失败,具体信息:Installation did not succeed.The application could not be
- 处理提交数据1、提交的域名称和处理方法的参数名一致提交数据 : http://localhost:8080/hello?name=xiaoh
- 本文实例为大家分享了java实现微信红包的具体代码,供大家参考,具体内容如下要求基于BigDecimal类实现微信红包算法的功能,比如设置红
- 以下共有4个函数分别是:1.从剪切板获得文字。2.将字符串复制到剪切板。3.从剪切板获得图片。4.复制图片到剪切板。/** * 从剪切板获得
- 序、前言emmmmm,首先这篇文章讲的不是用BinaryFormatter来进行结构体的二进制转换,说真的BinaryFormatter这个
- 本教程适合新手小白,Java7之前的版本是没有内置JavaFx的,Java7-10是内置JavaFx的,但是到了Java10以后的版本,Or
- 前言最近公司要把百度地图集成的项目中,于是我就研究了一天百度地图的SDK,当前的版本:Android SDK v3.0.0 。 虽然百度地图
- 在看了网上多篇rxjava和retrofit的文章后,大概有了一个初步的认识,刚好要做一个多图上传的功能,就拿它开刀吧。下面的内容将基于之前
- 前言本文主要给大家介绍了关于java poi导入Excel通用工具类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍
- 前面关于spring Boot的文章已经介绍了很多了,但是一直都没有涉及到数据库的操作问题,数据库操作当然也是我们在开发中无法回避的问题,那
- 最近,Oracle 宣布 Java 14(或 Oracle JDK 14)公开可用。如果你想进行最新的实验或者开发的话,那么你可以试试在 L
- Spring的出现是为了简化 Java 程序开发,而 SpringBoot 的出现是为了简化 Spring 程序开发.SpringBoot
- Java 线程池原理Executor框架的两级调度模型在HotSpot VM的模型中,Java线程被一对一映射为本地操作系统线程。JAVA线
- 一、实验目的(1)掌握应用黑盒测试技术进行测试用例设计。(2)掌握对测试用例进行优化设计方法。二、实验内容日期问题测试以下程序:该程序有三个
- 本文实例讲述了Spring使用ClassPathResource加载xml资源。分享给大家供大家参考,具体如下:一 代码package le
- 项目要求基于Broadcast,BroadcastReceiver等与广播相关的知识实现简单的音乐播放功能,包括音乐的播放、暂停、切换、进度
- 经典分布式事务,是相对互联网中的柔性分布式事务而言,其特性为ACID原则,包括原子性(Atomictiy)、一致性(Consistency)
- 本文实例讲述了C#从DataTable获取数据的方法。分享给大家供大家参考。具体如下:通过通用类,返回一个DataTable,要想显示每个单
- 本文实例总结了C#生成随机数的方法。分享给大家供大家参考。具体分析如下:开始,很简单地使用System.Random类来生成随机数。很快,问