学习 View 事件分发,就像外地人上了黑车!

原本这是作为深度思考练习的随笔,没打算发表公开的。可没想到的是,我无法忘却 3 年前备受折磨的那个夜晚 —— 在我第一次学习 View 事件分发,却被网文折磨的那个夜晚 …… 🤯

是网上介绍 View 事件分发的文章不够多吗?

不是的,恰恰相反,网上的爆款文章不计其数,待你仔细阅读,却 颇有一种“外地人上了黑车”的感觉 —— 一言不合先上 30 张图表,带你在城市外围饶个上百圈,就是不直奔主题 解释一个现象为什么会存在、造成它存在的缘由为何、它如此设计是为了解决什么问题 ……

比起 拨开迷雾、明确状况、建立感性认识,他们更热衷于自我包装。

—— 有没有帮助我不管,先唬住人再说。

为了唬人,就算给他人徒添困扰、白费大量时间,也在所不惜!

正是对那次痛苦经历的念念不忘,于是我 破例 将这篇文章分享给大家。

在此,我向 3 年前的那个自己发誓,我必在 结尾 200 字 就讲明白,别人非要绕个 3000、5000 字都讲不明白的事件分发。

不仅如此,我还要额外地帮助大家理解,事件分发流程中的 3 个小细节:之所以如此设计,是出于什么考虑。通过“知其所以然”,来方便大家更好地加深印象。

😉

此外,已经订阅专栏的小伙伴请不要担心,本文仅仅是介绍 View 事件分发机制的基础。至于滑动冲突等现实问题的解决,好戏还在后头 ~

还没阅读的小伙伴也请不要着急,正因为今天讲的是基础,光是看了这一篇,你也没白来

View 事件分发的本质是递归

什么是递归呢?递归的本质是什么呢?

顾名思义,递归是一种包含 “递” 流程和 “归” 流程的算法。当我们在找寻目标时,便是处于 “递” 流程,当我们找到目标,打算从目标开始来执行事务时,我们便开启了 “归” 流程。

如果这么说有点抽象的话,不妨结合现实中的实例来理解下递归:

案例:职场任务的下发和上报,就是典型的递归

领导 自上而下、逐级地下达任务、寻找目标执行者,这就是 “递” 流程。

直到找到合适的执行者时,便开启了 自下而上 的 “归”流程。若当前执行者无法让结果 OK,那么上报给他的上级,由他的上级来执行,如果上级也不 OK,那么继续向上,直到结果 OK 为止。

伪代码来表示,即:

boolean dispatch() {
if (hasTargetChild && child.dispatch()) {
return true;
} else {
return executeByMySelf();
}
}

View 事件分发为何要设计成递归呢?

如此设计,是为了与 View 的排版相呼应。

View 的排版规则是:嵌套越深的,显示层级越高。而显示层级越高,就越容易覆盖层级低的、被用户看见。

再加上,“所见即所得”,要求 “用户看到了什么,触控到的也该是什么”(简言之,操作要符合用户直觉)。

因此,正是考虑到嵌套越深,层级越高,触摸也通常会是交给层级高的来处理,因而也将事件分发设计成递归。

View 排版规则为何设计为“嵌套越深,显示层级越高”呢?

因为这符合常理。越外层的,作为父容器而充当背景,越里层的,作为子控件而至于前景。

所以,整个流程大致是怎样的呢?

首先我们要明确的 3 点是:

1.每次完整的事件分发流程,都包含自上而下的 “递”,和自下而上的 “归” 2 个流程。

2.每次完整的事件分发流程,都是针对一个事件(MotionEvent)完成的递归,而一个事件只对应着一个 ACTION,例如 ACTION_DOWN。

3.一次用户触摸操作,我们称之为一个事件序列。一个事件序列会包含 ACTION_DOWN、ACTION_MOVE … ACTION_MOVE、ACTION_UP 等多个事件。(其中 ACTION_MOVE 的数量是从 0 到多个不等)

也即一个事件序列,包含从 ACTION_DOWN 到 ACTION_UP 的多次事件分发流程。

下面我用一张图概括 View 事件分发的递和归流程。

事件分发流程.png

如图所示:👆👆👆

事件分发包含 3 个重要方法:

dispatchTouchEventonInterceptTouchEventonTouchEvent

通过前面的 《重学安卓:Activity 的快乐你不懂!》 我们知道,View 和 ViewGroup 是组合模式的关系,因而 ViewGroup 为了分发的需要,会重写一些 View 的方法,就包括这里的 dispatchTouchEvent。

因而首先,在递的过程中,当前层级是执行 child.dispatchTouchEvent:

  • 如果 child 是 ViewGroup,那么实际执行的就是 ViewGroup 重写的 dispatchTouchEvent 方法。该方法内可以判断,是否在当前层级拦截当前事件、或是递给下一级。
  • 如果 child 是不再有 child 的 ViewGroup 或 View,那么实际执行的就是 View 类实现的 super.dispatchTouchEvent 方法。该方法内可以判断,如果 View enabled 并且实现了 onTouchListener,且 onTouch 返回 true,那么不执行 onTouchEvent,并直接返回结果。否则执行 onTouchEvent。

此外,在 onTouchEvent 中如果 clickable 并且实现了 onClickListener 或 onLongClickListener,那么会执行 onClick 或 onLongClick。

总之,走到没有 child 的层级,即意味着步入“归”流程,如果该层级的 super.dispatchTouchEvent 没有返回 true,那么将继续执行上一级的 super.dispatchTouchEvent,直到被某一级消费,也即返回 true 了为止。

事件分发流程.png

上面我们介绍了正常流程下,所会执行到的方法,包括 View 实现的 dispatchTouchEvent,ViewGroup 重写的 dispatchTouchEvent,以及 onTouchEvent。

如图。👆👆👆

其实在事件的 “递” 流程中,ViewGroup 可以在当前层级,通过设置 onInterceptTouchEvent 方法返回 true,来拦截事件的下发,而直接步入“归”流程。

正所谓 “上有正策、下有对策”。在 ViewGroup 可以拦截事件下发的同时,child 也可以通过 getParent.requestDisallowInterceptTouchEvent 方法,来阻止上一级的下发拦截。

也正是如此,构成了 “结合对交互体验的预期来解决滑动冲突” 的现实基础。

额外需要明确的 3 个小细节

细节1:明确消费的概念

要将 “消费” 和 “执行” 这两个概念明确区分开。

网上的内容总让人误以为,当前层级不消费,就是不执行 super.dispatchTouchEvent 了。

事实上,不消费,简单地理解就是,“事情做了、只是结果不 OK” —— 在归流程中,如果当前层级的 super.dispatchTouchEvent return true 了,那么再往上的层级都不再执行自己的 super.dispatchTouchEvent,而是直接 return true。并且,当前层级的下级,都执行过 super.dispatchTouchEvent,只是结果返回了 false 而已。

细节2:明确拦截的作用

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
*

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值