Android开发进阶—View事件分发机制完全解析(上)

1.前言

       在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件,总结一句:事件分发机制很重要。对于Android事件分发没有一点点概念的可以先去看看View的事件体系

2.Android事件的分发

       在Android的事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。

       首先通过一个View视图树来了解一下Activity中View的布局结构

       

       当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述视图树中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。

       其实一个触摸事件是由一个ACTION_DOWN事件、多个ACTION_MOVE事件和一个ACTION_UP事件组成的,ViewGroup在传递触摸事件时,首先会遍历ViewGroup中的View,判断触摸点是否在各个子View中,如果在各个子View中则触发调用相关的方法,如果点击的位置没有子View,那么不管onIntercepTouchEvent返回的是什么,ViewGroup的onTouchEvent都会执行。我们首先分析ACTION_DOWN事件发生后View的事件分发情况。


       上图总共分为三层从上往下依次是Activity、ViewGroup、View,事件是从左上角白色的箭头开始的,当一个ACTION_DOWN事件发生以后的传递顺序依次是Window—>Activity—>根ViewGroup,当传递到Activity时由Activity的dispatchTouchEvent做分发,在dispatchTouchEvent和 onTouchEvent的框里有个【true—>消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。

1)如果事件不被中断,整个事件流向是一个类U型图


       从上图可以看出如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity—>ViewGroup—>View 从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View>ViewGroup>Activity从下往上调用onTouchEvent方法。

2)dispatchTouchEvent 和 onTouchEvent 的返回值一旦为true时,事件就停止传递了(到达终点)(没有谁能再收到这个事件)。看下图中只要return true事件就没再继续传下去了,对于return true我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了。如果事件能够传递给当前View,那么此方法一定会被调用,返回的结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,表示是否消耗当前事件。


3)dispatchTouchEvent 和 onTouchEvent的返回值为false时事件都回传给父控件的onTouchEvent处理。


       从上图中可以看出,dispatchTouchEvent的返回值为false的含义应该是:事件停止往子View传递和分发同时开始往父控件回溯(父控件的onTouchEvent开始从下往上回传直到某个onTouchEvent return true),事件分发机制就像递归,return false 的意义就是递归停止然后开始回溯。

       而对于onTouchEvent的返回值为false的含义就比较简单了,它就是不消费事件,并让事件继续往父控件的方向从下往上流动。

4)dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent
       ViewGroup 和View的这些方法的默认实现就是会让整个事件安装U型完整走完,所以 return super.xxxxxx() 就会让事件依照U型的方向的完整走完整个事件流动路径,中间不做任何改动,不回溯、不终止,每个环节都走到。

       

       所以如果看到方法return super.xxxxx() 那么事件的下一个流向就是走U型下一个目标,根据上图就能够很快判断出下一个走向是哪个控件的哪个函数。

5)onInterceptTouchEvent的作用


       从onInterceptTouchEvent()方法的名字上看,Intercept的意思就拦截,的确这个方法是在每个ViewGroup在做分发的时候调用,其目的就是问一问拦截器要不要拦截(也就是问问自己这个事件要不要自己来处理),如果要拦截自己处理那么onInterceptTouchEvent()方法就会返回true,并且将事件交给自己的onTouchEvent()方法进行处理。如果不拦截就是将事件分发到子View,那么onInterceptTouchEvent()方法就会返回false,在这个方法中默认是不拦截的,因为子View也需要这个事件,所以在onInterceptTouchEvent()方法的源码中return super.onInterceptTouchEvent()和return false是一样的,是不会拦截的,事件会继续往子View的dispatchTouchEvent传递。

6)ViewGroup 和View 的dispatchTouchEvent方法返回super.dispatchTouchEvent()的时候事件流走向


       首先看下ViewGroup 的dispatchTouchEvent,之前说的return true是终结传递。return false 是回溯到父View的onTouchEvent,然后ViewGroup怎样通过dispatchTouchEvent方法能把事件分发到自己的onTouchEvent处理呢,return true和false 都不行,那么只能通过Interceptor把事件拦截下来给自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent。那么对于View的dispatchTouchEvent return super.dispatchTouchEvent()的时候呢事件会传到哪里呢,很遗憾View没有拦截器。但是同样的道理return true是终结。return false 是回溯会父类的onTouchEvent,怎样把事件分发给自己的onTouchEvent 处理呢,那只能return super.dispatchTouchEvent,View类的dispatchTouchEvent()方法默认实现就是能帮你调用View自己的onTouchEvent方法的。

      根据上面事件分发的流程可以总结如下:

  1. 对于 dispatchTouchEvent(),onTouchEvent(),return true是终结事件传递。return false 是回溯到父View的onTouchEvent()方法
  2. ViewGroup 想把自己分发给自己的onTouchEvent(),需要拦截器onInterceptTouchEvent()方法return true 把事件拦截下来
  3. ViewGroup 的拦截器onInterceptTouchEvent()默认是不拦截的,所以return super.onInterceptTouchEvent() = return false
  4. View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent(),View的dispatchTouchEvent()默认实现(super)就是把事件分发给自己的onTouchEvent()
       通过上面的讲解,我们应该了解到如下几点:
       1、ViewGroup和View 的dispatchTouchEvent 是做事件分发,那么这个事件可能分发出去的四个目标
       (1)自己消费,终结传递 —— 返回true        
       (2)给自己的onTouchEvent处理 —— 调用super.dispatchTouchEvent()系统默认会去调用                                                  onInterceptTouchEvent()方法,在onInterceptTouchEvent()方法中返回true就会去把事件分给自己的                            onTouchEvent()方法进行处理
      (3)传给子View —— 调用super.dispatchTouchEvent()默认实现会去调用onInterceptTouchEvent()方法,在                         onInterceptTouchEvent()方法中,返回为false表示不对事件进行拦截,就会把事件传给子类
      (4)不传给子View,事件终止往下传递,事件开始回溯,从父View的onTouchEvent()开始事件从下到上回归执行每            个控件的onTouchEvent() —— 返回false
       由于View没有子View所以不需要onInterceptTouchEvent()来控件是否把事件传递给子View还是拦截,所以View的事件分发调用super.dispatchTouchEvent()的时候默认把事件传给自己的onTouchEvent()处理(相当于拦截)。

       2、ViewGroup和View的onTouchEvent()方法是做事件处理的,那么这个事件只能有两个处理方式:

       (1)自己消费掉,事件终结,不再传给谁 —— 返回true

       (2)继续从下往上传,不消费事件,让父View也能收到到这个事件 —— 返回false,View的默认实现是不消费的所            以super==false

       3、ViewGroup的onInterceptTouchEvent()方法对于事件有两种情况:

       (1)拦截下来,给自己的onTouchEvent()处理 —— 返回true

       (2)不拦截,把事件往下传给子View —— 返回false,ViewGroup默认是不拦截的所以super==false

3.触摸事件的其它情况

       在开始的时候说过一个触摸事件是由一个ACTION_DOWN事件、多个ACTION_MOVE事件和一个ACTION_UP事件组成的,以上的分析都是基于ACTION_DOWN事件发生后View的事件分发情况,接下来就来分析ACTION_MOVE事件和ACTION_UP事件在事件分发的过程中的传递情况。

       ACTION_MOVE事件和ACTION_UP事件在传递的过程中并不是和ACTION_DOWN事件一样,如果在执行ACTION_DOWN事件的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent()在进行事件分发的时候,只有前一个事件(如ACTION_DOWN事件),如果该方法返回true,才会收到ACTION_MOVE事件和ACTION_UP事件。具体这句话很多博客都说了,但是具体含义是什么呢?我们来看一下下面的具体分析。

       上面提到过了,事件如果不被打断的话是会不断往下传到叶子层(View),然后又不断回传到Activity,dispatchTouchEvent()和onTouchEvent()可以通过return true 消费事件,终结事件传递,而onInterceptTouchEvent()并不能消费事件,它相当于是一个分叉口起到分流导流的作用,ACTION_MOVE事件和ACTION_UP事件会在哪些函数被调用,之前说了并不是哪个函数收到了ACTION_DOWN,就会收到 ACTION_MOVE 等后续的事件的。下面通过几张图看看不同场景下,ACTION_MOVE事件和ACTION_UP事件的具体走向并总结一下规律。

1)我们在ViewGroup1 的dispatchTouchEvent()方法返回true消费这次事件
       ACTION_DOWN事件从(Activity的dispatchTouchEvent)——>ViewGroup1 的dispatchTouchEvent()) 后结束传递,事件被消费(如下图红色的箭头代码ACTION_DOWN 事件的流向)

//打印日志
Activity | dispatchTouchEvent ——> ACTION_DOWN 
ViewGroup1 | dispatchTouchEvent ——> ACTION_DOWN
——>消费


       在这种场景下ACTION_MOVE和ACTION_UP 将如何传递呢,看下面的打出来的日志

Activity | dispatchTouchEvent ——> ACTION_MOVE 
ViewGroup1 | dispatchTouchEvent ——> ACTION_MOVE
----
TouchEventActivity | dispatchTouchEvent ——> ACTION_UP 
ViewGroup1 | dispatchTouchEvent ——> ACTION_UP
----
下图中
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向


2)我们在ViewGroup2 的dispatchTouchEvent 返回true消费这次事件

Activity | dispatchTouchEvent ——> ACTION_DOWN 
ViewGroup1 | dispatchTouchEvent ——> ACTION_DOWN
ViewGroup1 | onInterceptTouchEvent ——> ACTION_DOWN
ViewGroup2 | dispatchTouchEvent ——> ACTION_DOWN
——>消费
Activity | dispatchTouchEvent ——> ACTION_MOVE 
ViewGroup1 | dispatchTouchEvent ——> ACTION_MOVE
ViewGroup1 | onInterceptTouchEvent ——> ACTION_MOVE
ViewGroup2 | dispatchTouchEvent ——> ACTION_MOVE
----
TouchEventActivity | dispatchTouchEvent ——> ACTION_UP 
ViewGroup1 | dispatchTouchEvent ——> ACTION_UP
ViewGroup1 | onInterceptTouchEvent ——> ACTION_UP
ViewGroup2 | dispatchTouchEvent ——> ACTION_UP
----
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向


3)我们在View 的dispatchTouchEvent 返回true消费这次事件

       这次的效果跟上面两个的差不多同样的收到ACTION_DOWN 的dispatchTouchEvent()函数都能收ACTION_MOVE和ACTION_UP

       根据上面的三组实验我们可以得出,如果在某个控件的dispatchTouchEvent 返回true消费终结事件,那么收到ACTION_DOWN 的函数也能收到 ACTION_MOVE和ACTION_UP

4)我们在View 的onTouchEvent 返回true消费这次事件

红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向


5)我们在ViewGroup 2 的onTouchEvent 返回true消费这次事件

红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向


6)我们在ViewGroup 1 的onTouchEvent 返回true消费这次事件

红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向


7)我们在Activity 的onTouchEvent 返回true消费这次事件

红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向


8)我们在View的dispatchTouchEvent 返回false并且Activity 的onTouchEvent 返回true消费这次事件

红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向

9)我们在View的dispatchTouchEvent 返回false并且ViewGroup 1 的onTouchEvent 返回true消费这次事件

红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向

10)我们在View的dispatchTouchEvent 返回false并且在ViewGroup 2 的onTouchEvent 返回true消费这次事件

红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向


11)我们在ViewGroup2的dispatchTouchEvent 返回false并且在ViewGroup1 的onTouchEvent返回true消费这次事件

红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向


12)我们在ViewGroup2的onInterceptTouchEvent 返回true拦截此次事件并且在ViewGroup 1 的onTouchEvent返回true消费这次事件

红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向

       通过上面的图可以看出这里存在的规律,对于在onTouchEvent()消费事件的情况:在哪个View的onTouchEvent()返回true,那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent()并结束本次事件传递过程。

       对于ACTION_MOVE事件和ACTION_UP事件来说,ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值