Android 事件分发机制三:事件分发工作流程,开发者必备的顶级 Android 开发工具
方法返回 true 表示拦截事件,返回 false 表示不拦截。
这个方法默认只对鼠标的相关操作的一种特殊情况进行了拦截,其他的情况需要具体的实现类去重写拦截。
onTouchEvent
该方法是消费事件的主要方法,存在于 view 中,viewGroup 默认并没有重写该方法。方法返回 true 表示消费事件,返回 false 表示不消费事件。
viewGroup 分发事件时,如果没有一个子 view 消费事件,那么会调用自身的 onTouchEvent 方法来处理事件。View 的 dispatchTouchEvent 方法中,并不是直接调用 onTouchEvent 方法来消费事件,而是先调用 onTouchListener 判断是否消费;如果 onTouchListener 没有消费事件,才会调用 onTouchEvent 来处理事件。
我们为 view 设置的 onClickListener 与 onLongClickListener 都是在 View 的 dispatchTouchEvent 方法中,根据具体的触摸情况被调用。
重要规则
事件分发有一个很重要的原则:一个触控点的事件序列只能给一个 view 消费,除非发生异常情况如被 viewGroup 拦截?。具体到代码实现就是:消费了一个触控点事件序列的 down 事件的 view,将持续消费该触控点事件序列接下来的所有的事件?。举个栗子:
当我手指按下屏幕时产生了一个 down 事件,只有一个 view 消费了这个 down 事件,那么接下来我的手指滑动屏幕产生的 move 事件会且仅会给这个 view 消费。而当我手机抬起,再按下时,这时候又会产生新的 down 事件,那么这个时候就会再一次去寻找消费 down 事件的 view。所以,事件分发,是以事件序列为单位的?。
因此下面的工作流程中都是指 down 事件的分发?,而不是 ACTION_MOVE 或 ACTION_UP 的分发。因为消费了 down 事件,意味着接下来的 move 和 up 事件都会给这个 view 处理,也就无所谓分发了。但同时注意事件序列是可以被 viewGroup 的 onInterceptTouchEvent 中断的,这些就属于其他的情况了。
细心的读者还会发现事件分发中包含了多点触控。在多点触控的情况下,ACTION_POINTER_DOWN 与 ACTION_DOWN 的分发规则是不同的,具体可前往第二篇文章了解详细。ACTION_POINTER_DOWN 在 ACTION_DOWN 的分发模型上稍作了一些修改而已,后面会详细解
析,
工作流程模型
工作流程模型,本质上就是不同的控件对象,viewGroup 和 view 之间事件分发方法的关系。需要注意的是,这里讨论的是 viewGroup 和 view 的默认方法实现,不涉及其他实现类如 DecorView 的重写方法。
下面用一段伪代码来表示三个事件分发方法之间的关系(?这里再次强调,这里的事件分发模型分发的事件类型是 ACTION_DOWN 且都是默认的方法,没有经过重写,这点很重要?):
public boolean dispatchTouchEvent(MotionEvent event){
// 先判断是否拦截
if (onInterceptTouchEvent()){
// 如果拦截调用自身的 onTouchEvent 方法判断是否消费事件
return onTouchEvent(event);
}
// 否则调用子 view 的分发方法判断是否处理事件
if (childView.dispatchTouchEvent(event)){
return true;
}else{
return onTouchEvent(event);
}
}
这段代码非常好的展示了三个方法之间的关系:在 viewGroup 收到触摸事件时,会先去调用?onInterceptTouchEvent
?方法判断是否拦截,如果拦截则调用自己的?onTouchEvent
?方法处理事件,否则调用子 view 的?dispatchTouchEvent
?方法来分发事件。因为子 view 也有可能是一个 viewGroup,这样就形成了一个类似递归的关系。
这里我再补上 view 分发逻辑的简化伪代码:
public boolean dispatchTouchEvent(MotionEvent event){
// 先判断是否存在 onTouchListener 且返回值为 true
if (mOnTouchListener!=null && mOnTouchListener.onTouch(event)){
// 如果成功消费则返回 true
return true;
}else{
// 否则调用 onTouchEvent 消费事件
return onTouchEvent(event);
}
}
view 与 viewGroup 不同的是他不需要分发事件,所以也就没有必要拦截事件。view 会先检查是否有 onTouchListener 且返回值是否为 true,如果是 true 则直接返回,否则调用 onTouchEvent 方法来处理事件。
基于上述的关系,可以得到下面的工作流程图:
这里为了展示类递归关系使用了画了两个 viewGroup,只需看中间一个即可,下面对这个图进行解析:
viewGroup
viewGroup 的 dispatchTouchEvent 方法接收到事件消息,首先会去调用 onInterceptTouchEvent 判断是否拦截事件
如果拦截,则调用自身的 onTouchEvent 方法
如果不拦截则调用子 view 的 dispatchTouchEvent 方法
子 view 没有消费事件,那么会调用 viewGroup 本身的 onTouchEvent
上面 1、2 步的处理结果为 viewGroup 的 dispatchTouchEvent 方法的处理结果,并返回给上一层的 onTouchEvent 处理
view
view 的 dispatchTouchEvent 默认情况下会调用 onTouchEvent 来处理事件,返回 true 表示消费事件,返回 false 表示没有消费事件
第 1 步的结果就是 dispatchTouchEvent 方法的处理结果,成功消费则返回 true,没有消费则返回 false 并交给上一层的 onTouchEvent 处理
可以看到整个工作流程就是一个“U”型结构,在不拦截的情况下,会一层层向下寻找消费事件的 view。而如果当前 view 不处理事件,那么就一层层向上抛,寻找处理的 viewGroup。
上述的工作流程模型并不是完整的,还有其他的特殊情况没有考虑。下面讨论几种特殊的情况:
事件序列被中断
我们知道,当一个 view 接收了 down 事件之后,该触控点接下来的事件都会被这个 view 消费。但是,viewGroup 是可以在中途掐断事件流的,因为每一个需要分发给子 view 的事件都需要经过拦截方法:onInterceptTouchEvent
?(当然,这里不讨论子 view 设置不拦截标志的情况)。那么当 viewGroup 掐断事件流之后,事件的走向又是如何的呢?参看下图:(注意,这里不讨论多指操作的情况,仅讨论单指操作的 move 或 up 事件被 viewGroup 拦截的情况)
当 viewGroup 拦截子 view 的 move 或 up 事件之后,会将当前事件改为 cancel 事件并发送给子 view
如果当前事件序列还未结束,那些接下来的事件都会交给 viewGroup 的 ouTouchEvent 处理
此时不管是 viewGroup 还是 view 的 onTouchEvent 返回了 false,那么将导致整个控件树的 dispatchTouchEvent 方法返回 false
秉承着一个事件序列只能给一个 view 消费的原则,如果一个 view 消耗了 down 事件却在接下来的 move 或 up 事件返回了 false,那么此事件不会给上层的 viewGroup 处理,而是直接返回 false。
多点触控情况
上面讨论的所有情况,都是不包含多点触控情况的。多点触控的情况,在原有的事件分发流程上,新增了一些特殊情况。这里就不再画图,而是把一些特殊情况描述一下,读者了解一下就可以了。
默认情况下,viewGroup 是支持多点触控的分发,但 view 是不支持多点触控的,需要自己去重写?dispatchTouchEvent
?方法来支持多点触控。
多点触控的分发规则如下:
viewGroup 在已有 view 接受了其他触点的 down 事件的情况下,另一个手指按下产生 ACTION_POINTER_DOWN 事件传递给 viewGroup:
viewGroup 会按照 ACTION_DOWN 的方式去分发 ACTION_POINTER_DOWN 事件
如果子 view 消费该事件,那么和单点触控的流程一致
如果子 view 未消费该事件,那么会交给上一个最后接收 down 事件的 view 去处理
viewGroup 两个 view 接收了不同的 down 事件,那么拦截其中一个 view 的事件序列,viewGroup 不会消费拦截的事件序列。换句话说,viewGroup 和其中的 view 不能同时接收触摸事件。
Activity 的事件分发
细心的读者会发现,上述的工作流程并不涉及 Activity。我们印象中的事件分发都是?Activity -> Window -> ViewGroup
?,那么这是怎么回事?这一切,都是 DecorView “惹的祸” 。
DecorView 重写 viewGroup 的?dispatchTouchEvent
?方法,当接收到触摸事件后,DecorView 会首先把触摸对象传递给内部的 callBack 对象。没错,这个 callBack 对象就是 Activity。加入 Activity 这个环节之后,分发的流程如下图所示:
评论