阅读 234

事件分发勘误:当前View的TouchTarget是什么

前言

这里先给大家道个歉! 上一篇文章为啥还在聊:事件分发?还不是因为不会!,其实有一个严重的错误。不知道给小伙伴们带来了多少困扰。那就是我在上一篇文章中说当前View的TouchTarget记录的是消费当前事件的View。其实这是不对的

所以今天这篇文章首先是纠错,其次是彻底捋一下TouchTarget到底指的是什么,以及事件分发的全流程。

正文

上文咱们从源码看到,如果当前View的mFirstTouchTarget不为null,那么当前的TouchEvent事件,就会直接分发到mFirstTouchTarget中记录的View上(也就是调用这个View的dispathTouchEvent())。

那么假设mFirstTouchTarget为消费这个事件的View,那么事件就会从根View直接分发到消费事件的View,也就是说事件直接跳过所有中间的层级的View(“没有中间商赚差价”)。不过我猜有经验的小伙伴都会知道,这个错的,不会越过中间的View。

没错,事实上这个假设的确是错的!

Log打脸

代码中分别是ViewGroupA中嵌套ViewGroupB,ViewGroupB嵌套了ViewC。所有的分发方法都直接返回super的实现,只有ViewC的onTouchEvent()方法返回true。此时我们点击滑动ViewC...

ViewGroupA -> dispatchTouchEvent -> MotionEvent.ACTION_DOWN

ViewGroupA -> onInterceptTouchEvent -> MotionEvent.ACTION_DOWN

ViewGroupB -> dispatchTouchEvent ->MotionEvent.ACTION_DOWN

ViewGroupB -> onInterceptTouchEvent -> MotionEvent.ACTION_DOWN

ViewC -> dispatchTouchEvent -> MotionEvent.ACTION_DOWN

ViewC -> onTouchEvent -> MotionEvent.ACTION_DOWN

ViewGroupA -> dispatchTouchEvent -> MotionEvent.ACTION_MOVE

ViewGroupA -> onInterceptTouchEvent -> MotionEvent.ACTION_MOVE

ViewGroupB -> dispatchTouchEvent ->MotionEvent.ACTION_MOVE

ViewGroupB -> onInterceptTouchEvent -> MotionEvent.ACTION_MOVE

ViewC -> dispatchTouchEvent -> MotionEvent.ACTION_MOVE

ViewC -> onTouchEvent -> MotionEvent.ACTION_MOVE

ViewGroupA -> dispatchTouchEvent -> MotionEvent.ACTION_UP

ViewGroupA -> onInterceptTouchEvent -> MotionEvent.ACTION_UP

ViewGroupB -> dispatchTouchEvent ->MotionEvent.ACTION_UP

ViewGroupB -> onInterceptTouchEvent -> MotionEvent.ACTION_UP

ViewC -> dispatchTouchEvent -> MotionEvent.ACTION_UP

ViewC -> onTouchEvent -> MotionEvent.ACTION_UP

我们会发现,Touch事件是按照View层级的顺序一级一级的去传递。因此我们可以得出一个结论:

当前View上的mFirstTouchTarget很有可能记录的是它子View中可能消费事件的那个View。

Debug求证

接下来,让我们断点到ViewGroupA上的dispathTouchEvent()一看究竟:

的确如此,当前View的mFirstTouchTarget就是记录它子View中那个命中消费事件的View。

因此基于这个结果,事件分发的流程就真正串起来了!

上篇文章我们知道事件分发的逻辑一切都在根View的dispatchTouchEvent()中完成。此时只把场景限制到ViewGroupA->ViewGroupB->ViewC中,我们来看一下分发是如何完成的。

梳理

事件DOWN来到ViewGroupA的dispatchTouchEvent(),此方法中由于自己onInterceptTouchEvent()返回false,因此将通过事件的x、y来交给符合此范围的子View继续去分发此事件。但是此时谁都不知道这个事件能被谁消费

找到子View后(也就是ViewGroupB),调用ViewGroupB的dispatchTouchEvent()。对于ViewGroupA来说,ViewGroupB的dispatchTouchEvent()返回值将决定是否有View消费此事件。

  • 如果返回true,说明事件分发成功,mFirstTouchTarget记录为ViewGroupB,以后的事件全部交给它处理
  • 如果返回false,此事件没有View消费,mFirstTouchTarget为null。调用自身onTouchEvent()尝试自己消费,如果不消费,以后事件将不再传递过来。(因为对于ViewGroupA的上一层来说,mFirstTouchTarget为null)

ViewGroupB的dispatchTouchEvent()同样要重走拦截这一套,当然自身同样没有拦截,然后事件同样通过x、y交给自己的子View的dispatchTouchEvent(),也就是ViewC。

ViewGroupB同样也在等ViewCdispatchTouchEvent()返回值来决定自己的mFirstTouchTarget

由于ViewC是一个View,而对于View的dispatchTouchEvent()返回值来说,它取决于自身的onTouchEvent()。因为我们的ViewC消费了事件,也就是onTouchEvent()返回了true。因此ViewC的dispatchTouchEvent()也返回true。

所以ViewGroupB的通过ViewC的dispatchTouchEvent()返回true,知道了事件被ViewC消费,那么此时mFirstTouchTarget记录了ViewC,后续的事件ViewGroupB就知道分发给ViewC了。因此此时VIewGroupB的dispatchTouchEvent()返回true。

同理,ViewGroupA也就是知道了,分发到自己的事件以后可以分发给VIewGroupB...

就这样,后续的MVOE,UP事件就可以顺利的一层层往下分发了。

总结

因此,有一些问题我们就可以通过源码去解释了:

问题1:ViewGroupB的onInterceptTouchEvent()返回true,但onTouchEvent()返回false会发生什么?

当前View如果拦截了事件,那么它的dispatchTouchEvent()返回值,将取决子自身的onTouchEvent()。因此此时返回了false。所以VewGroupB的dispatchTouchEvent()返回false。

所以此时对于ViewGroupA来说,事件在子View这里是没有分发出去的,所以ViewGroupA尝试消费,如果自身onTouchEvent()返回false,那么后续的事件将不过分发过来了。

问题2:ViewGroupB中的dispatchTouchEvent()不掉任何方法直接返回true,会发生什么?

我们知道VewGroup以及View中dispatchTouchEvent()的实现才是真正进行事件分发的关键。如果我们ViewGroupB中的dispatchTouchEvent()直接返回true。对于VewGroupA来说,后续的事件将直接交给ViewGroupB,那是ViewGroupBdispatchTouchEvent()没有调任何方法,因此对于ViewGroupB来说它除了会回调dispatchTouchEvent()将不会调用onInterceptTouchEvent()onTouchEvent()

尾声

这俩篇文章所以真正意义上把事件分发的基本流程从根本了梳理了一篇。建议各位小伙伴结合着文章自己也看一篇源码...

一篇下来,关于事件分发的面试题绝对不可能难住你...(哈哈,还差一个CANCEL事件,以后有时间再补上)

OK,小伙伴们,如果觉得文章不错...我有个小小的请求,我想吃鸡腿了....

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

个人公众号:咸鱼正翻身

关注下面的标签,发现更多相似文章
评论