事件分发是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事件分发的更多相关文章

  1. Android 进阶学习:事件分发机制全然解析,带你从源代码的角度彻底理解(上)

    http://blog.csdn.net/guolin_blog/article/details/9097463 事实上我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客開始,就零 ...

  2. Android事件分发机制二:viewGroup与view对事件的处理

    前言 很高兴遇见你~ 在上一篇文章 Android事件分发机制一:事件是如何到达activity的? 中,我们讨论了触摸信息从屏幕产生到发送给具体 的view处理的整体流程,这里先来简单回顾一下: 触 ...

  3. Android事件分发机制三:事件分发工作流程

    前言 很高兴遇见你~ 本文是事件分发系列的第三篇. 在前两篇文章中,Android事件分发机制一:事件是如何到达activity的? 分析了事件分发的真正起点:viewRootImpl,Activit ...

  4. Android事件分发机制五:面试官你坐啊

    前言 很高兴遇见你~ 事件分发系列文章已经到最后一篇了,先来回顾一下前面四篇,也当个目录: Android事件分发机制一:事件是如何到达activity的? : 从window机制出发分析了事件分发的 ...

  5. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  6. 【朝花夕拾】Android自定义View篇之(七)Android事件分发机制(下)滑动冲突解决方案总结

    前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/11072989.html],谢谢! 前面两篇文章,花了很大篇幅讲解了Android的事件分发机制 ...

  7. Android事件分发机制四:学了事件分发有什么用?

    " 学了事件分发,影响我CV大法吗?" " 影响我陪女朋友的时间" " ..... " 前言 Android事件分发机制已经来到第四篇了,在 ...

  8. Android事件分发机制浅谈(一)

    ---恢复内容开始--- 一.是什么 我们首先要了解什么是事件分发,通俗的讲就是,当一个触摸事件发生的时候,从一个窗口到一个视图,再到一个视图,直至被消费的过程. 二.做什么 在深入学习android ...

  9. Android事件分发机制(下)

    这篇文章继续讨论Android事件分发机制,首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子 ...

随机推荐

  1. 随机的标识符GUID

    Guid guid = Guid.NewGuid();Console.WriteLine(guid.ToString());

  2. C# Serialization performance in System.Runtime.Serialization.Formatters.Binary.BinaryFormatter,Newtonsoft.Json.JsonConvert and System.Text.Json.JsonSerializer.Serialize

    In .net core 3.0 using System;using System.Collections.Generic;using System.Collections;using System ...

  3. Web前端基础(17):jQuery基础(四)

    1. jQuery的属性操作 jquery的属性操作模块分为四个部分:html属性操作,dom属性操作,类样式操作和值操作 html属性操作:是对html文档中的属性进行读取,设置和移除操作.比如at ...

  4. C#中实现文件重命名的方式

    场景 在C#中如果是删除文件的话可以直接使用 if (System.IO.File.Exists(fileName)) { System.IO.File.Delete(fileName); } 但是如 ...

  5. 数据处理之以OLEDB方式读取Excel数据丢失的原因及解决方法

    1.引言 在应用程序的设计中,经常需要读取Excel数据或将Excel数据导入转换到其他数据载体中,C#读取Excel的方式有两种,一种是通过OLEDB方式读取,另一种为通过COM组件方式读取.近段时 ...

  6. JAVA微信企业付款到零钱(十分钟搞定),附完整DEMO下载

    最近帮朋友做了一个简单的微分销系统,实现从企业付款到零钱分润的功能,简单记录一下微信企业付款到零钱的开发过程, 主要就是按规则封装好请求参数调用微信接口,涉及一些签名校验: A.接口流程 1. 获取用 ...

  7. ThinkPHP6框架的下载与安装

    thinkphp6发布也有一段时间了,相对来说比较稳定,是时候学习一下thinkphp6框架,提前学习,到正式发布的时候,可以直接拿来做正式的项目,先人一步.thinkPHP6.0在5.1的基础上对底 ...

  8. Flutter学习一

    包管理 依赖本地包 dependencies: pkg1: path: ../../code/pkg1 依赖git包 dependencies: pkg1: git: url: git://githu ...

  9. Resolving RMAN-06023 or RMAN-06025 (Doc ID 2038119.1)

    Resolving RMAN-06023 or RMAN-06025 (Doc ID 2038119.1) APPLIES TO: Oracle Database - Enterprise Editi ...

  10. P4762 [CERC2014]Virus synthesis

    题意 真是道回文自动机好题. 首先考虑答案必定是一个回文串+剩余部分的形式,因此可以建出回文自动机,之后考虑每个长度为偶数的回文串. 对于一个长度为偶数的回文串,设它在回文自动机上对应的节点为\(x\ ...