当前位置 博文首页 > 一只修仙的猿:Android事件分发机制二:viewGroup与view对事件的
很高兴遇见你~
在上一篇文章 Android事件分发机制一:事件是如何到达activity的? 中,我们讨论了触摸信息从屏幕产生到发送给具体 的view处理的整体流程,这里先来简单回顾一下:
前面的分发步骤我们清楚了,那么viewGroup是如何对触摸事件进行分发的呢?View又是如何处理触摸信息的呢?正是本文要讨论的内容。
事件处理中涉及到的关键方法就是 dispatchTouchEvent
,不管是viewGroup还是view。在viewGroup中,dispatchTouchEvent
方法主要是把事件分发给子view,而在view中,dispatchTouchEvent
主要是处理消费事件。而主要的消费事件内容是在 onTouchEvent
方法中。下面讨论的是viewGroup与view的默认实现,而在自定义view中,通常会重写 dispatchTouchEvent
和 onTouchEvent
方法,例如DecorView等。
秉着逻辑先行源码后到的原则,本文虽然涉及到大量的源码,但会优先讲清楚流程,有时间的读者仍然建议阅读完整源码。
事件分发中涉及到一个很重要的点:多点触控,这是在很多的文章中没有体现出来的。而要理解viewGroup如何处理多点触控,首先需要对触摸事件信息类:MotionEvent,有一定的认识。MotionEvent中承载了触摸事件的很多信息,理解它更有利于我们理解viewGroup的分发逻辑。所以,首先需要先理解MotionEvent。
触摸事件的基本类型有三种:
一个完整的触摸事件系列是:从ACTION_DOWN开始,到ACTION_UP结束 。这其实很好理解,就是手指按下开始,手指抬起结束。
手指可能会在屏幕上滑动,那么中间会有大量的ACTION_MOVE事件,例如:ACTION_DOWN、ACTION_MOVE、ACTION_MOVE...、ACTION_UP。
这是正常的情况,而如果出现了一些异常的情况,事件序列被中断,那么会产生一个取消事件:
所以,完整的事件序列是:从ACTION_DOWN开始,到ACTION_UP或者ACTION_CANCEL结束 。当然,这是我们一个手指的情况,那么在多指操作的情况是怎么样的呢?这里需要引入另外的事件类型:
区别于ACTION_DOWN和ACTION_UP,使用另外两个事件类型来表示手指的按下与抬起,使得ACTION_DOWN和ACTION_UP可以作为一个完整的事件序列的边界 。
同时,一个手指的事件序列,是从ACTION_DOWN/ACTION_POINTER_DOWN开始,到ACTION_UP/ACTION_POINTER_UP/ACTION_CANCEL结束。
到这里先简单做个小结:
触摸事件的类型有:ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_POINTER_DOWN、ACTION_POINTER_UP,他们分别代表不同的场景。
一个完整的事件序列是从ACTION_DOWN开始,到ACTION_UP或者ACTION_CANCEL结束。
一个手指的完整序列是从ACTION_DOWN/ACTION_POINTER_DOWN开始,到ACTION_UP/ACTION_POINTER_UP/ACTION_CANCEL结束。
第二,我们需要理解MotionEvent中所携带的信息。
假如现在屏幕上有两个手指按下,如下图:
触摸点a先按下,而触摸点b后按下,那么自然而然就会产生两个事件:ACTION_DOWN和ACTION_POINTER_DOWN。那么是不是ACTION_DOWN事件就只包含有触摸点a的信息,而ACTION_POINTER_DOWN只包含触摸点b的信息呢?换句话说,这两个事件是不是会独立发出触摸事件?答案是:不是。
每一个触摸事件中,都包含有所有触控点的信息。例如上述的点b按下时产生的ACTION_POINTER_DOWN事件中,就包含了触摸点a和触摸点b的信息。那么他是如何区分这两个点的信息?我们又是如何知道ACTION_POINTER_DOWN这个事件类型是属于触摸点a还是触摸点b?
在MotionEvent对象内部,维护有一个数组。这个数组中的每一项对应不同的触摸点的信息,如下图:
数组下标称为触控点的索引,每个节点,拥有一个触控点的完整信息。这里要注意的是,一个触控点的索引并不是一成不变的,而是会随着触控点的数目变化而变化。例如当同时按下两个手指时,数组情况如下图:
而当手指a抬起后,数组的情况变为下图:
可以看到触控点b的索引改变了。所以跟踪一个触控点必须是依靠一个触控点的id,而不是他的索引 。
现在我们知道每一个MotionEvent内部都维护有所有触控点的信息,那么我们怎么知道这个事件是对应哪个触控点呢?这就需要看到MotionEvent的一个方法:getAction
。
这个方法返回一个整型变量,他的低1-8位表示该事件的类型,高9-16位表示触控点索引。我们只需要将这16位进行分离,就可以知道触控点的类型和所对应的触控点。同时,MotionEvent有两个获取触控点坐标的方法:getX()/getY()
,他们都需要传入一个触控点索引来表示获取哪个触控点的坐标信息。
同时还要注意的是,MOVE事件和CANCEL事件是没有包含触控点索引的,只有DOWN类型和UP类型的事件才包含触控点索引。这里是因为非DOWN/UP事件,不涉及到触控点的增加与删除。
这里我们再来小结一下:
- 一个MotionEvent对象内部使用一个数组来维护所有触控点的信息
- UP/DOWN类型的事件包含了触控点索引,可以根据该索引做出对应的操作
- 触控点的索引是变化的,不能作为跟踪的依据,而必须依据触控点id
关于MotionEvent需要了解一个更加重要的点:事件分离。
首先需要知道事件分发的一个原则:一个view消费了某一个触点的down事件后,该触点事件序列的后续事件,都由该view消费 。这也比较符合我们的操作习惯。当我们按下一个控件后,只要我们的手指一直没有离开屏幕,那么我们希望这个手指滑动的信息都交给这个view来处理。换句话说,一个触控点的事件序列,只能给一个view消费。
经过前面的描述我们知道,一个事件是包含所有触摸点的信息的。当viewGroup在派发事件时,每个触摸点的信息就需要分开分别发送给感兴趣的view,这就是事件分离。
例如Button1接收了触摸点a的down事件,Button2接收了触摸点b的down事件,那么当一个MotionEvent对象到来时,需要将他里面的触摸点信息,把触摸点a的信息拆开发送给button1,把触摸点b的信息拆开发送给button2。如下图:
那么,可不可以不进行分离?当然可以。这样的话每次都把所有触控点的信息发送给子view。这可以通过FLAG_SPLIT_MOTION_EVENTS这个标志进行设置是否要进行分离。
小结一下:
一个触控点的序列一般情况下只给一个view处理,当一个view消费了一个触控点的down事件后,该触控点的事件序列后续事件都会交给他处理。
事件分离是把一个motionEvent中的触控点信息进行分离,只向子view发送其感兴趣的触控点信息。
我们可以通过设置FLAG_SPLIT_MOTION_EVENTS标志让viewGroup是否对事件进行分离
到这里关于MotionEvent的内容就讲得差不多,当然在分离的时候,还需要进行一定的调整,例如坐标轴的更改、事件类型的更改等等,放在后面讲,接下来看看ViewGroup是如何分发事件的。
这一步可以说是事件分发中的重头戏了。不过在理解了上面的MotionEvent之后,对于ViewGroup的分发细节也就容易理解了。
整体来说,ViewGroup分发事件分为三个大部分,后面的内容也会围绕着三大部分展开:
大体的流程是:每一个事件viewGroup会先判断是否要拦截,如果是down事件(这里的down事件表示ACTION_DOWN和ACTION_POINTER_DOWN,下同),还需要挨个遍历子view看看是否有子view消费了down事件,最后再把事件派发下去。
在开始解析之前,必须先了解一个关键对象:TouchTarget。
前面我们讲到:一个触控点的序列一般情况下只给一个view处理,当一个view消费了一个触控点的down事件后,该触控点的事件序列后续事件都会交给他处理。对于viewGroup来说,他有很多个子view,如果不同的子view接受了不同的触控点的down事件,那么ViewGroup如何记录这些信息并精准把事件发送给对应的子view呢?答案就是:TouchTarget。
TouchTarget中维护了每个子view以及所对应的触控点id,这里的id可以不止一个。TouchTarget本身是个链表,每个节点记录了子view所对应的触控点id。在viewGroup中,该链表的链表头是mFirstTouchTarget,如果他为null,表示没有任何子view接收了down事件。
TouchTarget有个非常神奇的设计,他只使用一个整型变量来记录所有的触控id。整型变量中哪一个二进制位为1,则对应绑定该id的触控点。
例如 00000000 00000000 00000000 10001000,则表示绑定了id为3和id为7的两个触控点,因为第3位和第7位的二进制位是1。这里可以间接说明系统支持的最大多点触控数是32,当然实际上一般是8比较多。当要判断一个TouchTarget绑定了哪些id时,只需要通过一定的位操作即可,既提高了速度,也优化了空间占用。
当一个down事件来临时,viewGroup会为这个down事件寻找适合的子view,并为他们创建一个TouchTarget加入到链表中。而当一个up事件来临时,viewGroup会把对应的TouchTarget节点信息删除。那接下来,就直接看到viewGroup中的dispatchTouchEvent
是如何分发事件的。首先看到源码中的第一部分:事件拦截。
这里的拦截分为两部分:安全拦截和逻辑拦截。
安全拦截是一直被忽略的一种情况。当一个控件a被另一个非全屏控件b遮挡住的时候,那么有可能被恶意软件操作发生危险。例如我们看到的界面是这样的:
但实际上,我们看到的这个按钮时不可点击的,实际上触摸事件会被分发到这个按钮后面的真正接收事件的按钮:
然后我们就白给了。这个安全拦截行为由两个标志控制:
具体的源码如下:
View.java api29
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
// 两个标志,前者表示当被覆盖时不处理;后者表示当前窗口是否被非全屏窗口覆盖
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
return false;
}
return true;
}
第二种拦截是逻辑拦截。如果当前viewGroup中没有TouchTarget,而且这个事件不是down事件,这就意味着viewGroup自己消费了先前的down事件,那么这个事件就无须分发到子view必须自己消费,也就不需要拦截这种情况的事件。除此之外的事件都是需要分发到子view,那么viewGroup就可以对他们进行判断是否进行拦截。简单来说,只有需要分发到子view的事件才需要拦截 。
判断是否拦截主要依靠两个因素:FLAG_DISALLOW_INTERCEPT标志和 onInterceptTouchEvent()
方法。
onInterceptTouchEvent()
方法判断是否需要拦截。onInterceptTouchEvent方法默认只对一种特殊情况作了拦截。一般情况下我们会重写这个方法来拦截事件:// 只对一种特殊情况做了拦截
// 鼠标左键点击了滑动块
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
viewGroup的 dispatchTouchEvent
方法逻辑中对于事件拦截部分的源码分析如下:
ViewGroup.java api29
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// 对遮盖状态进行过滤
if (onFilterTouchEventForSecurity(ev)) {
...
// 判断是否需要拦截
final boolean intercepted;
// down事件或者有target的非down事件则需要判断是否需要拦截
// 否则不需要进行拦截判断,因为一定是交给自己处理
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 此标志为子view通过requestDisallowInterupt方法设置
// 禁止viewGroup拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 调用onInterceptTouchEvent判断是否需要拦截
intercepted = onInterceptTouchEvent(ev);
// 恢复事件状态
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// 自己消费了down事件,那么后续的事件非down事件都是自己处理
intercepted = true;
}
...;
}
...;
}
对于每一个down事件,不管是ACTION_DOWN还是ACTION_POINTER_DOWN,viewGroup都会优先在控件树中寻找合适的子控件来消费他。因为对于每一个down事件,标志着一个触控点的一个崭新的事件序列,viewGroup会尽自己的最大能力寻找合适的子控件。如果找不到合适的子控件,才会自己处理down事件。因为,消费了down事件,意味着接下来该触控点的事件序列事件都会交给该view消费,如果viewGroup拦截了事件,那么子view就无法接收到任何事件消息。
viewGroup寻找子控件的步骤也不复杂。首先viewGroup会为他的子控件构造一个控件列表,构造的顺序是view的绘制顺序的逆序,也就是一个view的z轴系数越高,显示高度越高,在列表的顺序就会越靠前。这其实比较好理解,显示越高的控件肯定是优先接收点击的。除了默认情况,我们也可以进行自定义列表顺序,这里就不展开了。
viewGroup会按顺序遍历整个列表,判断触控点的位置是否在该view的范围内、该view是否可以点击等,寻找合适的子view。如果找到合适的子view,则会把down事件分发给他,如果该view接收事件,则会为他创建一个TouchTarget,将该触控id和view进行绑定,之后该触控点的事件就可以直接分发给他了。
而如果没有一个控件适合,那么会默认选取TouchTarget链表的最新一个节点。也就是当我们多点触控时,两次手指按下,如果没有找到合适的子view,那么就被认为是和上一个手指点击的是同个view。因此,如果viewGroup当前有正在消费事件的子控件,那么viewGroup自己是不会消费down事件的。
接下来我们看看源码分析(代码有点长,需要慢慢分析理解):
ViewGroup.java api29
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// 对遮盖状态进行过滤
if (onFilterTouchEventForSecurity(ev)) {
// action的高9-16位表示索引值
// 低1-8位表示事件类型
// 只有down或者up事件才有索引值
final int action = ev.getAction();
// 获取到真正的事件类型
final int actionMasked = action & MotionEvent.ACTION_MASK;
...
// 拦截内容的逻辑
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
...
}
...
// 三个变量:
// split表示是否需要对事件进行分裂,对应多点触摸事件
// newTouchTarget 如果是down或pointer_down事件的新的绑定target
// alreadyDispatchedToNewTouchTarget 表示事件是否已经分发给targetview了
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 如果没有取消和拦截进入分发
if (!canceled && !intercepted) {
...
// down或pointer_down事件,表示新的手指按下了,需要寻找接收事件的view
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 多点触控会有不同的索引,获取索引号
// 该索引位于MotionEvent中的一个数组,索引值就是数组下标值
// 只有up或down事件才会携带索引值
final int actionIndex = ev.getActionIndex();
// 这个整型变量记录了TouchTarget中view所对应的触控点id
// 触控点id的范围是0-31,整型变量中哪一个二进制位为1,则对应绑定该id的触控点
// 例如 00000000 00000000 00000000 10001000
// 则表示绑定了id为3和id为7的两个触控点
// 这里根据是否需要分离,对触控点id进行记录,
// 而如果不需要分离,则默认接收所有触控点的事件
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// down事件表示该触控点事件序列是一个新的序列
// 清除之前绑定到到该触控id的TouchTarget
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
// 如果子控件数目不为0而且还没绑定到新的id
if (newTouchTarget == null && childrenCount != 0) {
// 使用触控点索引获取触控点位置
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// 从前到后创建view列表
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
// 判断是否是自定义view顺序
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 遍历所有子控件
for (int i = childrenCount - 1; i >= 0; i--) {
// 从子控件列表中获取到子控件
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...
// 检查该子view是否可以接受触摸事件和是否在点击的范围内
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 检查该子view是否在touchTarget链表中
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 链表中已经存在该子view,说明这是一个多点触摸事件
// 即两次都触摸到同一个view上
// 将新的触控点id绑定到该TouchTarget上
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// 找到合适的子view,把事件分发给他,看该子view是否消费了down事件
// 如果消费了,需要生成新的TouchTarget
// 如果没有消费,说明子view不接受该down事件,继续循环寻找合适的子控件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 保存该触控事件的相关信息
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 保存该view到target链表
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// 标记已经分发给子view,退出循环
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}// 这里对应for (int i = childrenCount - 1; i >= 0; i--)
...
}// 这里对应判断:(newTouchTarget == null && childrenCount != 0)
if (newTouchTarget == null && mFirstTouchTarget != null) {
// 没有子view接收down事件,直接选择链表尾的view作为target
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}// 这里对应if (actionMasked == MotionEvent.ACTION_DOWN...)
}// 这里对应if (!canceled && !intercepted)
...
}// 这里对应if (onFilterTouchEventForSecurity(ev))
...
}
经过了拦截与寻找消费down事件的控件之后,无论前面的处理结果如何,最终都是需要将事件进行派发,不管是派发给自己还是子控件。这里派发的对象只有两个:viewGroup自身或TouchTarget。
经过了前面的寻找消费down事件子控件步骤,那么每个触控点都找到了消费自己事件序列的控件并绑定在了TouchTarget中;而如果没有找到合适的子控件,那么消费的对象就是viewGroup自己。因此派发事件的主要任务就是:把不同触控点的信息分发给合适的viewGroup或touchTarget。
派发的逻辑需要结合前面MotionEvent和TouchTarget的内容。我们知道MotionEvent包含了当前屏幕所有触控点信息,而viewGroup的每个TouchTarget则包含了不同的view所感兴趣的触控点。
如果不需要进行事件分离,那么直接将当前的所有触控点的信息都发送给每个TouchTarget即可;
如果需要进行事件分离,那么会将MotionEvent中不同触控点的信息拆开分别创建新的MotionEvent,并发送给感兴趣的子控件;
如果TouchTarget链表为空,那么直接分发给viewGroup自己;所以touchTarget不为空的情况下,viewGroup自己是不会消费事件的,这也就意味着viewGroup和其中的view不会同时消费事件。
上图展示了需要事件分离的情况下进行的事件分发。
在把原MotionEvent拆分成多个MotionEvent时,不仅需要把不同的触控点信息进行分离,还需要对坐标进行转换和改变事件类型:
viewGroup中真正执行事件派发的关键方法是 dispatchTransformedTouchEvent
,该方法会完成关键的事件分发逻辑。源码分析如下:
ViewGroup.java api29
// 该方法接收原MotionEvent事件、是否进行取消、目标子view、以及目标子view感兴趣的触控id
// 如果不是取消事件这个方法会把原MotionEvent中的触控点信息拆分出目标view感兴趣的触控点信息
// 如果是取消事件则不需要拆分直接发送取消事件即可
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 如果是取消事件,那么不需要做其他额外的操作,直接派发事件即可,然后直接返回
// 因为对于取消事件最重要的内容就是事件本身,无需对事件的内容进行设置
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// oldPointerIdBits表示现在所有的触控id
// desirePointerIdBits来自于该view所在的touchTarget,表示该view感兴趣的触控点id
// 因为desirePointerIdBits有可能全是1,所以需要和oldPointerIdBits进行位与
// 得到真正可接收的触控点信息
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// 控件处于不一致的状态。正在接受事件序列却没有一个触控点id符合
if (newPointerIdBits == 0) {
return false;
}
// 来自原始MotionEvent的新的MotionEvent,只包含目标感兴趣的触控点
// 最终派发的是这个MotionEvent
final MotionEvent transformedEvent;
// 两者相等,表示该view接受所有的触控点的事件
// 这个时候transformedEvent相当于原始MotionEvent的复制
if (newPointerIdBits == oldPointerIdBits) {
// 当目标控件不存在通过setScaleX()等方法进行的变换时,
// 为了效率会将原始事件简单地进行控件位置与滚动量变换之后
// 发送给目标的dispatchTouchEvent()方法并返回。
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
// 复制原始MotionEvent
transformedEvent = MotionEvent.obtain(event);
} else {
// 如果两者不等,说明需要对事件进行拆分
// 只生成目标感兴趣的触控点的信息
// 这里分离事件包括了修改事件的类型、触控点索引等
transformedEvent = event.split(newPointerIdBits);
}
// 对MotionEvent的坐标系,转换为目标控件的坐标系并进行分发
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
// 计算滚动量偏移
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
// 存在scale等变换,需要进行矩阵转换
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
// 调用子view的方法进行分发
handled = child.dispatchTouchEvent(transformedEvent);
}
// 分发完毕,回收MotionEvent
transformedEvent.recycle();
return handled;
}
好了,了解完上面的内容,来看看viewGroup的 dispatchTouchEvent
中派发事件的代码部分:
ViewGroup.java api29
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// 对遮盖状态进行过滤
if (onFilterTouchEventForSecurity(ev)) {
...
if (mFirstTouchTarget == null) {
// 经过了前面的处理,到这里touchTarget依旧为null,说明没有找到处理down事件的子控件
// 或者down事件被viewGroup本身消费了,所以该事件由viewGroup自己处理
// 这里调用了dispatchTransformedTouchEvent方法来分发事件
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 已经有子view消费了down事件
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
// 遍历所有的TouchTarget并把事件分发下去
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
// 表示事件在前面已经处理了,不需要重复处理
handled = true;
} else {
// 正常分发事件或者分发取消事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 这里调用了dispatchTransformedTouchEvent方法来分发事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// 如果发送了取消事件,则移除该target
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// 如果接收到取消获取up事件,说明事件序列结束
// 直接删除所有的TouchTarget
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 清除记录的信息
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
// 如果仅仅只是一个PONITER_UP
// 清除对应触控点的触摸信息
removePointersFromTouchTargets(idBitsToRemove);
}
}// 这里对应if (onFilterTouchEventForSecurity(ev))
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}