通过上一篇文章(探索Android事件分发机制,带你通过源码分析事件分发流程——View篇)我们已经了解了View的事件分发过程,还没有阅读的朋友可以点连接去阅读。本篇我们将分析事件分发机制主菜——ViewGroup的事件分发
一、事件的传递顺序
为什么说ViewGroup的事件分发是主菜呢?在解答这个问题之前,我们先看一看触摸事件是怎么到达View手中的:
- 触摸到手机屏幕后,手机的屏幕会将点击事件传递给Activity
- Activity将事件交给PhoneWindow(Activity的dispatchTouchEvent方法),PhoneWindow是Window的实现类
- PhoneWindow将事件交给DecorView(PhoneWindow的superDispatchTouchEvent方法),这个DecorView就是Activity的根布局,它实际上是一个FrameLayout
- DecorView将事件交给布局文件中的根布局,随后依次向下传递
- 最终传递到了View手中
通过上面的传递步骤,大家可以看到事件是经由多个ViewGroup最终传递到了View手中。我们知道ViewGroup是一个容器,里面装着子View,那么ViewGroup应该把事件交给哪个子View呢?这里的逻辑就要比View的事件分发复杂多了。
好的,废话不多说,我们开始进入正题
二、要提前知道的几个概念
我在阅读源码的过程中走了不少弯路,主要是有一些概念没有弄懂,所以为了防止大家在阅读的时候懵逼,我们在看源码之前,先了解几个概念
1.pointer指针的概念
在源码中,频繁的提到了指针这个概念,这在早期安卓版本中是没有的,那么,这个指针究竟是何方神圣呢?
我发现,pointer这个概念的出现和MotionEvent联系密切,于是我翻看了MotionEvent的源码,果然,在MotionEvent的类注释中,找到了对Pointer的描述,我对这些描述进行了翻译:
/** * Some devices can report multiple movement traces at the same time. Multi-touch * screens emit one movement trace for each finger. The individual fingers or * other objects that generate movement traces are referred to as <em>pointers</em>. * Motion events contain information about all of the pointers that are currently active * even if some of them have not moved since the last event was delivered. */ 一些设备可以同时报告多个运动轨迹。多点触摸屏为每个手指发射一个运动轨迹。 生成运动轨迹的各个手指或其他对象被称为<em>指针</em>. 运动事件包含关于当前活动的所有指针的信息,即使它们中的一些自从递送最后一个事件以来没有移动。 ---------------------------------------------------------------------------------------------------- /** * Each pointer has a unique id that is assigned when it first goes down * (indicated by {@link #ACTION_DOWN} or {@link #ACTION_POINTER_DOWN}). A pointer id * remains valid until the pointer eventually goes up (indicated by {@link #ACTION_UP} * or {@link #ACTION_POINTER_UP}) or when the gesture is canceled (indicated by * {@link #ACTION_CANCEL}). */ 每个指针都有一个唯一的id,当它第一次Down时分配。 指针id保持有效,直到指针最终Up或手势被Cancel ---------------------------------------------------------------------------------------------------- /** * The order in which individual pointers appear within a motion event is undefined. * Thus the pointer index of a pointer can change from one event to the next but * the pointer id of a pointer is guaranteed to remain constant as long as the pointer * remains active. Use the {@link #getPointerId(int)} method to obtain the * pointer id of a pointer to track it across all subsequent motion events in a gesture. * Then for successive motion events, use the {@link #findPointerIndex(int)} method * to obtain the pointer index for a given pointer id in that motion event. */ 每个指针在运动事件中出现的顺序未定义。 因此,从一个事件到下一个事件时,指针的索引会发生改变,但是指针id保证保持恒定,只要指针保持活动。 使用{@link #getPointerId(int)}方法获取指针的id,以便在手势中的所有后续运动事件中跟踪它。 然后对于连续的运动事件,使用{@link #findPointerIndex(int)}方法获得该运动事件中给定指针id的指针索引。
通过官方的描述,我们可以知道:指针是随着多点触摸技术出现的概念,实际使用手机时,会出现同时多点触摸到手机屏的情况,所以每个触摸点就用指针来描述。早期的安卓源码之所以没有这个概念,我想应该是那时候还没有开发出支持多点触摸的屏幕
2.TouchTarget分发目标的数据结构
TouchTarget是ViewGroup的内部类,用途大家通过名字也应该能知道,这个类是对事件分发目标的封装,我们在这里主要了解一下这个类封装的数据结构
我们看一下TouchTarget的源码,在这里我就直接给大家截图出来了
好,我们可以看到,TouchTarget里封装着目标View以及指针等等。
值得我们注意的是我用红圈圈住的地方,一个TouchTarget肚子里装着另一个TouchTarget的引用,并且起名叫next~看到这里大家是否想起了什么?
没错,就是链表数据结构!所有TouchTarget被串成串,组成了一个分发目标列表,在分发的时候会将这个列表遍历,然后依次分发下去。相信大家了解了这一点之后,在下面源码中看到遍历TouchTarget的时候就不会蒙了
3、dispatchTransformedTouchEvent方法
提前对事件分发机制有了解的朋友应该知道,在ViewGroup的逻辑中,会去调用子View的dispatchTouchEvent方法,或者调用super.dispatchTouchEvent()方法。但是在API-24的源码中,并没有发现直接调用dispatchTouchEvent。在这里,dispatchTransformedTouchEvent方法中,进行了一系列的逻辑判断,并对dispatchTouchEvent方法进行了相应的调用,我们来简单看一下这个方法,感兴趣的朋友可以看一下源码以及我的注释;如果感觉看源码有点晕,可以跳过源码直接看下面的分析~
/** * 将运动事件转换为指定子控件的坐标空间,过滤掉不相关的指针,并在必要时覆盖其操作。 * 如果child为null,则假设MotionEvent将发送到此ViewGroup。 */ private boo