View事件分发源码分析(一)

View的事件分发源码,因为我们知道ViewGroup也是继承自View,所以这里的View是一个总称,本章我们大部分介绍的都是ViewGroup中的源码

事件传递所涉及到的重要方法:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前View,则此方法一定会执行,返回boolean类型数据,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent影响,表示是否消耗掉当前事件。

public boolean onInterceptTouchEvent(MotionEvent ev)
在dispatchTouchEvent内部会调用该方法,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么同一个事件序列当中,此方法不会再次被调用,返回结果表示是否拦截当前事件。
重点内容
注:onInterceptTouchEvent在View类中是没有这个方法的,例如TextView继承自View但是他是没有onInterceptTouchEvent这个方法的。具体原因我们后续可以从源码中窥知一二。

注:同一个事件序列是指从手机接触屏幕开始(MotionEvent.ACTION_DOWN),到手指离开屏幕的时候结束(MotionEvent.ACTION_UP),中间含有数量不定的(MotionEvent.ACTION_MOVE)

public boolean onTouchEvent(MotionEvent ev)
在dispatchTouchEvent内部会调用该方法,用来处理事件,返回结果表示是否消耗当前事件,如果不消耗,则同一个事件序列中,当前View无法再次接收到事件

事件的传递过程:Activity—->Window—->View

我们先来看Activity的dispatchTouchEvent源码

这里写图片描述

如图红框所示,Activity会将具体的工作交给Window来完成。由于Window的唯一实现是PhoneWindow
接下来我们来看PhoneWindow中的superDispatchTouchEvent()方法。

这里写图片描述

这里又调用了mDecor的superDispatchTouchEvent()方法;那么mDecor是什么呢?就是我们常听到的DecorView

这里写图片描述

FrameLayout我们就不陌生了吧,它就继承自ViewGroup而ViewGroup继承自View这也就证明了
事件的传递过程:Activity—->Window—->View

我们来看一下ViewGroup的dispatchTouchEvent源码,也就是我们要重点分析的源码,源码大概200多行,由于源码较长我只分析一些比较重要的部分。这里我会将代码行数一起截图下来
(本人使用的是Api23)ViewGroup的dispatchTouchEvent源码从 2181 行开始

第一部分


这里写图片描述

这里是判断如果分发的事件是MotionEvent.ACTION_DOWN(因为down事件是开端)进行一些初始化操作.从源码的英文注释也可以看出:清除以往的Touch状态(state)开始新的手势(gesture)
cancelAndClearTouchTargets(ev)中有一个非常重要的操作: 将mFirstTouchTarget设置为null
注:这里的mFirstTouchTarget是一个很重要的变量
这里写图片描述

这里写图片描述

随后在resetTouchState()中重置Touch状态标识,这里也有调用clearTouchTargets()方法
这里写图片描述

我们继续看dispatchTouchEvent的源码

第二部分


这部分代码从行数上我们就能看出来是紧接着第一部分的,从英文注释上我们不难看出,这部分是用来检查是否进行拦截的,也就进入了开头提到的的3个重要方法中的onInterceptTouchEvent,这里我们也发现了mFirstTouchTarget这个很重要的变量。
这里写图片描述

我们可以发现这里执行了onInterceptTouchEvent方法,给intercepted变量进行赋值onInterceptTouchEvent这个用来拦截事件的方法很简单。
这里写图片描述

onInterceptTouchEvent() 中的 if 语句是针对鼠标事件,所以这个方法就超简单了,默认返回的就是 false
方法开头也介绍过了就是用来判断是否拦截事件的,如果ViewGroup一旦决定拦截,那么这个事件序列内的所有事件都会直接交给它来处理,当然这也不是绝对的,我们仍然可以通过一些手段来强制交给其他的View,这里我们暂且不考虑这种极端情况。

第三部分


检查cancel,通过这里我们要给变量canceled进行赋值操作;
这里写图片描述

第四部分


这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

这里的代码就比较多了 大约100行左右,2237行红色框的if判断条件我们可以看出canceled和intercepted都是false的情况下才会执行,也就是不是取消也不拦截的状态下。2259行的if条件主要参考childrenCount!=0也就是当前ViewGroup存在子View的情况下
我们这里主要看一下2259红色框这个if判断中做了那些操作,2268行绿色框中的for循环主要的工作就是从当前ViewGroup中查找可以接收处理事件的子View,查找的方法大致依据Touch坐标寻找子View来接收Touch事件,这里我们可以注意到总共有两处出现break可以打断这个for循环的操作
第一个break:这里是在newTouchTarget != null的情况下进入的,那么这种情况代表什么呢,其实就代表我们找到了处理事件的子View,

第二个break:这里引入了一个我个人认为很重要的一个方法dispatchTransformedTouchEvent 如果上面的if不满足,当然也不会执行break语句. 于是代码会执行到这里来. 调用方法dispatchTransformedTouchEvent()将Touch事件传递给子View做递归处理(也就是遍历该子View的View树)该方法很重要,看一下源码中关于该方法的描述:
这里写图片描述
这里写图片描述
这里写图片描述

将Touch事件传递给特定的子View. 该方法十分重要!在该方法中为一个递归调用,会递归调用dispatchTouchEvent()方法, 在dispatchTouchEvent()
如果子View为ViewGroup:
并且Touch没有被拦截那么**递归调用**dispatchTouchEvent()
如果子View为View:
那么最终就会调用其onTouchEvent()(因为子View非常逗比的把事件分发给了自己,所以会调用onTouchEvent()),这个就不再赘述了. 该方法返回true则表示子View消费掉该事件,同时进入该if判断.
满足if语句后重要的操作有:
1 给newTouchTarget赋值
2 给alreadyDispatchedToNewTouchTarget赋值为true.将Touch派发给新的TouchTarget
3 执行break.因为该for循环遍历子View判断哪个子View接受Touch事件,既然已经找到了那么就跳出该for循环.
4 注意:如果dispatchTransformedTouchEvent()返回false子ViewonTouchEvent返回false(即Touch事件未被消费)那么就不满足该if条件,也就无法执行addTouchTarget()从而导致mFirstTouchTarget=null(我们又发现了mFirstTouchTarget变量).那么该子View就无法继续处理ACTION_MOVE事件和ACTION_UP事件!!这也就说明如果一个View无法处理同一事件序列中的ACTION_DOWN事件,那么同一序列事件都不会再交给他来执行
5 注意:如果dispatchTransformedTouchEvent()返回true子ViewonTouchEvent返回true(即Touch**事件被消费)那么就满足该if条件.从而**mFirstTouchTarget != null

小结:对于此处ACTION_DOWN的处理具体体现在dispatchTransformedTouchEvent()该方法返回boolean,如下:
true—->事件被消费—–>mFirstTouchTarget != null
false—>事件未被消费—->mFirstTouchTarget = null
因为dispatchTransformedTouchEvent()会递归调用dispatchTouchEvent()和onTouchEvent()所以该方法的返回值是由onTouchEvent()决定的。

简单地说onTouchEvent()是否消费了Touch事件(true or false)的返回值决定了dispatchTransformedTouchEvent()的返回值!从而决定了mFirstTouchTarget是否为null!从而进一步决定了ViewGroup是否处理Touch事件.这一点在下面的代码中很有体现

注:第四部分是对ACTION_DOWN的处理 如果事件不是ACTION_DOWN则不会经过第四部分的处理直接来到接下来的第五部分

第五部分


这里写图片描述
这里写图片描述

通过第四部分的结论我得知mFirstTouchTarget 变量的两种情况:
情况1:
false—>事件未被消费—->mFirstTouchTarget = null
经过上面的分析mFirstTouchTarget为null就是说Touch事件未被消费. 即没有找到能够消费touch事件的子组件或Touch事件被拦截了,则调用ViewGroupdispatchTransformedTouchEvent()方法处理Touch事件则和普通View一样. 即子View没有消费Touch事件,那么子View的上层ViewGroup才会调用其onTouchEvent()处理Touch事件.在源码中的注释为:No touch targets so treat this as an ordinary view.也就是说此时ViewGroup像一个普通的View那样调用dispatchTouchEvent(),且在dispatchTouchEvent()中会去调用onTouchEvent()方法.具体的说就是在调用dispatchTransformedTouchEvent()时第三个参数为null.
第三个参数View child为null会做什么样的处理呢?请参见上面dispatchTransformedTouchEvent()的源码分析
这就是为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了
这就是为什么子view对于Touch事件处理返回false那么其上层的ViewGroup才可以处理Touch事件

情况2:
true—->事件被消费—–>mFirstTouchTarget != null
mFirstTouchTarget不为null即找到了可以消费Touch事件子View且后续Touch事件可以传递到该子View

这里我们对于ViewGroup的dipatchTouchEvent源码进行了比较详细的分析,也简单介绍了下onInterceptTouchEvent,通过以上的分析我相信大家对于ViewGorup的事件是如何分发的应该有了一大致的了解,后续我们会进入View分析它的dipatchTouchEvent以及最终处理事件的onTouchEvent方法。
该部分将在View的事件分发源码分析(二)中继续讲解。

猜你喜欢

转载自blog.csdn.net/omyrobin/article/details/73216015
今日推荐