本文将讨论嵌套布局,子view重叠时,当点击子view,事件的分发过程,主要针对自定义viewGroup和自定义View:
要知道viewGroup和view所关心的事件,前者是 OnInterceptTouchEvent、dispatchTouchEvent、onTouchEvent,而后者没有onInterceptTouchEvent方法,即前者可以进行
事件的拦截。一旦在上级中拦截了Touch事件,则后面根本不会触发任何方法。
dispatchTouchEvent()进行事件的分发,只要事件没有被拦截,那么该方法一定会被调用,返回结果表示是否消耗事件。
onTouchEvent在dispatchTouchEvent中调用,如果返回false,则表明该控件未接收事件,true则表示消耗该事件。
假设本文的布局背景为,两个自定义viewGroup中嵌套一个自定义view控件。<ViewGroup2> <ViewGroup1><View/></ViewGroup1> </ViewGroup2>,从整个事件的触发流程
来看,当一个touch行为产生时,通过方法里的log,来判断事件的触发情况。
首先,如果ViewGroup和View中方法均返回false,即对touch事件不拦截,正常分发。
如果在ViewGroup1的disPatchTouchEvent()返回True,则情况如下图,说明ViewGroup1消耗了事件,不会再向下分发,也不会处理。
如果在返回false,则情况如下图,说明在本层不再继续分发,并交给上层的onTouchEvent事件处理。
下载Android源码, 在Frameworks/base/core/java/android/view/ViewGroup.java中有具体实现。
disPatchTouchEvent的源码实现过程:首先判断actionMasked,如果是DOWN,就会默认为起始操作,重置TouchTarget,TouchState等参数。接着用if (actionMasked
== MotionEvent.ACTION_DOWN || mFirstTouchTarget !=null) , 调用了Intercept方法是判断如果是起始动作才拦截,或者已经有人消费掉了事件,再去判断拦截,起始动作是第一次向下分
发的时候,每个view都可以决定是否拦截,然后进一步判断是否消费,如果有人消费掉了事件,那么也拦截,不再向下传递。
在判断是否拦截后,他才会开始分发操作:
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
handled = child.dispatchTouchEvent(transformedEvent);
}
简化来讲,如果自身没有child,(自身是VIewGroup且没child或自身为view)就会调用父类的disPatchTouchEvent方法即view的方法,而该方法如下代码:
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
这里实际就是调用了onTouchEvent,并返回其值。
如果自身有child,就会继续向下分发至子控件去,并由子控件继续分发。
因此,disPatchTouchEvent实际上最终执行的还是onTouchEvent。
接下来讨论onInerceptTouchEvent(),该方法就是把事件拦截下来,和disPatchTouchEvent不同的是,他不会消耗事件,只会留给自己以及上层架构使用,比如在
ViewGroup1的onInerceptTouchEvent()事件返回true,则情况如下图:
从源码来看,该方法里仅做了个if判断,若要实现我们想要的效果,可以重写过滤条件。(个人感觉。。)
最后,来分析onTouchEvent()方法,返回true相当于onTouchEvent方法把事件已经处理掉了,就不存在继续向上层传递的情况,表现为由子view逐层向外处理
onTouchEvent时,如果某一层的onTouchEvent事件返回true,则后面的onTouch事件将不再触发,这里就不再贴图演示。
最终分析整个Touch事件的流程,我们可以把ViewGroup2的dispatchTouchEvent当成方法的一个主入口,它可能会触发后面所有控件的方法。具体流程是:当Touch事件
发生时,调用最外层disPatchTouchEvent(此方法是一个过程,到判断拦截也没有结束),此时,该方法会先判断是否拦截,即调用onInterceptTouchEvent(),若未拦截,
则由于disPatchTouchEvent的返回值由它的子view决定,就会去调用其子ViewGroup1的disPatchTouchEvent,它也会走判断拦截的过程。当到达最终控件时,不存在子
view,或它自身就是一个view,就会直接用onTouchEvent方法,开始判断是否消耗,从而返回结果给上级的disPatchTouchEvent,因此,会逐级调用onTouchEvent。
整体流程图如下:
(该图引用自单灿灿博客,很准确,不再作图)
此时,如果使用getaction函数,会发现,在上述的几种场景中,返回不同的行为,可能为UP,Down,Move等行为,如果注意到行为变化的时机时,就会发现,仅有事件
被消耗时,才会有UP的动作判断,这个其实很好判断,如果连DOWN事件都没处理,怎么会有抬手的动作,Move等动作。
Up,Move等事件,其传递和判断流程和up完全一致,只需要注意他们的发生是有前提条件的。比如在ViewGroup1处理了OntouchEvent事件,并返回true,log如下:
图中,onTouchEvent到ViewGroup1结束,接下来就开始其他动作的判断,且由于子view没处理Down的onTouch事件,所以,move动作只在ViewGroup2和ViewGroup1之间
传递。这里要注意一点,第一次Down的时候会for循环所有child,但在源码中,dispatchTouchEvent其实每次使用了if(mFirstTouchTarget==NULL)在判断是否有收到Touch事
件。如果像上面一样,进入了Down之后的事件,就会进行一个while循环,在其中进行Touchtarget next=target.next,这个next是遍历同级,而不是子集,在代码里可以看到
newTouchTarget=addTouchTarget(child,idBitsToAssign)。这个对象保存了传递路线信息,是一个链式结构,不过这个路线不是ViewGroup2->ViewGroup1->View的一个
单子,而是每个ViewGroup都会保存一个向下的路线信息。会再次调用dispatchTransformedTouchEvent来处理后续的动作。