说到事件分发机制,可能不少人都只知其一;不知其二,懵懵懂懂的感受。你们都知道有dispatch、onInterupt、onTouchEvent这3个核心方法,也大概知道这些方法的做用。可是不知道你们知不知道这3个方法之间的关联,以及事件分发的原理呢,今天给你们分享一下事件分发,只要跟着个人节奏一步一步地走下去,保证只知其一;不知其二的同窗看完之后醍醐灌顶,瞬间打通任督二脉。数组
在不少人的意识里,认为在View和ViewGroup中拥有以上3个方法,因此事件分发的目标是View和ViewGroup。这里其实说得并不全,事件分发最初是到达Activity的,我写了一个最简单的Viewgroup以及View,而后运行起来之后,对该View进行了点击,最后将事件分发的关键位置都进行了打点,你们请看下图:ui
在上图中,首先进入的是Activity的dispatchTouchEvent方法,这个重点提一下,由于多是你们最容易忽略的。那么是否是Activity、ViewGroup、View中都分别有这3个方法呢?spa
答案是否认的,咱们能够从多个方面来验证这个猜测。首先你们看上面打点,Activity中只有dispatch以及onTouchEvent,少了onInterupt,在View中也是同样,而Viewgroup中是拥有以上3个方法的。这里并非我不想打出来或者是没有执行到相应的事件,而是Activity以及View这2个父类中根本就没有onInterupt。我这里截个Activity中没有onInterupt方法的截图,View中也是同样,这里就不重复截图了。3d
因此咱们能够得出以下结论:日志
在Activity以及View中是不含有onIneruptTouchEvent的,其余的都含有。仔细想一下也是能够理解的,Activity做为最顶层,没有必要拦截全部的事件,而View做为最底层,已经没有下游可供其拦截了。orm
正如你们所知,事件分发有以上3个核心方法,为了探究他们之间的联系,我这里将全部方法都打了一遍日志。cdn
咱们首先从Activity的dispatchTouchEvent方法看起,这个也是最核心的分发方法。blog
你们能够看到,在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(),即返回true时就会执行到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方法,依此类推,总结没看懂的同窗能够从开头从新看起多看几遍,若是以上有讲错的地方也欢迎提出,共同进步。