Android 事件分发详解及示例代码_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来绘制一张事件传递的流程图

 

现在来理一下事件序列的流程:

  1. ACTION_DOWN事件从Activity#dispatchTouchEvent方法开始
  2. ACTION_DOWN事件传递至ViewGroup#dispatchTouchEvent方法,ViewGroup#onInterceptTouchEvent返回false,表示不拦截ACTION_DOWN
  3. ACTION_DOWN事件传递到View#dispatchTouchEvent方法,在View#onTouchEvent进行执行,返回true,表示事件已经被消费
  4. 返回的结果true,被回传到View#dispatchTouchEvent,之后回传到ACTION_DOWN事件的起点Activity#dispatchTouchEvent方法
  5. 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)

项目经理完成的不错,老板决定把这个项目的二期、三期等都交给项目经理,同样项目经理也觉得这个软件工程师完成的不错,所以也把二期、三期等都交给这个工程师来做
通过上面的传递过程,我们可以得出一些结论:

  1. 某个ViewGroup如果onInterceptTouchEvent返回为false,则表示ViewGroup不拦截事件,而是将其传递给View#dispatchTouchEvent方法
  2. 事件总是由父元素分发给子元素
  3. 某个View如果onTouchEvent返回true,表示事件被消费,则其结果将直接通过dispatchTouchEvent方法传递回Activity
  4. 如果某个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事件传递的流程图

 

 

我们来整理下这个事件序列的流程:

  1. ACTION_DOWN事件的传递与之前相同,不同的地方在于,返回值的传递
  2. 因为不可点击,View#onTouchEvent返回值为false,将其传递给自己的dispatchTouchEvent方法,之后传递到ViewGroup#dispatchTouchEvent方法,再传递到ViewGroup#onTouchEvent方法
  3. ViewGroup返回false之后,ACTION_DOWN事件交由Activity#onTouchEvent方法进行处理,然而依旧返回false,最后ACTION_DOWN事件的返回结果即为false
  4. ACTION_UP事件在发现View、ViewGroup并不处理ACTION_DOWN事件后,直接将其传递给了Activity#onTouch方法处理,处理返回false,ACTION_UP事件的返回结果即为false

这里使用工作中的情况来模拟:依旧是老板(Activity)、项目经理(ViewGroup)、软件工程师(View) 从老板交任务给项目经理,项目经理交任务给工程师,这一段流程和之前的例子相同。不同之处是软件工程师没有完成这个任务(View#onTouchEvent返回false),告诉项目经理我没有完成,然后项目经理自己进行了尝试,同样没有完成(ViewGroup#onTouchEvent返回false),项目经理告诉了老板,我没有完成,然后老板自己试了下也没有完成这个任务(Activity#onTouchEvent返回false),但之后的也有项目的二期、三期,不过老板知道你们完成不了,所以都是他自己进行尝试,不过很惨都没完成。(这段有点与正常情况不同,不过只是打个比方)

通过结合上面两个例子,可以得出一些结论:

  1. 某个View如果onTouchEvent返回false,表示事件没有被消费,则事件将传递给其父View的onTouchEvent进行处理
  2. 某个View如果它不消耗ACTION_DOWN事件,那么这个序列的后续事件也不会再交由它来处理
  3. 如果事件没有View对其进行处理,那么最后将有Activity进行处理
  4. 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的事件分发机制进行,得出如下结论。

  1. 一个事件序列是指从手指触摸屏幕开始,到手指离开屏幕结束,这个过程中产生的一系列事件。一个事件序列以ACTION_DOWN事件开始,中间可能经过若干个MOVE,以ACTION_UP事件结束。
  2. 事件的传递过程是由外向内的,即事件总是由父元素分发给子元素
  3. 如果某个View消费了ACTION_DOWN事件,那么通常情况下,这个事件序列中的后续事件也将交由其进行处理,但可以通过调用其父View的onInterceptTouchEvent方法,对后续事件进行拦截
  4. 如果某个View它不消耗ACTION_DOWN事件,那么这个序列的后续事件也不会再交由它来处理
  5. 如果事件没有View对其进行处理,那么最后将有Activity进行处理
  6. 如果事件传递的结果为true,回传的结果直接通过不断调用父View#dispatchTouchEvent方法,传递给Activity;如果事件传递的结果为false,回传的结果不断调用父View#onTouchEvent方法,获取返回结果。
  7. View默认的onTouchEvent在View可点击的情况下,将会消耗事件,返回true;不可点击的情况下,则不消耗事件,返回false(longClickable的情况,读者可以自行测试,结果与clickable相同)
  8. 如果某个ViewGroup的onInterceptTouchEvent返回为true,那么这个事件序列中的后续事件,不会在进行onInterceptTouchEvent的判断,而是由它的dispatchTouchEvent方法直接传递给onTouchEvent方法进行处理
  9. 如果某个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
, 事件分发
事件分发详解
android事件分发机制、android事件分发、android view事件分发、android 事件分发拦截、android 按键事件分发,以便于您获取更多的相关知识。

时间: 2024-11-01 14:47:33

Android 事件分发详解及示例代码_Android的相关文章

Android ListView组件详解及示例代码_Android

Android 列表组件 ListView 列表组件是开发中经常用到组件,使用该组件在使用时需要为它提供适配器,由适配器提供来确定显示样式和显示数据. 下面看一个例子: 新建一个项目Lesson8_ListViewTest,Activity name是MainListViewTest . MainListViewTest.java的代码是: package android.basic.lesson8; import android.app.Activity; import android.os.

Android Content Provider详解及示例代码_Android

Android:Content Provider的使用. 1.Content Provider 简介 2.使用现成的Content Provider 3.定义自己的Content Provider 一.Content Provider 简介 我们说Android应用程序的四个核心组件是:Activity.Service.Broadcast Receiver 和 Content Provider.在Android中,应用程序彼此之间相互独立的,它们都运行在自己独立的虚拟机中.Content Pro

Android Dialog 对话框详解及示例代码_Android

Android Dialog 对话框 1.Dialog介绍 2.AlertDialog的基本使用 3.自定义对话框 Custom Dialog 一.Dialog介绍 Dialog也是Android中常用的用户界面元素,他同Menu一样也不是View的子类.让我们看一下它的继承关系: 这里要留意一下他的直接子类 AlertDialog,和间接子类 DatePickerDialog,ProgressDialog,TimePickerDialog,其中后三个我们在前面的章节已经讲过,今天我们把重点放在

Android事件分发详解(五)——Touch事件传递验证

MainActivity如下: package cn.c; import android.os.Bundle; import android.app.Activity; import android.view.MotionEvent; /** * Demo描述: * 分析Android事件分发和处理机制 * * * 总结: * 1 ViewGroup继承自View * 事件的传递方向为:从最外层(Activity)传递至最内层(某个View) * 事件的消费方向为:从最内层(某个View)传递至

Android 进度条使用详解及示例代码_Android

在这里,总结一下loading进度条的使用简单总结一下. 一.说起进度条,必须说说条形进度条,经常都会使用到嘛,特别是下载文件进度等等,还有像腾讯QQ安装进度条一样,有个进度总给人良好的用户体验. 先来找图看看,做这个图完成不用图片就可以做到了. 看下xml布局文件,其实就是直接用xml写的在加两个属性设置一下就好了,一个style,另一个是background. <ProgressBar android:id="@+id/pb_progressbar" style="

Android Service详解及示例代码_Android

Android Service 详细介绍: 1.Service的概念 2.Service的生命周期 3.实例:控制音乐播放的Service 一.Service的概念 Service是Android程序中四大基础组件之一,它和Activity一样都是Context的子类,只不过它没有UI界面,是在后台运行的组件. 二.Service的生命周期 Service对象不能自己启动,需要通过某个Activity.Service或者其他Context对象来启动.启动的方法有两种,Context.startS

Android SQLite详解及示例代码_Android

在Android中使用SQLite数据库的入门指南,打算分下面几部分与大家一起分享, 1.什么是SQLite 2.Android中使用SQLite 一.什么是SQLite SQLite是一款开源的.轻量级的.嵌入式的.关系型数据库.它在2000年由D. Richard Hipp发布,可以支援Java.Net.PHP.Ruby.Python.Perl.C等几乎所有的现代编程语言,支持Windows.Linux.Unix.Mac OS.Android.IOS等几乎所有的主流操作系统平台. SQLit

Android事件分发详解(六)——ACTION_DOWN的消费验证

MainActivity如下: package cn.c; import android.os.Bundle; import android.app.Activity; import android.view.MotionEvent; /** * Demo描述: * 分析Android事件分发和处理机制 * * 在该示例中涉及到三个自定义的View.分别是: * 最外层的布局MyFrameLayout * 内层的布局MyLinearLayout * 最里层的自定义按钮MyButton * * 在

Android 文件选择器详解及实例代码_Android

     本文给大家讲解下Android文件选择器的使用.实际上就是获取用户在SD卡中选择的文件或文件夹的路径,这很像C#中的OpenFileDialog控件.        此实例的实现过程很简单,这样可以让大家快速的熟悉Android文件选择器,提高开发效率.        网上曾经见到过一个关于文件选择器的实例,很多人都看过,本实例是根据它修改而成的,但更容易理解,效率也更高,另外,本实例有自己的特点:        1.监听了用户按下Back键的事件,使其返回上一层目录.