Android 自定义 View 之事件分发机制总结

前言:不论你在什么时候开始,重要的是开始之後就不要停止。

前面几篇文章,算是对自定义 View 这个知识点有了认识。我们知道,掌握了 onMeasure 、onLayout 之后也能开发一些自定义控件,但是通常情况下,一个自定义控件还需要加入用户触摸反馈,这就引入了另一个自定义 View 基础知识——事件分发机制。

事件分发机制涉及两个知识点:1、事件分发传递流程;2、滑动冲突问题。本篇介绍第一个知识点事件分发传递流程,第二个知识点会在下一篇再做详细介绍。

本文不打算分析源码,源码分析笔者会在恰当的时期再写的,本文是对事件分发传递流程的总结性文章。你不必再去别的地方参考文章浪费时间了,因为这里已经涵盖到了几乎所有的事件分发机制的理论知识,掌握本篇和下一篇文章,再结合后面两个自定义控件的实例加以巩固,那么这个知识点也就吸收了。废话不讲了,开更——

Android 事件分发流

一张图来解释和说明会清晰很多,下面根据画的一张事件分发流程图,说明的事件从用户点击之后,在不同函数不同返回值的情况的最终走向。(这张图在网上很流行,但是本质上是错误的,源码不是这么走向,知识为了帮助理解事件分发流程)


image

仔细看的话,图分为3层,从上往下依次是 Activity、ViewGroup、View
事件从左上角那个白色箭头开始,由 Activity 的 dispatchTouchEvent 做分发
箭头的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
dispatchTouchEvent 和 onTouchEvent 的框里有个【true---->消费】的字,表示的意思是如果方法返回 true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。
目前所有的图的事件是针对 ACTION_DOWN 的,对于 ACTION_MOVE 和 ACTION_UP我们最后做分析。
只有 return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消费了(终止传递)。

仔细看整个图,我们得出事件流 走向的几个结论(希望读者专心的看下图 1,多看几遍,脑子有比较清晰的概念。)
1、如果事件不被中断,整个事件流向是一个类U型图,我们来看下这张图,可能更能理解U型图的意思。

image

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

2、dispatchTouchEvent 和 onTouchEvent 一旦 return true, 事件就停止传递了(到达终点)(没有谁能再收到这个事件)。看下图中只要 return true 事件就没再继续传下去了,对于 return true 我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了。

image

3、dispatchTouchEvent 和 onTouchEvent return false 的时候事件都回传给父控件的onTouchEvent 处理。

image

看上图深蓝色的线,对于返回 false 的情况,事件都是传给父控件 onTouchEvent 处理。

  • 对于 dispatchTouchEvent 返回 false 的含义应该是:事件停止往子 View 传递和分发同时开始往父控件回溯(父控件的 onTouchEvent 开始从下往上回传直到某个 onTouchEvent return true),事件分发机制就像递归,return false 的意义就是递归停止然后开始回溯。
  • 对于onTouchEvent return false 就比较简单了,它就是不消费事件,并让事件继续往父控件的方向从下往上流动。

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

image

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

5、onInterceptTouchEvent 的作用

image

Intercept 的意思就拦截,每个 ViewGroup 每次在做分发的时候,问一问拦截器要不要拦截(也就是问问自己这个事件要不要自己来处理)如果要自己处理那就在 onInterceptTouchEvent 方法中 return true 就会交给自己的 onTouchEvent 的处理,如果不拦截就是继续往子控件往下传。默认是不会去拦截的,因为子 View 也需要这个事件,所以 onInterceptTouchEvent 拦截器 return super.onInterceptTouchEvent() 和r eturn false 是一样的,是不会拦截的,事件会继续往子 View 的 dispatchTouchEvent 传递。

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

image

首先看下 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 方法的。

说了这么多,不知道有说清楚没有,我这边最后总结一下:

  • 对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父 View 的 onTouchEvent 方法。
  • ViewGroup 想把自己分发给自己的 onTouchEvent,需要拦截器 onInterceptTouchEvent 方法 return true 把事件拦截下来。
  • ViewGroup 的拦截器 onInterceptTouchEvent 默认是不拦截的,所以 return super.onInterceptTouchEvent()=return false;
  • View 没有拦截器,为了让View可以把事件分发给自己的 onTouchEvent,View的 dispatchTouchEvent 默认实现(super)就是把事件分发给自己的 onTouchEvent。

ViewGroup 和 View 的 dispatchTouchEvent 是做事件分发,那么这个事件可能分发出去的四个目标

注:------> 后面代表事件目标需要怎么做。
1、 自己消费,终结传递。------->return true ;
2、 给自己的 onTouchEvent 处理-------> 调用 super.dispatchTouchEvent() 系统默认会去调用 onInterceptTouchEvent,在onInterceptTouchEvent return true就会去把事件分给自己的 onTouchEvent 处理。
3、 传给子 View ------>调用 super.dispatchTouchEvent() 默认实现会去调用 onInterceptTouchEvent 在 onInterceptTouchEvent return false,就会把事件传给子类。
4、 不传给子 View,事件终止往下传递,事件开始回溯,从父 View 的 onTouchEvent 开始事件从下到上回归执行每个控件的 onTouchEvent -------> return false;
注: 由于 View 没有子 View 所以不需要 onInterceptTouchEvent 来控件是否把事件传递给子 View 还是拦截,所以 View 的事件分发调用 super.dispatchTouchEvent() 的时候默认把事件传给自己的 onTouchEvent 处理(相当于拦截),对比 ViewGroup 的 dispatchTouchEvent 事件分发,View 的事件分发没有上面提到的4个目标的第3点

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

1、自己消费掉,事件终结,不再传给谁----->return true;
2、继续从下往上传,不消费事件,让父View也能收到到这个事件----->return false;View的默认实现是不消费的。所以super==false。

ViewGroup的onInterceptTouchEvent方法对于事件有两种情况:

1、拦截下来,给自己的onTouchEvent处理--->return true;
2、不拦截,把事件往下传给子View---->return false,ViewGroup默认是不拦截的,所以super==false;

关于ACTION_MOVE 和 ACTION_UP

上面讲解的都是针对ACTION_DOWN的事件传递,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
---->消费

image

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

Activity | dispatchTouchEvent --> ACTION_MOVE 
ViewGroup1 | dispatchTouchEvent --> ACTION_MOVE
----
Activity | dispatchTouchEvent --> ACTION_UP 
ViewGroup1 | dispatchTouchEvent --> ACTION_UP
----

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


image

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 事件的流向


image

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

这个我不就画图了,效果和在ViewGroup2 的dispatchTouchEvent return 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 事件的流向


image

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

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


image

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

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


image

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

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


image

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

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


image

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

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


image

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

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


image

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

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


image

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

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


image

一下子画了好多图,还有好几种情况就不再画了,相信你也看出规律了,对于在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 消费,那么事件到此为止停止传递(MOVE 和 UP也会传到这里),如果ACTION_DOWN事件是在 onTouchEvent 消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的 onTouchEvent 处理并结束传递。

事件分发机制是一个很难理解的东西,先记住结论以及会怎么使用。对于具体的原理,会在后面的源码分析文章中介绍到。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容