【转载】更简单的学习Android事件分发
事件分发是Android中非常重要的机制,是用户与界面交互的基础。这篇文章将通过示例打印出的Log,绘制出事件分发的流程图,让大家更容易的去理解Android的事件分发机制。
一、必要的基础知识
1、相关方法
Android中与事件分发相关的方法主要包括dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三个方法,而事件分发一般会经过三种容器,分别为Activity、ViewGroup、View。下表对这三种容器分别拥有的事件分发相关方法进行了整理。
| 事件相关方法 | 方法功能 | Activity | ViewGroup | View |
|---|---|---|---|---|
| public boolean dispatchTouchEvent | 事件分发 | Yes | Yes | Yes |
| public boolean onInterceptTouchEvent | 事件拦截 | No | Yes | No |
| public boolean onTouchEvent | 事件消费 | Yes | Yes | Yes |
- 分发: dispatchTouchEvent如果返回true,则表示在当前View或者其子View(子子…View)中,找到了处理事件的View;反之,则表示没有寻找到
- 拦截: onInterceptTouchEvent如果返回true,则表示这个事件由当前View进行处理,不管处理结果如何,都不会再向子View传递这个事件;反之,则表示当前View不主动处理这个事件,除非他的子View返回的事件分发结果为false
- 消费: onTouchEvent如果返回true,则表示当前View就是事件传递的终点;反之,则表示当前View不是事件传递的终点
2、相关事件
这篇文章中我们只考虑4种触摸事件: ACTION_DOWN、ACTION_UP、ACTION_MOVE、ACTION_CANAL。 事件序列:一个事件序列是指从手指触摸屏幕开始,到手指离开屏幕结束,这个过程中产生的一系列事件。一个事件序列以ACTION_DOWN事件开始,中间可能经过若干个MOVE,以ACTION_UP事件结束。 接下来我们将使用之前的文章自定义View——弹性滑动中例子来作为本文的示例,简单增加一些代码即可,修改之后的代码请点击查看。
二、示例的默认情况
我们可以从示例代码的xml中看出,图片都是可点击的。
<?xml version="1.0" encoding="utf-8"?>
<com.idtk.customscroll.ParentView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
tools:context="com.idtk.customscroll.MainActivity"
> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/zhiqinchun"
android:clickable="true"/> <com.idtk.customscroll.ChildView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/hanzhan"
android:clickable="true"/> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/shengui"
android:clickable="true"/> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/dayu"
android:clickable="true"/> </com.idtk.customscroll.ParentView>
我们现在来点击一下,查看下打印出的日志。

根据打印出的log来绘制一张事件传递的流程图

现在来理一下事件序列的流程:
- ACTION_DOWN事件从Activity#dispatchTouchEvent方法开始
- ACTION_DOWN事件传递至ViewGroup#dispatchTouchEvent方法,ViewGroup#onInterceptTouchEvent返回false,表示不拦截ACTION_DOWN
- ACTION_DOWN事件传递到View#dispatchTouchEvent方法,在View#onTouchEvent进行执行,返回true,表示事件已经被消费
- 返回的结果true,被回传到View#dispatchTouchEvent,之后回传到ACTION_DOWN事件的起点Activity#dispatchTouchEvent方法
- ACTION_UP事件的传递过程与ACTION_DOWN相同,这里不再复述
这里使用工作中的情况来模拟一下:老板(Activity)、项目经理(ViewGroup)、软件工程师(View)
- 老板分配一个任务给项目经理(Activity#dispatchTouchEvent → ViewGroup#dispatchTouchEvent),项目经理选择自己不做这个任务(ViewGroup#dispatchTouchEvent返回false),交由软件工程师处理这个任务(<View#dispatchTouchEvent)(我们忽略总监与组长的情况),软件工程师完成了这个任务(View#onTouchEvent返回true)
- 把结果告诉项目经理(返回结果true,View#dispatchTouchEvent→ ViewGroup#dispatchTouchEvent),项目经理把结果告诉老板(返回结果true,ViewGroup#dispatchTouchEvent→Activity#dispatchTouchEvent)
- 项目经理完成的不错,老板决定把这个项目的二期、三期等都交给项目经理,同样项目经理也觉得这个软件工程师完成的不错,所以也把二期、三期等都交给这个工程师来做
通过上面的传递过程,我们可以得出一些结论:
- 事件总是由父元素分发给子元素
- 某个ViewGroup如果onInterceptTouchEvent返回为false,则表示ViewGroup不拦截事件,而是将其传递给View#dispatchTouchEvent方法
- 某个View如果onTouchEvent返回true,表示事件被消费,则其结果将直接通过dispatchTouchEvent方法传递回Activity
- 如果某个View消费了ACTION_DOWN事件,那么这个事件序列中的后续事件也将交由其进行处理(有一些特殊情况除外,比如在序列中的之后事件进行拦截)
三、在View中不消费事件
我们现在修改示例代码的xml部分,android:clickable="true"全部修改为android:clickable="false"
<?xml version="1.0" encoding="utf-8"?>
<com.idtk.customscroll.ParentView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
tools:context="com.idtk.customscroll.MainActivity"
> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/zhiqinchun"
android:clickable="false"/> <com.idtk.customscroll.ChildView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/hanzhan"
android:clickable="false"/> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/shengui"
android:clickable="false"/> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/dayu"
android:clickable="false"/> </com.idtk.customscroll.ParentView>
这时再点击一下,查看新打印出的日志

现在根据log中显示的逻辑,分别绘制ACTION_DOWN事件与ACTION_UP事件传递的流程图


我们来整理下这个事件序列的流程:
- ACTION_DOWN事件的传递与之前相同,不同的地方在于,返回值的传递
- 因为不可点击,View#onTouchEvent返回值为false,将其传递给自己的dispatchTouchEvent方法,之后传递到ViewGroup#dispatchTouchEvent方法,再传递到ViewGroup#onTouchEvent方法
- ViewGroup返回false之后,ACTION_DOWN事件交由Activity#onTouchEvent方法进行处理,然而依旧返回false,最后ACTION_DOWN事件的返回结果即为false
- ACTION_UP事件在发现View、ViewGroup并不处理ACTION_DOWN事件后,直接将其传递给了Activity#onTouch方法处理,处理返回false,ACTION_UP事件的返回结果即为false
这里使用工作中的情况来模拟:依旧是老板(Activity)、项目经理(ViewGroup)、软件工程师(View) 从老板交任务给项目经理,项目经理交任务给工程师,这一段流程和之前的例子相同。不同之处是软件工程师没有完成这个任务(View#onTouchEvent返回false),告诉项目经理我没有完成,然后项目经理自己进行了尝试,同样没有完成(ViewGroup#onTouchEvent返回false),项目经理告诉了老板,我没有完成,然后老板自己试了下也没有完成这个任务(Activity#onTouchEvent返回false),但之后的也有项目的二期、三期,不过老板知道你们完成不了,所以都是他自己进行尝试,不过很惨都没完成。(这段有点与正常情况不同,不过只是打个比方)
通过结合上面两个例子,可以得出一些结论:
- 某个View如果onTouchEvent返回false,表示事件没有被消费,则事件将传递给其父View的onTouchEvent进行处理
- 某个View如果它不消耗ACTION_DOWN事件,那么这个序列的后续事件也不会再交由它来处理
- 如果事件没有View对其进行处理,那么最后将有Activity进行处理
- View默认的onTouchEvent在View可点击的情况下,将会消耗事件,返回true;不可点击的情况下,则不消耗事件,返回false(longClickable的情况,读者可以自行测试,结果与clickable相同)
四、在ViewGroup中拦截事件
事件分发中拦截的情况,这里我把它分为2种,一种是在ACTION_DOWN事件时,就进行拦截的;另一种是在ACTION_DOWN之后的事件序列中,对事件进行了拦截。
1、在事件开始时拦截
为了达到在ViewGroup中,一开始就拦截触摸事件的效果,我们需要进行修改,在ParentView#onInterceptTouchEvent方法的最后部分,我注释掉的intercept=true;进行恢复,然后为activity_main.xml中的ParentView增加android:clickable="true"属性。
<?xml version="1.0" encoding="utf-8"?>
<com.idtk.customscroll.ParentView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
tools:context="com.idtk.customscroll.MainActivity"
> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/zhiqinchun"
android:clickable="true"/> <com.idtk.customscroll.ChildView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/hanzhan"
android:clickable="true"/> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/shengui"
android:clickable="true"/> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/dayu"
android:clickable="true"/> </com.idtk.customscroll.ParentView>


我们现在来看下拦截情况下的事件流程图


这里大部分和之前的例子相同,主要的区别是在于ViewGroup#onInterceptTouchEvent方法中,对传递的事件进行了拦截,返回true,ACTION_DOWN事件就传递到了ViewGroup#onTouchEvent中进行处理,ACTION_DOWN事件之后的传递就与之前的例子相同了。另一点重要的区别是,在ViewGroup拦截下事件之后,此事件序列的其余事件,在进入ViewGroup#dispatchTouchEvent方法之后,不在需要进行是否拦截事件的判断,而是直接进入了onTouchEvent方法之中。
使用工作中的情况来模拟:老板(Activity)、项目经理(ViewGroup)、软件工程师(View) 老板吧任务交给项目经理,项目经理认为这个项目比较难,所以决定自己处理(ViewGroup#onInterceptTouchEvent,return true),项目经理比较厉害他把任务完成了(ViewGroup#onTouchEvent,return true),然后他告诉老板他完成了(return true,ViewGroup#dispatchTouchEvent→Activity#dispatchTouchEvent)。之后老板依旧会把任务交给项目经理,项目经理知道这个任务难度,所以不假思索(也就是这个事件序列中的其余事件没有经过ViewGroup#onInterceptTouchEvent)的自己来做。
通过上面的例子,可以得出一些结论:
- 某个ViewGroup如果onInterceptTouchEvent返回为true,则ViewGroup拦截事件,将事件传递给其onTouchEvent方法进行处理
- 某个ViewGroup如果它的onInterceptTouchEvent返回为true,那么这个事件序列中的后续事件,不会在进行onInterceptTouchEvent的判断,而是由它的dispatchTouchEvent方法直接传递给onTouchEvent方法进行处理
2、在事件序列中拦截
这里把使用的示例恢复到初始状态,然后把我在ParentView#onInterceptTouchEvent方法,switch内的两个注释掉的intercept = true;代码进行恢复,最后部分intercept = true;再次注释掉。
<?xml version="1.0" encoding="utf-8"?>
<com.idtk.customscroll.ParentView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
tools:context="com.idtk.customscroll.MainActivity"
> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/zhiqinchun"
android:clickable="true"/> <com.idtk.customscroll.ChildView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/hanzhan"
android:clickable="true"/> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/shengui"
android:clickable="true"/> <com.idtk.customscroll.ChildView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/dayu"
android:clickable="true"/> </com.idtk.customscroll.ParentView>

重新运行之后,滑动一个图片,来看看Log


这里分成两张图片,是因为中间有很多ACTION_MOVE,为了方便观察,所以只截取了Log的首尾部分。 这里的关键部分,就是红框中的ACTION_CANCEL,可以看到ACTION_DOWN事件的传递时onInterceptTouchEvent并没有拦截,返回false,在其后的事件ACTION_MOVE再次进入onInterceptTouchEvent时,ViewGroup对事件进行了拦截,这样将会对View传递一个ACTION_CANCEL事件,之后的ACTION_MOVE事件就不再传递给View了。
使用工作中的情况来模拟:老板(Activity)、项目经理(ViewGroup)、软件工程师(View) 这里的情况就是,一期的任务和第一个例子一样的情况一样,由软件工程师完成,不过忽然项目经理觉得二期的任务有点难,然后决定自己完成。这时就给工程师说,这个项目的后续任务,不要你来完成了(ACTION_CANCEL)。
从这里也可以得出一个结论:
- 某个View接收了ACTION_DOWN之后,这个序列的后续事件中,如果在某一刻被父View拦截了,则这个字View会收到一个ACTION_CANCEL事件,并且也不会再收到这个事件序列中的后续事件。
五、小结
本文通过示例打印出的各种Log对Android的事件分发机制进行,得出如下结论。
- 一个事件序列是指从手指触摸屏幕开始,到手指离开屏幕结束,这个过程中产生的一系列事件。一个事件序列以ACTION_DOWN事件开始,中间可能经过若干个MOVE,以ACTION_UP事件结束。
- 事件的传递过程是由外向内的,即事件总是由父元素分发给子元素
- 如果某个View消费了ACTION_DOWN事件,那么通常情况下,这个事件序列中的后续事件也将交由其进行处理,但可以通过调用其父View的onInterceptTouchEvent方法,对后续事件进行拦截
- 如果某个View它不消耗ACTION_DOWN事件,那么这个序列的后续事件也不会再交由它来处理
- 如果事件没有View对其进行处理,那么最后将有Activity进行处理
- 如果事件传递的结果为true,回传的结果直接通过不断调用父View#dispatchTouchEvent方法,传递给Activity;如果事件传递的结果为false,回传的结果不断调用父View#onTouchEvent方法,获取返回结果。
- View默认的onTouchEvent在View可点击的情况下,将会消耗事件,返回true;不可点击的情况下,则不消耗事件,返回false(longClickable的情况,读者可以自行测试,结果与clickable相同)
- 如果某个ViewGroup的onInterceptTouchEvent返回为true,那么这个事件序列中的后续事件,不会在进行onInterceptTouchEvent的判断,而是由它的dispatchTouchEvent方法直接传递给onTouchEvent方法进行处理
- 如果某个View接收了ACTION_DOWN之后,这个序列的后续事件中,在某一刻被父View拦截了,则这个字View会收到一个ACTION_CANCEL事件,并且也不会再收到这个事件序列中的后续事件
| 事件相关方法 | 方法功能 | Activity | ViewGroup | View |
|---|---|---|---|---|
| public boolean dispatchTouchEvent | 事件分发 | Yes | Yes | Yes |
| public boolean onInterceptTouchEvent | 事件拦截 | No | Yes | No |
| public boolean onTouchEvent | 事件消费 | Yes | Yes | Yes |
【转载】更简单的学习Android事件分发的更多相关文章
- Android 进阶学习:事件分发机制全然解析,带你从源代码的角度彻底理解(上)
http://blog.csdn.net/guolin_blog/article/details/9097463 事实上我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客開始,就零 ...
- Android事件分发机制二:viewGroup与view对事件的处理
前言 很高兴遇见你~ 在上一篇文章 Android事件分发机制一:事件是如何到达activity的? 中,我们讨论了触摸信息从屏幕产生到发送给具体 的view处理的整体流程,这里先来简单回顾一下: 触 ...
- Android事件分发机制三:事件分发工作流程
前言 很高兴遇见你~ 本文是事件分发系列的第三篇. 在前两篇文章中,Android事件分发机制一:事件是如何到达activity的? 分析了事件分发的真正起点:viewRootImpl,Activit ...
- Android事件分发机制五:面试官你坐啊
前言 很高兴遇见你~ 事件分发系列文章已经到最后一篇了,先来回顾一下前面四篇,也当个目录: Android事件分发机制一:事件是如何到达activity的? : 从window机制出发分析了事件分发的 ...
- 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...
- 【朝花夕拾】Android自定义View篇之(七)Android事件分发机制(下)滑动冲突解决方案总结
前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/11072989.html],谢谢! 前面两篇文章,花了很大篇幅讲解了Android的事件分发机制 ...
- Android事件分发机制四:学了事件分发有什么用?
" 学了事件分发,影响我CV大法吗?" " 影响我陪女朋友的时间" " ..... " 前言 Android事件分发机制已经来到第四篇了,在 ...
- Android事件分发机制浅谈(一)
---恢复内容开始--- 一.是什么 我们首先要了解什么是事件分发,通俗的讲就是,当一个触摸事件发生的时候,从一个窗口到一个视图,再到一个视图,直至被消费的过程. 二.做什么 在深入学习android ...
- Android事件分发机制(下)
这篇文章继续讨论Android事件分发机制,首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子 ...
随机推荐
- 缓存AJAX的请求
在客户端缓存Ajax请求 浏览器可以缓存图片.js文件.css文件,同样浏览器也可以缓存XML Http调用(当然这需要XML Http以get方式发送调用),这种缓存基于URL,当我们发送一个请 ...
- Selenium(十):用By定位元素、鼠标事件、键盘事件
1. 用By定位元素 除了前面介绍的单位方法,WebDriver还提供了另外一套写法,即统一调用find_element()方法,通过By来声明定位的方法,并且传入对应定位方法的定位参数.具体如下: ...
- Python3 进程、线程和协程
Infi-chu: http://www.cnblogs.com/Infi-chu/ 进程.线程和协程的对比 1.定义对比 进程:是系统进行资源分配的基本单位,每启动一个进程,操作系统都需要为其分配运 ...
- 由导入paramkio包失败,而pip list又能查到此包,而引出的:离线安装python第三方库的实用方法:解决公司内网,服务器/电脑不能上网却需要安装python三方库问题(下:Linux环境中)
问题描述: 公司的Linux服务器是内网,今天要实现某个功能,会用到python控制ssh,需要安装一个Paramkio库,和上一篇一样,仅仅依靠Pypi上下载下来的离线.whl安装包是不行的,lin ...
- 网络时间服务和chrony
⽹络时间服务和chrony 实验练习: 准备实验环境: 可用的centos6.7系统. centos6 :192.168.37.6 centos7 :192.168.37.7 关闭selinux 关闭 ...
- 【Https异常】This request has been blocked; the content must be served over HTTPS
一.问题出现场景 项目从http升级到https后,jsp页面存在发送http请求的情况下就会出现该异常.因为HTTPS 是 HTTP over Secure Socket Layer,以安全为目标的 ...
- 面向对象程序设计(JAVA) 第13周学习指导及要求
2019面向对象程序设计(Java)第13周学习指导及要求 (2019.11.19-2019.11.25) 学习目标 (1) 掌握事件处理的基本原理,理解其用途: (2) 掌握AWT事件模型的工作 ...
- acwing 50. 序列化二叉树
地址 https://www.acwing.com/problem/content/46/ 请实现两个函数,分别用来序列化和反序列化二叉树. 您需要确保二叉树可以序列化为字符串,并且可以将此字符串反序 ...
- 微服务SpringCloud项目架构搭建入门
Spring的微服务框架SpringCloud受到众多公司欢迎,给大家带来一篇框架搭建入门.本次采用的版本是Spring Cloud版本为Finchley.RELEASE. 一.SpringCloud ...
- Python连载53-UDP、TCP、FTP编程实例
一.服务器程序要求永远运行,一般用死循环来处理 1.服务器改造版本V03(主程序 原封不动,这里只修改了运行的程序) if __name__ == "__main__": whil ...