View在Android中是一个非常重要的概念,作用堪比四大组件,所谓View的事件分发,就是对MotionEvent事件的分发过程,当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,这个过程就是事件分发。
事件分发过程由三个很重要的方法共同来完成:
● public boolean dispatchTouchEvent(MotionEvent ev)
● public boolean onInterceptTouchEvent(MotionEvent ev)
● public boolean onTouchEvent(MotionEvent event)
当一个触摸事件产生后,他的传递过程遵循如下顺序:Activity->Window->View,即事件总是先传递给Activity,Activity再传递给Window,最后再传给顶级View,顶级View接收到事件后,会按照分发机制向下继续分发。
Activity的dispatchTouchEvent方法源码如图1:
通过源码分析,首先事件开始交给Activity所附属的Window进行分发,如果返回true,整个事件循环就结束了,返回false意味着所有View的onTouchEvent都返回了false,那么Activity的onTouchEvent就会被调用。
接下来看Window是如何将事件传递给ViewGroup的。通过源码我们得知,Window是个抽象类,而Window的superDispatchTouchEvent方法也是抽象方法,因此我们必须找到Window的实现类才行,而Window的实现类有切只有一个,就是PhoneWindow,因此接下来看一下PhoneWindow是如何处理点击事件的,源码如下:
到这里逻辑就很清晰了,PhoneWindow将事件直接传递给了DecorView,而这个DecorView其实是继承自FrameLayout的一个ViewGroup,通过((ViewGroup)(getWindow().getDecorView().findViewById(android.R.id.content))).getChildAt(0)可以获取Activity所设置的View,也就是说,我们通过setContentView设置的View是DecorView的一个子View。目前事件传递到了DectorView这里,DectorView是顶级的View,是一个ViewGroup,他会调用dispatchTouchEvent(MotionEvent ev)方法,然后的逻辑是这样的,如果顶级ViewGroup拦截事件,即onInterceptTouchEvent方法返回true,则事件由ViewGroup处理,这是如果ViewGroup的mTouchListener被设置,则onTouch会被调用,否则onTouchEvent方法会被调用,也就是说onTouchListener的优先级高于onTouchEvent,如果顶级的ViewGroup不拦截事件,则事件会传递给他所在的点击事件链上的子View,这时候子View的dispatchTouchEvent(MotionEvent ev)会被调用,到此为止,事件已经从顶级View传递给了下一层的View,接下来的传递过程和顶级View是一致的,如此循环,完成整个事件的分发。
首先看ViewGroup对点击事件的分发过程,其主要实现是在ViewGroup的dispatchTouchEvent(MotionEvent ev)方法中,这个方法代码比较多,挑重点说一下,先看下面一段,描述的是View是否拦截点击事件事情这个逻辑。
从上面代码可以看出,ViewGroup在如下两种情况下会判断是否要拦截当前事件:事件类型为ACTION_DOWN或者mFirstTouchTarget !=null,那么mFirstTouchTarget !=null是什么意思呢,从后面的逻辑可以看出来,当事件由ViewGroup的子元素成功处理时,mFirstTouchTarget 会被赋值并指向子元素,反过来,一旦ViewGroup拦截时候,mFirstTouchTarget !=null就不成立。那么当ACTION_MOVE和ACTION_UP事件到来时,由于(actionMasked == MotionEvent.ACTION_DOWN
||mFirstTouchTarget !=null)这个条件为false,将导致ViewGroup的onInterceptTouchEvent不会再被调用,并且同一序列中其他的事件都默认会交给他来处理,ViewGroup的onTouchEvent方法将会被回调。
接着看当ViewGroup不拦截事件时候,事件会向下分发交由他的子View进行处理,源代码如下:
首先遍历ViewGroup的所有子元素,然后判断点击事件的坐标是够落在子元素内,如果满足,那么事件就会交由他来处理,从下面源码可以看到,dispatchTransformedTouchEvent实际上是调用的就是子元素的dispatchTouchEvent方法,代码如下:
图8在上面的代码中child传递的不是null,因此他会直接调用子元素的dispatchTouchEvent发放,这样事件就交由子元素处理了,从而完成了一轮事件分发,如果子元素的dispatchTouchEvent返回true,那么mFirstTouchTarget就会通过addTouchTarget方法被赋值同时跳出for循环,代码如下:
图9如果遍历所有的子元素后事件都没有被处理,这包含两种情况,第一种是ViewGroup没有子元素,第二种是子元素处理了点击事件,但是在dispatchTouchEvent中返回了false,这一般是因为子元素在onTouchEvent中返回了false。这两种情况下,ViewGroup自己会处理事件,代码如下:
图10注意上面红框这段代码,里面第三个参数child为null,从前面的分析可以知道,他会调用super.dispatchTouchEvent(event)
很显然,这里就转到View的dispatchTouchEvent方法,即点击事件开始交由View来处理,请看下面分析。
View对点击事件的处理相对简单,注意这里的View不包括ViewGroup,先看他dispatchTouchEvent方法部分代码:
View对点击事件的处理过程,首先会判断有没有设置OnTouchListener,如果有,则OnTouchListener中的onTouch方法就会被调用,否则View的onTouchEvent会被调用。到这里,点击事件的分发就已经分析完了,参考书籍:Android开发艺术探索。
网友评论