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 事件分发的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!

(0)

相关推荐

  • android事件分发机制的实现原理

    android中的事件处理,以及解决滑动冲突问题都离不开事件分发机制,android中的事件流,即MotionEvent都会经历一个从分发,拦截到处理的一个过程.即dispatchTouchEvent(),onInterceptEvent()到onTouchEvent()的一个过程,在dispatchTouchEvent()负责了事件的分发过程,在dispatchTouchEvent()中会调用onInterceptEvent()与onTouchEvent(),如果onInterceptEven

  • Android Touch事件分发深入了解

    本文带着大家深入学习触摸事件的分发,具体内容如下 1. 触摸动作及事件序列 (1)触摸事件的动作 触摸动作一共有三种:ACTION_DOWN.ACTION_MOVE.ACTION_UP.当用户手指接触屏幕时,便产生一个动作为ACTION_DOWN的触摸事件,此时若用户的手指立即离开屏幕,会产生一个动作为ACTION_UP的触摸事件:若用户手指接触屏幕后继续滑动,当滑动距离超过了系统中预定义的距离常数,则产生一个动作为ACTION_MOVE的触摸事件,系统中预定义的用来判断用户手指在屏幕上的滑动是

  • Android View事件分发机制详解

    准备了一阵子,一直想写一篇事件分发的文章总结一下,这个知识点实在是太重要了. 一个应用的布局是丰富的,有TextView,ImageView,Button等,这些子View的外层还有ViewGroup,如RelativeLayout,LinearLayout.作为一个开发者,我们会思考,当点击一个按钮,Android系统是怎样确定我点的就是按钮而不是TextView的?然后还正确的响应了按钮的点击事件.内部经过了一系列什么过程呢? 先铺垫一些知识能更加清晰的理解事件分发机制: 1. 通过setC

  • 谈谈对Android View事件分发机制的理解

    最近因为项目中用到类似一个LinearLayout中水平布局中,有一个TextView和Button,然后对该LinearLayout布局设置点击事件,点击TextView能够触发该点击事件,然而奇怪的是点击Button却不能触发.然后google到了解决办法(重写Button,然后重写其中的ontouchEvent方法,且返回值为false),但是不知道原因,这两天看了几位大神的博客,然后自己总结下. public class MyButton extends Button { private

  • Android事件分发机制的详解

    Android事件分发机制 我们只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL.一个手势(gesture)是一个事件列,以一个DOWN事件开始(当用户触摸屏幕时产生),后跟0个或多个MOVE事件(当用户四处移动手指时产生),最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)由于其他原因结束时产生).当我们说到"手势剩余部分"时指的是手势后续的MOVE事件和最后的UP或CANCEL事件. 在这里我也不考虑多点触摸手势(我

  • Android View的事件分发机制

    一.Android View框架提供了3个对事件的主要操作概念. 1.事件的分发机制,dispatchTouchEvent.主要是parent根据触摸事件的产生位置,以及child是否愿意负责处理该系列事件等状态,向其child分发事件的机制. 2.事件的拦截机制,onInterceptTouchEvent.主要是parent根据它内部的状态.或者child的状态,来把事件拦截下来,阻止其进一步传递到child的机制. 3.事件的处理机制,onTouchEvent.主要是事件序列的接受者(可以是

  • Android事件分发机制(上) ViewGroup的事件分发

    综述 Android中的事件分发机制也就是View与ViewGroup的对事件的分发与处理.在ViewGroup的内部包含了许多View,而ViewGroup继承自View,所以ViewGroup本身也是一个View.对于事件可以通过ViewGroup下发到它的子View并交由子View进行处理,而ViewGroup本身也能够对事件做出处理.下面就来详细分析一下ViewGroup对时间的分发处理. MotionEvent 当手指接触到屏幕以后,所产生的一系列的事件中,都是由以下三种事件类型组成.

  • Android View 事件分发机制详解

    Android开发,触控无处不在.对于一些 不咋看源码的同学来说,多少对这块都会有一些疑惑.View事件的分发机制,不仅在做业务需求中会碰到这些问题,在一些面试笔试题中也常有人问,可谓是老生常谈了.我以前也看过很多人写的这方面的文章,不是说的太啰嗦就是太模糊,还有一些在细节上写的也有争议,故再次重新整理一下这块内容,十分钟让你搞明白View事件的分发机制. 说白了这些触控的事件分发机制就是弄清楚三个方法,dispatchTouchEvent(),OnInterceptTouchEvent(),o

  • Android事件分发机制(下) View的事件处理

    综述 在上篇文章Android中的事件分发机制(上)--ViewGroup的事件分发中,对ViewGroup的事件分发进行了详细的分析.在文章的最后ViewGroup的dispatchTouchEvent方法调用dispatchTransformedTouchEvent方法成功将事件传递给ViewGroup的子View.并交由子View进行处理.那么现在就来分析一下子View接收到事件以后是如何处理的. View的事件处理 对于这里描述的View,它是ViewGroup的父类,并不包含任何的子元

  • Android View事件分发和消费源码简单理解

    Android View事件分发和消费源码简单理解 前言: 开发过程中觉得View事件这块是特别烧脑的,看了好久,才自认为看明白.中间上网查了下singwhatiwanna粉丝的读书笔记,有种茅塞顿开的感觉. 很重要的学习方法:化繁为简,只抓重点. 源码一坨,不要指望每一行代码都看懂.首先是没必要,其次大量非关键代码会让你模糊真正重要的部分. 以下也只是学姐的学习成果,各位同学要想理解深刻,还需要自己亲自去看源码. 2.源码分析 由于源码实在太长,而且也不容易看懂,学姐这里就不贴出来了,因为没必

随机推荐