Android 事件分发流程浅析
事件分发流程模型
假设当前Activity中存在两个ViewGroup,每一个ViewGroup中各自有3个View。大致的模型是一个这样的结构:

事件分发流程的四个特点
事件的分发流程是通过【传播链】进行的,最顶层是Activity,最下层是View
事件传播链不能跨级别,只能逐级别发送
传播链形成之后,会按照之前的传播链直接进行(传播链具有记忆性)
传播链行程后,View可以通过api影响传播链
相对上层的View具有两次对于事件的处理机会
刚收到事件
没有子View处理事件
事件的种类
ACTION_DOWN
ACTION_MOVE
ACTION_UP
ACTION_CANCEL
前言:View的事件处理逻辑
ViewGroup也是一个View,在这个事件分发的过程中,不免会调用到View的dispatchTouchEvent方法, 我们首先分析一下这个。
View#dispatchedTouchEvent
这块的逻辑比较单纯,总结来说,如果是设置了touchListener的话,就回执行onTouch回调;否则就回看看onTouchEvent咋写的。
View#setOnTouchListener中的lambda的返回值会影响onClick
设置的setOnTouchListener的结果会影响onClick的执行。我们同时也分析一下这个过程。
在上面的代码中,如果没有设置onTouchEvent,result默认为false,所以会执行onTouchEvent,在onTouchEvent中,当motionEvent为action_up时,会调用performClickInternal():
在performClick中才会调用onClick。所以onClick的执行在onTouch之后,同时受到OnTouchListener的返回值的影响。在OnClickListener中手动调用performClick可以缓解这个问题。
事件分发流程的代码解析
事件分发流程的执行过程是从Activity开始的,到View结束,具体的传播链(调用链)如下:

Activity在收到事件之后,其实会交给PhoneWindow处理。
PhoneWindow中持有DecorView,然后会调用DecorView的相关方法:
DecorView其实是一个FrameLayout,因此现在调用的重点就进入了ViewGroup#dispatchTouchEvent。
ViewGroup#dispatchTouchEvent
ACTION_DOWN事件来临
滑动触摸的起始一般是从down事件开始,紧接着是一连串的move事件,最后以up事件结束。事件的传递机制非常的复杂,其中的细节点非常多,我通过图示的方式也很难很好地呈现,所以我想将这个ViewGroup#dispatchTouchEvent拆分成很多小块,揉碎了来讲解,在最后再进行回顾。
首先我们先看看当ACTION_DOWN到来时的代码逻辑:
Case#1 action_down & onInterceptTouchEvent 返回true
假设此时在ViewGroup2中重写了onInterceptTouchEvent返回true的逻辑,我们看下面的代码,代码高度简化后就是这样的逻辑:
在这个情况下,会调用dispatchTransformedTouchEvent,其中child!=null的情况,我们紧接着具体看看:
super指的就是View#dispatchTouchEvent,上面已经分析过。
Case#1 action_down & onInterceptTouchEvent 返回false
如果是返回false的话,就需要执行if语句。在这个if语句中,会构建当前ViewGroup的View的优先级,具体是通过z坐标来排序的。这个过程通过buildTouchDispatchChildList()实现。
for循环中,主要的逻辑是这样:
首先通过调用dispatchTransformedTouchEvent进行分发, 如果返回true,就进行一些变量的设置。
这个地方,我个人认为是事件分发的机制的精髓所在。因为dispatchTransformedTouchEvent让child!=null时,会调用子View的dispatchTouchEvent,在我们的例子中,当前的ViewGroup2的下面全部都是子View,但是在更复杂的情况下,ViewGroup2中会有嵌套更加深层的ViewGroup。通过这样的分析,我们可以简单把这里理解为【递归】的开始。
回到我们这个简单的例子,如果ViewGroup2传到View1,View1选择消费事件,View#dispatchTouchEvent返回true,这里就进入if条件(不过如果有更深层的View嵌套,具体的分析也大差不差)

会设置:

并且中断当前的for循环。此时在后续的条件判断中,会走if的第二个分支,设置handled为true并且返回给Activity,这样就达成了一次从Activity-> ViewGroup -> View的事件传递。
Case#3 action_move
在后续的事件中,同样会从Activity开始分发,然后走到ViewGroup这里。
因为不是滑动事件的开始(action_down),因此不会重新构建View的列表。
而且在第一次的过程中,有些重要的变量,如mFirstTouchTarget已经被赋值了,所以逻辑会稍微有些改变,在后续的流程中会继续dispatchTransformedTouchEvent,这样其实又是一个【递归】的过程。
同时需要注意一点的是,在子View中可以通过getParent().reqeustDisallowInterceptTouchEvent进行事件拦截,这样就不会调用父ViewGroup的onInterceptTouchEvent。

实践例子
我直接使用老师上课的那个例子说明一下问题。在老师的例子中,只用了一个ViewPager作为ViewGroup,其中包含了一个ListView。继承自ViewPager的ViewGroup中重写了onInterceptTouchEvent,返回true。
改动1
此时,对于down事件,取消true;对于move事件按照水平滑动的阈值进行调整。但是这样改动之后,还是只能水平滑动,不能垂直滑动。

这是因为ViewGroup中的返回true,直接使得onIntercept执行,不可能通过dispatch传到子View中,子View就不能通过设置disallow的flag。这也就是四个特性中的,需要注意让【传播链】形成。
改动2
改动ViewGroup的onInterceptTouchEvent,改动如下。不能拦截down事件,需要让【传播链】完成。这样就可以左右滑动奏效了。

总结
事件传递过程主要有四个特性
事件逐级传递
上层ViewGroup可以通过onInterceptTouchEvent干预事件接收
传播链形成后,后续事件按照传播链传播,不会重新构建
传播链形成后,可以通过子View设置disallowIntercept的flag
是这样的传播链
action_down & !intercept = false,此时传播链没有构建mFirstTouchTarget=null,会调用View的方法(View这里会消费事件,并且应该会返回true?我猜的)
action_down & !intercept = true,构建view的列表,并且【递归】交给子view处理
action_move 传播链构件好了,mFirstTouchTarget != null,返回true
