Anroid触摸事件分发机制---ViewGroup相关

2023-11-11 04:10

本文主要是介绍Anroid触摸事件分发机制---ViewGroup相关,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这篇文章只介绍ViewGroup事件分发机制,因为Activity和View的事件分发,相对很简单,网上随便找一篇文章基本就能理解了,但是对于ViewGroup的事件分发机制,我真的看了不下20篇文章,没有一篇讲的到位的,没有一篇告诉你重点关注哪个点,最后只是告诉你一堆结论,或者给你看一堆流程图,最气人的是没有一篇文章告诉你ACTION_CANCEL怎么回事,ACTION_CANCEL时系统其实做了一个很重要的操作,这个操作你理解了,你之前的疑惑也许就都 理解了,后面会提到,其实对于ViewGroup的分发源码,你看Android2.3的源码就可以了,真的很简单,甚至让你看了后一辈子都忘不了,肯本不需要记任何结论或画任何复杂的流程图,高版本的源码太过复杂但是原理和Android2.3基本一样。

重点要关注的点,我会在涉及到的时候强调。

一:Android事件分发整体流程简图

这是个关注点

这个图很重要,可能你也懂这个图,但是我只是想提醒你看源码的时候心里想着这个图,这样逻辑才清晰。

二:ViewGroup事件分发Anroid2.3源码

我先把源码帖出来,这个源码跟高版本相比已经很简单了,但是我们看的时候还是可以简化一些,我下面会贴出重点代码

public boolean dispatchTouchEvent(MotionEvent ev) {final int action = ev.getAction();final float xf = ev.getX();final float yf = ev.getY();final float scrolledXFloat = xf + mScrollX;final float scrolledYFloat = yf + mScrollY;final Rect frame = mTempRect;boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (action == MotionEvent.ACTION_DOWN) {if (mMotionTarget != null) {mMotionTarget = null;}if (disallowIntercept || !onInterceptTouchEvent(ev)) {ev.setAction(MotionEvent.ACTION_DOWN);final int scrolledXInt = (int) scrolledXFloat;final int scrolledYInt = (int) scrolledYFloat;final View[] children = mChildren;final int count = mChildrenCount;for (int i = count - 1; i >= 0; i--) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE|| child.getAnimation() != null) {child.getHitRect(frame);if (frame.contains(scrolledXInt, scrolledYInt)) {final float xc = scrolledXFloat - child.mLeft;final float yc = scrolledYFloat - child.mTop;ev.setLocation(xc, yc);child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;if (child.dispatchTouchEvent(ev))  {mMotionTarget = child;return true;}}}}}}boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||(action == MotionEvent.ACTION_CANCEL);if (isUpOrCancel) {mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;}final View target = mMotionTarget;if (target == null) {ev.setLocation(xf, yf);if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {ev.setAction(MotionEvent.ACTION_CANCEL);mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;}return super.dispatchTouchEvent(ev);}if (!disallowIntercept && onInterceptTouchEvent(ev)) {final float xc = scrolledXFloat - (float) target.mLeft;final float yc = scrolledYFloat - (float) target.mTop;mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;ev.setAction(MotionEvent.ACTION_CANCEL);ev.setLocation(xc, yc);if (!target.dispatchTouchEvent(ev)) {}mMotionTarget = null;return true;}if (isUpOrCancel) {mMotionTarget = null;}final float xc = scrolledXFloat - (float) target.mLeft;final float yc = scrolledYFloat - (float) target.mTop;ev.setLocation(xc, yc);if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {ev.setAction(MotionEvent.ACTION_CANCEL);target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;mMotionTarget = null;}return target.dispatchTouchEvent(ev);
}


上面的代码我们再简化一下,并做 一些说明:


pirvate View mMotionTarget;
public boolean dispatchTouchEvent(MotionEvent ev) {final int action = ev.getAction();//是否允许拦截boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (action == MotionEvent.ACTION_DOWN) {//只有ACTION_DOWN时才走这个if语句//ACTION_DOWN时重置mMotionTarget为null,mMotionTarget指向处理并且消费掉当前ACTION_DOWN的子View。//如果不为null说明指向上次处理并消费了ACTION_DOWN的子View,因此要重置为nullif (mMotionTarget != null) {mMotionTarget = null;}//disallowIntercept默认为false,可通过requestDisallowInterceptTouchEvent(true)修改其值//高版本anroid中我有看到,在ACTION_DOWN时有一个对FLAG_DISALLOW_INTERCEPT重置的操作,避免requestDisallowInterceptTouchEvent(true)造成的影响//所以我在这里加这句代码和高版本一致mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;//不拦截有两种情况:1,不允许拦截;2,虽然允许拦截,但是并没有拦截。//既然不拦截就找对应的子View处理这个ACTION_DOWN.就是for循环判断 点击区域落到哪个子View身上。if (disallowIntercept || !onInterceptTouchEvent(ev)) {for (所有子View) {final View child = 找到的那个可以接收ACTION_DOWN的子View;if (child.dispatchTouchEvent(ev))  {//交给这个子View处理,如果子View消费掉了,就让mMotionTarget指向这个子View,且直接return,本次ACTION_DOWN事件分发结束。//如果这个子View虽然处理了但是没有消费掉该ACTION_DOWN,则继续向下执行。mMotionTarget = child;//唯一给mMotionTarget赋值的地方,谨记!!!return true;}}//没有找到合适的子View处理该ACTION_DOWN,例如点击了空白区域,即没有子View的区域等等。继续向下执行}}//targt和mMotionTarget指向同一个对象,需要注意的是,只有在ACTION_DOWN时ViewGroup不拦截且子View消费掉ACTION_DOWN时,才给mMotionTarget,只有这一个地方,谨记!!!final View target = mMotionTarget;//target为null,说明没有可接收事件的子View,需要ViewGroup自己处理该事件//target为null的原因有很多://1,ACTION_DOWN时,可能是之前的逻辑中onInterceptTouchEvent()返回true即拦截了,或者虽然没拦截但是没有找到合适的子View处理该事件,//或者虽然找到子View处理该事件了但是子View没有消费掉该事件,这些都导致mMotionTarget没有被赋值(参考前面的for循环部分)。//2,非ACTION_DOWN时,则说明之前的事件序列都是ViewGroup自己处理的(参考1)或者之前ViewGroup拦截了某次事件(后面的代码可看到拦截时会将mMotionTarget置为null)if (target == null) {//ViewGroup自己处理事件的逻辑return super.dispatchTouchEvent(ev);//若执行了这里则后续代码不再执行}//能走到这里的不可能是ACTION_DOWN,且target不是null,说明子View有处理能力同时ViewGroup也可以拦截,体会一下。//如果拦截则给子View(target)一个ACTION_CANCEL事件,同时mMotionTarget置为null,本次分发结束,下次分发的事件就交给ViewGroup自己处理,if (!disallowIntercept && onInterceptTouchEvent(ev)) {ev.setAction(MotionEvent.ACTION_CANCEL);if (!target.dispatchTouchEvent(ev)) {}//下面两行可看到,所谓的拦截其实并不是拦截,而是让子View执行ACTION_CANCEL事件,但是ViewGroup并不处理拦截下来的这个事件,而是直接消费掉(true),只是说下次事件//由我ViewGroup来处理。mMotionTarget = null;return true;}if (isUpOrCancel) {mMotionTarget = null;}//如果不拦截,则由子View处理return target.dispatchTouchEvent(ev);
}

这段代码你肯定看的懂,没必要告诉你一堆结论,给你一堆流程图,记结论很累,你的理解就是结论。

这里并没有详细介绍onInterceptTouchEvent(ev)和requestDisallowInterceptTouchEvent(true)两个方法,这两个方法很好理解,也有很多文章介绍,,简单说下就是:

requestDisallowInterceptTouchEvent(true)用于修改FLAG_DISALLOW_INTERCEPT标记为,为true说明禁止Viewgroup拦截,这样也就不会调用onInterceptTouchEvent(ev)判断是否拦截了,为false说明允许拦截,注意,允许拦截不代表肯定拦截,还要调用onInterceptTouchEvent(ev)判断最终拦截不拦截。

关注点*:再次强调看源码时一定要结合事件分发大体过程看,结合最上面的图看,脑子里要有这个分发的过程,提醒 自己时间是随着手指边移动边不断分发的。且是不断调用dispatchTouchEvent(MotionEvent ev) 的

关注点1:mMotionTarget默认为null,唯一给它赋值的地方在有子View消费掉ACTION_DOWN时,注意是唯一。

关注点2:target就是mMotionTarget

关注点3:ViewGroup拦截子View的某个非ACTION_DOWN事件时,会给这个子View一个ACTION_CANCEL事件同时把mMotionTarget置为null,这样下次分发的事件就由ViewGroup处理了。因为mMotionTarget为null了,这个事件序列之后的事件就全部交给ViewGroup处理了。

如果对您有用,请打赏仨瓜俩枣的!

  

这篇关于Anroid触摸事件分发机制---ViewGroup相关的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/387457

相关文章

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

关于Maven生命周期相关命令演示

《关于Maven生命周期相关命令演示》Maven的生命周期分为Clean、Default和Site三个主要阶段,每个阶段包含多个关键步骤,如清理、编译、测试、打包等,通过执行相应的Maven命令,可以... 目录1. Maven 生命周期概述1.1 Clean Lifecycle1.2 Default Li

numpy求解线性代数相关问题

《numpy求解线性代数相关问题》本文主要介绍了numpy求解线性代数相关问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 在numpy中有numpy.array类型和numpy.mat类型,前者是数组类型,后者是矩阵类型。数组

一文带你理解Python中import机制与importlib的妙用

《一文带你理解Python中import机制与importlib的妙用》在Python编程的世界里,import语句是开发者最常用的工具之一,它就像一把钥匙,打开了通往各种功能和库的大门,下面就跟随小... 目录一、python import机制概述1.1 import语句的基本用法1.2 模块缓存机制1.

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

MySQL中的锁和MVCC机制解读

《MySQL中的锁和MVCC机制解读》MySQL事务、锁和MVCC机制是确保数据库操作原子性、一致性和隔离性的关键,事务必须遵循ACID原则,锁的类型包括表级锁、行级锁和意向锁,MVCC通过非锁定读和... 目录mysql的锁和MVCC机制事务的概念与ACID特性锁的类型及其工作机制锁的粒度与性能影响多版本

Redis的Hash类型及相关命令小结

《Redis的Hash类型及相关命令小结》edisHash是一种数据结构,用于存储字段和值的映射关系,本文就来介绍一下Redis的Hash类型及相关命令小结,具有一定的参考价值,感兴趣的可以了解一下... 目录HSETHGETHEXISTSHDELHKEYSHVALSHGETALLHMGETHLENHSET

Spring使用@Retryable实现自动重试机制

《Spring使用@Retryable实现自动重试机制》在微服务架构中,服务之间的调用可能会因为一些暂时性的错误而失败,例如网络波动、数据库连接超时或第三方服务不可用等,在本文中,我们将介绍如何在Sp... 目录引言1. 什么是 @Retryable?2. 如何在 Spring 中使用 @Retryable