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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

禁止平板,iPad长按弹出默认菜单事件

通过监控按下抬起时间差来禁止弹出事件,把以下代码写在要禁止的页面的页面加载事件里面即可     var date;document.addEventListener('touchstart', event => {date = new Date().getTime();});document.addEventListener('touchend', event => {if (new

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

【Tools】大模型中的自注意力机制

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 自注意力机制(Self-Attention)是一种在Transformer等大模型中经常使用的注意力机制。该机制通过对输入序列中的每个元素计算与其他元素之间的相似性,

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

log4j2相关配置说明以及${sys:catalina.home}应用

${sys:catalina.home} 等价于 System.getProperty("catalina.home") 就是Tomcat的根目录:  C:\apache-tomcat-7.0.77 <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" /> 2017-08-10

Node Linux相关安装

下载经编译好的文件cd /optwget https://nodejs.org/dist/v10.15.3/node-v10.15.3-linux-x64.tar.gztar -xvf node-v10.15.3-linux-x64.tar.gzln -s /opt/node-v10.15.3-linux-x64/bin/npm /usr/local/bin/ln -s /opt/nod