伪代码
boolean dispatchTouchEvent(Event event){
boolean result = false;
//第一步重置
if(DOWN){
//DOWN事件中重置。如清空FirstTarget,将禁止拦截标志位重置为0
}
//第二步判断是否要拦截
if(DOWN || firstTarget != null){//firseTarget有值,代表事件曾经分发到了子View
if(禁止拦截标志位为0){ //禁止拦截标志位可以由子View设置为1
intercepted = onInterceptTouchEvent();//询问自己是否要拦截
}else {
intercepted = false;
}
}else {
intercepted = true;
}
//第三步 DOWN事件中的分发判断
if(!intercepted){
if(DOWN){
for(int i = childCount-1; i>=0; i--){ //倒着循环,所以访问的是最后添加的View,即最上层显示的View
View v = childs[i];
if(v在触摸范围内){
if(v.dispatchTouchEvent(event)){
addTarget(v);//将该v加入firstTarget链表中,之所以是个链表因为有多指情况,单指不考虑
alreadyDispatchedToNewTouchTarget = true;//用于第四步避免重复分发
break;
}
}
}
}
}
//第四步 之后事件的分发判断
if(firstTarget == null){//当无子View消耗事件
result = super.dispatchTouchEvent(event);//把自己当成一个View,事件交给自己判断
}else{
target = firstTarget;
while(target != null){//对于单指情况,只循环一次
if(alreadyDispatchedToNewTouchTarget){
result = true;
}else {
v = target.view;
boolean cancelChild = intercepted || v是否需要cancel;
if(cancelChild){
event.setAction(CANCEL);
}
result = v.dispatchTouchEvent(event);
if(cancelChild){
从firstTarget链表中移除该v所在的target;
}
target = target.next;
}
}
}
return result;
}
}
···
代码细节
1、mFirstTarget的用处:用于"非DOWN事件"的直接分发。在DOWN事件中会通过循环来询问子View是否消耗事件,如果消耗,mFirstTarget就会有值,后续时间就会通过第四步直接对其进行分发。如果该值为空,说明无子View会处理事件,则第四步就会交给自己处理。
2、alreadyDispatchedToNewTouchTarget变量,是为了第三步已经分发了,而在第四步重复分发的情况,该变量在非DOWN事件就失去作用了。
结论
1、先判断子View是否消耗事件,如果子View不消耗才交由自己消耗。
在DOWN事件中会通过循环来访问子View,如果消耗,则mFirstTarget有值,的事件通过mFirstTarget对目标子View直接进行分发;如果子View都不消耗,则mFirstTarget无值,
事件交由自己判断。
2、决定拦截后,后续事件交由自己处理
如果DOWN事件中拦截,则mFirstTarget无值,所以直接交由自己处理
如果非DOWN事件中拦截,则mFirstTarget有值,此时,在第四步中当前事件会清空mFirstTarget中的Target,并对该Target中的子View发送CANCEL事件,而后续事件mFirstTarget等同于无值处理(即交由自己处理)。并且之后在第二步也不会再询问是否要阻止拦截,直接认为拦截事件。
3、子View显示在上层的会先判断触摸事件
因为第三步的循环时根据子View的添加顺序逆序循环,而最后添加的子View都是显示在最上层,所以如果最上层子View消耗了事件,被遮挡的子View就无法收到事件了
4、子View无法在DOWN事件中阻止其父View拦截事件(如果其父View想要拦截的话)
因为子View如果想阻止拦截,是通过设置FLAG的方式,然后在第二步会进行判断。但是第一步DOWN事件会重置状态。