需要首先清楚的两个概念:
ViewGroup
ViewGroup是一组View的组合,在其内部会包含多个子View,当用户点击屏幕的时候,点击的位置可能在ViewGroup上,也可能在其内部View控件上。
因此ViewGroup事件分发的重点在于如何处理当前ViewGroup和子View之间事件分发的逻辑关系:
ViewGroup中有touch相关的回调有三个:
public boolean dispatchTouchEvent(MotionEvent event);
public boolean onInterceptTouchEvent(MotionEvent event);
public boolean onTouchEvent(MotionEvent event);
View
View是最小的控件单位,不能够再被细分,所以View的事件分发重点在于如何处理处理touch事件:
View中有两个touch相关的回调事件:
public boolean dispatchTouchEvent(MotionEvent event);
public boolean onTouchEvent(MotionEvent event);
事件分发的核心dispatchTouchEvent
整个事件的分发就是一个大的递归函数,而这个递归函数就是dispatchTouchEvent,在这个递归函数中适时的调用onInterceptTouchEvent函数来拦截事件,或者调用onTouchEvent来处理事件。
dispatchTouchEvent中会经历三个步骤:
步骤1的具体实现:
如果事件为DOWN事件,则调用onInterceptTouchEvent进行事件拦截判断;
或者mFirstTouchTarget不为null,代表已经有子View捕获了这个事件,子View的dispatchTouchEvent返回true,代表当前的View捕获touch事件。
步骤2的具体实现:
图中1表明事件主动分发的前提是DOWN事件,事实上在分发DOWN事件的过程中,找到具体处理该DOWN事件的View之后,后续的MOVE和UP事件就都会分发给该View,不再需要dispatch的判断。
图中2在遍历ViewGroup下所有的子View,需要注意的是,遍历的方式是从后向前遍历,要是发现某一个子View接收touch事件,则停止遍历,这是因为Android中子View是从上向下放到子View数组中,后面的View在前面View的上面,要是上面的View可接收touch,那么就会遮挡下层View的事件接收(xml布局中,同一个ViewGroup中包裹的View,写在后面的View覆盖在上面,写在前面的View被后面的View覆盖的)
图中3在判断touch事件的坐标是否在子View的坐标范围内,并且子View没有处于动画状态。
图中4中通过调用dispatchTransformedTouchEvent方法将事件分发给子View,要是子View接收处理该事件,则该函数返回true,并将mFirstTouchEvent赋给该子View,之后的MOVE和UP事件也都会直接分发给该子View。
步骤3的具体实现:
步骤3中有两个分支判断:
为什么DOWN事件特殊
所有额touch事件都是从DOWN事件开始的,这是DOWN事件比较特殊的原因之一,另外一个原因是DOWN事件的捕获结果会直接影响后续的MOVE和UP事件的处理。
在事件分发的过程中,只有DOWN事件会传递给子View进行事件捕获的判断,一旦某个子View捕获了DOWN事件,就会将mFirstTouchTarget赋值给这个View,后续的MOVE和UP事件,也会通过遍历mFirstTouchTarget链表查找到该View,并将MOVE和UP事件直接分配给该View。也就是后续的MOVE、UP事件下发给谁,都是由起始的DOWN事件被谁捕获决定的。
mFirstTouchTarget的作用
mFirstTouchTarget的类型是TouchuTarget。
上面TouchTarget的源码可以看出,TouchTarget是一个链表结构,这个mFirstTouchTarget的作用就是为了记录捕获了touch事件的View,会将这个View保存在TouchTarget的child上,如果mFirstTouchTarget不为null,则后续的事件全部分发给TouchTarget的child进行处理。
在向子View分发事件的代码中,有一段源码:
上面的代码中,红色框处target不为null,表明已经有子View捕获了该touch事件,但是蓝色框的地方,intercepted又变成了true,表示父ViewGroup开始拦截该事件,父ViewGroup拦截事件,就会使得该事件不会传递到子View中,这种情况下,父ViewGroup会给子View传入cancelChild = true。
触发这段逻辑的场景
当父ViewGroup的onInterceptTouchEvent返回false,然后在子View的dispatchTouchEvent中返回了true(表示子View捕获事件),在之后MOVE的过程中,父ViewGroup的onInterceptTouchEvent又返回了true,intercepted重新置为true,此时上面的逻辑就会被触发,子View就会收到一个ACTION_CANCEL的事件。
总结:
关于dispatchTouchEvent、onInterceptTouchEvent以及onTouchEvent的说明:
ViewGroup和View的dispatchTouchEvent是做事件的分发的,事件可能分发的目标:
ViewGroup和View的onTouchEvent是做事件处理,有两种处理方式:
ViewGroup和View的onInterceptTouchEvent对于事件的处理有两种:
如上图,事件的分发流程是一个U形图,从activity分发到最终的子View的过程会调用整个事件分发链路上的dispatchTouchEvent,当然ViewGroup在接收事件的时候,还会调用自己的拦截器,以便让事件调用到达自己的onTouchEvent中;
要是最终的子View没有接收事件,那么事件会回溯到activity,回溯的流程中会调用到链路上的onTouchEvent。
举例1:
ViewGroup2的dispatchTouchEvent返回true,事件的分发会终止在此,如上图中的红色箭头,蓝色箭头是事件后续的MOVE和UP的流向。此时ViewGroup2还要通过onInterceptTouchEvent拦截事件,从而触发onTouchEvent。
举例2:
子View的onTouchEvent返回true,此时事件的传递如上图。
举例3:
ViewGroup1的onTouchEvent返回true,这种情况下ViewGroup的dispathTouchEvent返回false,表示不拦截事件,事件一直向下分发,直到子View向上回溯事件,在ViewGroup1的onTouchEvent返回true消费事件,可以看到红色事件的分发流程和后续事件MOVE和UP的分发并不一致。