欢迎光临散文网 会员登陆 & 注册

Android 事件分发流程浅析

2022-02-18 23:13 作者:房顶上的铝皮水塔  | 我要投稿

事件分发流程模型

假设当前Activity中存在两个ViewGroup,每一个ViewGroup中各自有3个View。大致的模型是一个这样的结构:

事件分发流程的四个特点

事件的分发流程是通过【传播链】进行的,最顶层是Activity,最下层是View

  1. 事件传播链不能跨级别,只能逐级别发送

  2. 传播链形成之后,会按照之前的传播链直接进行(传播链具有记忆性)

  3. 传播链行程后,View可以通过api影响传播链

  4. 相对上层的View具有两次对于事件的处理机会

    1. 刚收到事件

    2. 没有子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事件,需要让【传播链】完成。这样就可以左右滑动奏效了。


总结

  1. 事件传递过程主要有四个特性

    1. 事件逐级传递

    2. 上层ViewGroup可以通过onInterceptTouchEvent干预事件接收

    3. 传播链形成后,后续事件按照传播链传播,不会重新构建

    4. 传播链形成后,可以通过子View设置disallowIntercept的flag

  2. 是这样的传播链

    1. action_down & !intercept = false,此时传播链没有构建mFirstTouchTarget=null,会调用View的方法(View这里会消费事件,并且应该会返回true?我猜的)

    2. action_down & !intercept = true,构建view的列表,并且【递归】交给子view处理

    3. action_move 传播链构件好了,mFirstTouchTarget != null,返回true

    


Android 事件分发流程浅析的评论 (共 条)

分享到微博请遵守国家法律