事件分发流程核心源码解析

624 阅读7分钟

       说到事件分发机制,可能很多人都一知半解,懵懵懂懂的感觉。大家都知道有dispatch、onInterupt、onTouchEvent这3个核心方法,也大概知道这些方法的作用。但是不知道大家知不知道这3个方法之间的关联,以及事件分发的原理呢,今天给大家分享一下事件分发,只要跟着我的节奏一步一步地走下去,保证一知半解的同学看完以后醍醐灌顶,瞬间打通任督二脉。

一、事件分发的方法以及目标

       在很多人的意识里,认为在View和ViewGroup中拥有以上3个方法,所以事件分发的目标是View和ViewGroup。这里其实说得并不全,事件分发最初是到达Activity的,我写了一个最简单的Viewgroup以及View,然后运行起来以后,对该View进行了点击,最后将事件分发的关键位置都进行了打点,大家请看下图:


       在上图中,首先进入的是Activity的dispatchTouchEvent方法,这个重点提一下,因为可能是大家最容易忽略的。那么是不是Activity、ViewGroup、View中都分别有这3个方法呢?

       答案是否定的,我们可以从多个方面来验证这个猜想。首先大家看上面打点,Activity中只有dispatch以及onTouchEvent,少了onInterupt,在View中也是一样,而Viewgroup中是拥有以上3个方法的。这里并不是我不想打出来或者是没有执行到相应的事件,而是Activity以及View这2个父类中根本就没有onInterupt。我这里截个Activity中没有onInterupt方法的截图,View中也是一样,这里就不重复截图了。


所以我们可以得出如下结论:

       在Activity以及View中是不含有onIneruptTouchEvent的,其他的都含有。仔细想一下也是可以理解的,Activity作为最顶层,没有必要拦截所有的事件,而View作为最底层,已经没有下游可供其拦截了。


二、事件分发的流程

       正如大家所知,事件分发有以上3个核心方法,为了探究他们之间的联系,我这里将所有方法都打了一遍日志。


        我们首先从Activity的dispatchTouchEvent方法看起,这个也是最核心的分发方法。


       大家可以看到,在Activity中是调用了Window的superDispatchTouchEvent方法,这个Window其实是抽象类,只有一个实例那就是PhoneWindow,所以我们查看Phonewindow中的方法


       发现PhoneWindow中该方法是调用了DecorView中的superDispatchTouchEvent方法,我们点进去看下


       在DecorView中该方法是调用了其父类的dispatchTouchEvent方法,那DecorView的父类是谁呢?当然是FrameLayout,这也就相当于是最外层的ViewGroup了。继续跟下去,我们查看一下ViewGroup的dispatchTouchEvent方法。这里面代码太复杂,我这里只截取其中重要的部分来分析。


       我们看到,在dispatchTouchEvent中是有显式地调用onInterceptTouchEvent的,这也就解释了打点中ViewGroup中的onInterceptTouchEvent方法仅次于dispatchTouchEvent执行的原因。我们接着看下面一段关键代码:


        首先调用了buildTouchDispatchChildList对所有的view进行一个排序


       buildTouchDispatchChildList最终是调用到了buildOrderdChildList方法,从以上代码可以看出,该方法其实只做了一件事情,那就是对所有的view按照Z轴进行排序。这里解释一下,Z轴其实就是面向用户的立体方向。Z值越大的越放到了最底部,这里完美地解释了事件为什么会从最外层的ViewGroup往里面传了。需要注意的是,当前比较Z值的View不单单是在点击区域内的,而是屏幕上所有的View都会放入进去比较的。

       在比较完以后,我们可以看到上图中,接下来调用了另一个核心方法名为isTransformedTouchPointInView,我们点进去看下这个方法。


       这个方法的实现很简单,传入了点击的x、y坐标,判断当前传入的view是否在该坐标内,通过循环来一个一个遍历,最后得到的就是关键的在该坐标内的View了。

       到这一步,已经能从外面到里面一个一个地拿到那么点击位置的View了,接下来就是处理实际的分发流程了:


       我们可以看到,执行的是当前容器的dispatchTransformedTouchEvent方法,我们点进去看下:


       可以看到,该方法时调用了子View的dispatchTouchEvent。这里如果子View继续是ViewGroup的话依然走的是上面这些代码,如果子View是非ViewGroup呢,我们也可以看一下相关的代码:


       看到没,在普通View的dispatchTouchEvent中,如果onTouchListener方法没有被复写并且返回true的话,是会执行到当前View的onTouchEvent中的。当然如果子类中的onTouchEvent没有返回true的话,也是会执行到其ViewGroup的onTouchEvent的,这里涉及到一个责任链的模式,具体代码如下


       这里我们可以看到,对所有的View进行循环询问是否处理该事件,如果子View返回false,那么循环继续调用父View的onTouchEvent。如果dispatchTransFormedTouchEvent返回了true,也就是处理了该事件,那么会走到以下代码中:


      我们看到如果处理了就直接break了,这个break代表上面那个长长的事件分发事件结束,不再继续分发。


彩蛋快来砸呀!       

       这里需要注意的是,大家看我的打点中最后2个点是关于ACTION_UP的,是不是很奇怪为啥ACTION_DOWN的时候有一堆的打点,而ACTION_UP的时候就只有2个呢,貌似根本没传下去嘛,只是传到了Activity中。如果你发现了那说明你看得很仔细,这里我也给一下答案。


       首先如果在该View中没有处理onTouchEvent(),即返回false时就会执行到MotionEvent的setTargetAccessibilityFocus方法置为false,将该View的事件响应资格取消。

       接下来在下次还有事件分发到该View时,会先判断当前View是否还有资格,由于刚刚取消了资格,所以该View的onTouchEvent不再执行。


       在分发时首先获取到当前View是否有资格,如果没有资格就直接跳过。跳过的后果非常严重,直接不将该View加入到符合坐标的list数组中,相当于就待小黑屋收不到后面的事件了。


总结

看到这里可以串起来了吧,接下来我们来总结一下。


       首先是Activity的dispatchTouchEvent调用了PhoneWindow的superDispatchTouchEvent方法,PhoneWindow调用到DecorView的superDispatchTouchEvent方法,DecorView其实就是最外层ViewGroup,就走到了该ViewGroup的onInteruptTouchEvent方法,如果返回true则直接结束往下面的分发,否则就会调用到下一层的dispatchTouchEvent方法,下一层如果是View则会调用到onTouchEvent方法中,倘若onTouch返回true则分发结束,否则就调用到上一级的onTouchEvent方法,依此类推,总结没看懂的同学可以从开头重新看起多看几遍,如果以上有讲错的地方也欢迎提出,共同进步。