GVKun编程网logo

Android事件分发机制三:事件分发工作流程(android事件分发与处理)

5

最近很多小伙伴都在问Android事件分发机制三:事件分发工作流程和android事件分发与处理这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展Android事件分发机制、Andr

最近很多小伙伴都在问Android事件分发机制三:事件分发工作流程android事件分发与处理这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展Android 事件分发机制、Android事件分发机制、Android事件分发机制 ViewGroup分析、Android事件分发机制“不详解”等相关知识,下面开始了哦!

本文目录一览:

Android事件分发机制三:事件分发工作流程(android事件分发与处理)

Android事件分发机制三:事件分发工作流程(android事件分发与处理)

前言

很高兴遇见你~

本文是事件分发系列的第三篇。

在前两篇文章中,Android事件分发机制一:事件是如何到达activity的? 分析了事件分发的真正起点:viewRootImpl,Activity只是其中的一个环节;Android事件分发机制二:viewGroup与view对事件的处理 源码解析了viewGroup和view是如何分发事件的。

事件分发的核心内容,则为viewGroup和view对事件的分发,也就是第二篇文章。第二篇文章对源码的分析较为深入,缺乏一个更高的角度来审视事件分发流程。本文在前面的分析基础上,对整个事件分发的工作流程进行一个总结,更好地把握事件是如何在不同的对象和方法之间进行传递。

回顾

先来回顾一下整体的流程,以便更好地定位我们的知识。

  1. 触摸信息从手机触摸屏幕时产生,通过ims和WMS发送到viewRootImpl
  2. viewRootImpl通过调用view的dispatchPointerEvent方法把触摸信息传递给view
  3. view通过调用自身的dispatchTouchEvent方法开始了事件分发

图中的view指的是一个控件树,他可以是一个viewGroup也可以是一个简单的view。因为viewGroup是继承自view,所以一个控件树,也可以看做是一个view。

我们今天探讨的工作流程,就是从图中的view调用自身的dispatchTouchEvent开始。

主要对象与方法

事件分发的对象

这一部分内容在第二篇有详细解析,这里做个简单的回顾。

当我们手机触碰屏幕时会产生一系列的MotionEvent对象,根据触摸的情况不同,这些对象的类型也会不同。具体如下:

  • ACTION_DOWN: 表示手指按下屏幕
  • ACTION_MOVE: 手指在屏幕上滑动时,会产生一系列的MOVE事件
  • ACTION_UP: 手指抬起,离开屏幕、
  • ACTION_CANCEL:当出现异常情况事件序列被中断,会产生该类型事件
  • ACTION_POINTER_DOWN: 当已经有一个手指按下的情况下,另一个手指按下会产生该事件
  • ACTION_POINTER_UP: 多个手指同时按下的情况下,抬起其中一个手指会产生该事件

事件分发的方法

事件分发属于控件系统的一部分,主要的分发对象是viewGroup与view。而其中核心的方法有三个: dispatchTouchEventonInterceptTouchEvent 、 onTouchEvent 。那么在讲分发流程之前,先来介绍一下这三个方法。这三个方法属于view体系的类,其中Window.CallBack接口中包含了 dispatchTouchEvent 和 onTouchEvent 方法,Activity和Dialog都实现了Window.CallBack接口,因此都实现了该方法。因这三个方法经常在自定义view中被重写,以下的分析,如果没有特殊说明都是在默认方法实现的情况下。

dispatchTouchEvent

该方法是事件分发的核心方法,事件分发的逻辑都是在这个方法中实现。该方法存在于类View中,子类ViewGroup、以及其他的实现类如DecorView都重写了该方法。

无论是在viewGroup还是view,该方法的主要作用都是处理事件。如果成功处理则返回true,处理失败则返回false,表示事件没有被处理。具体到类,在viewGroup相关类中,该方法的主要作用是把事件分发到该viewGroup所拥有的子view,如果子view没有处理则自己处理;在view的相关类中,该方法的主要作用是消费触摸事件。

onInterceptTouchEvent

该方法只存在于viewGroup中,当一个事件需要被分发到子view时,viewGroup会调用此方法检查是否要进行拦截。如果拦截则自己处理,而如果不拦截才会调用子view的 dispatchTouchEvent 方法分发事件。

方法返回true表示拦截事件,返回false表示不拦截。

这个方法默认只对鼠标的相关操作的一种特殊情况进行了拦截,其他的情况需要具体的实现类去重写拦截。

onTouchEvent

该方法是消费事件的主要方法,存在于view中,viewGroup默认并没有重写该方法。方法返回true表示消费事件,返回false表示不消费事件。

viewGroup分发事件时,如果没有一个子view消费事件,那么会调用自身的onTouchEvent方法来处理事件。View的dispatchTouchEvent方法中,并不是直接调用onTouchEvent方法来消费事件,而是先调用onTouchListener判断是否消费;如果onTouchListener没有消费事件,才会调用onTouchEvent来处理事件。

我们为view设置的onClickListener与onLongClickListener都是在View的dispatchTouchEvent方法中,根据具体的触摸情况被调用。

重要规则

事件分发有一个很重要的原则:一个触控点的事件序列只能给一个view消费,除非发生异常情况如被viewGroup拦截 。具体到代码实现就是:消费了一个触控点事件序列的down事件的view,将持续消费该触控点事件序列接下来的所有的事件 。举个栗子:

当我手指按下屏幕时产生了一个down事件,只有一个view消费了这个down事件,那么接下来我的手指滑动屏幕产生的move事件会且仅会给这个view消费。而当我手机抬起,再按下时,这时候又会产生新的down事件,那么这个时候就会再一次去寻找消费down事件的view。所以,事件分发,是以事件序列为单位的 。

因此下面的工作流程中都是指down事件的分发 ,而不是ACTION_MOVE或ACTION_UP的分发。因为消费了down事件,意味着接下来的move和up事件都会给这个view处理,也就无所谓分发了。但同时注意事件序列是可以被viewGroup的onInterceptTouchEvent中断的,这些就属于其他的情况了。

细心的读者还会发现事件分发中包含了多点触控。在多点触控的情况下,ACTION_POINTER_DOWN与ACTION_DOWN的分发规则是不同的,具体可前往第二篇文章了解详细。ACTION_POINTER_DOWN在ACTION_DOWN的分发模型上稍作了一些修改而已,后面会详细解析,

工作流程模型

工作流程模型,本质上就是不同的控件对象,viewGroup和view之间事件分发方法的关系。需要注意的是,这里讨论的是viewGroup和view的默认方法实现,不涉及其他实现类如DecorView的重写方法。

下面用一段伪代码来表示三个事件分发方法之间的关系( 这里再次强调,这里的事件分发模型分发的事件类型是ACTION_DOWN且都是默认的方法,没有经过重写,这点很重要 ):

public boolean dispatchTouchEvent(MotionEvent event){

    // 先判断是否拦截
    if (onInterceptTouchEvent()){
        // 如果拦截调用自身的onTouchEvent方法判断是否消费事件
        return onTouchEvent(event);
    }
    // 否则调用子view的分发方法判断是否处理事件
    if (childView.dispatchTouchEvent(event)){
        return true;
    }else{
        return onTouchEvent(event);
    }
}

这段代码非常好的展示了三个方法之间的关系:在viewGroup收到触摸事件时,会先去调用 onInterceptTouchEvent 方法判断是否拦截,如果拦截则调用自己的 onTouchEvent 方法处理事件,否则调用子view的 dispatchTouchEvent 方法来分发事件。因为子view也有可能是一个viewGroup,这样就形成了一个类似递归的关系。

这里我再补上view分发逻辑的简化伪代码:

public boolean dispatchTouchEvent(MotionEvent event){
    // 先判断是否存在onTouchListener且返回值为true
    if (mOnTouchListener!=null && mOnTouchListener.onTouch(event)){
        // 如果成功消费则返回true
        return true;
    }else{
        // 否则调用onTouchEvent消费事件
        return onTouchEvent(event);
    }
}

view与viewGroup不同的是他不需要分发事件,所以也就没有必要拦截事件。view会先检查是否有onTouchListener且返回值是否为true,如果是true则直接返回,否则调用onTouchEvent方法来处理事件。

基于上述的关系,可以得到下面的工作流程图:

这里为了展示类递归关系使用了画了两个viewGroup,只需看中间一个即可,下面对这个图进行解析:

  • viewGroup
    1. viewGroup的dispatchTouchEvent方法接收到事件消息,首先会去调用onInterceptTouchEvent判断是否拦截事件
      • 如果拦截,则调用自身的onTouchEvent方法
      • 如果不拦截则调用子view的dispatchTouchEvent方法
    2. 子view没有消费事件,那么会调用viewGroup本身的onTouchEvent
    3. 上面1、2步的处理结果为viewGroup的dispatchTouchEvent方法的处理结果,并返回给上一层的onTouchEvent处理
  • view
    1. view的dispatchTouchEvent默认情况下会调用onTouchEvent来处理事件,返回true表示消费事件,返回false表示没有消费事件
    2. 第1步的结果就是dispatchTouchEvent方法的处理结果,成功消费则返回true,没有消费则返回false并交给上一层的onTouchEvent处理

可以看到整个工作流程就是一个“U”型结构,在不拦截的情况下,会一层层向下寻找消费事件的view。而如果当前view不处理事件,那么就一层层向上抛,寻找处理的viewGroup。

上述的工作流程模型并不是完整的,还有其他的特殊情况没有考虑。下面讨论几种特殊的情况:

事件序列被中断

我们知道,当一个view接收了down事件之后,该触控点接下来的事件都会被这个view消费。但是,viewGroup是可以在中途掐断事件流的,因为每一个需要分发给子view的事件都需要经过拦截方法:onInterceptTouchEvent (当然,这里不讨论子view设置不拦截标志的情况)。那么当viewGroup掐断事件流之后,事件的走向又是如何的呢?参看下图:(注意,这里不讨论多指操作的情况,仅讨论单指操作的move或up事件被viewGroup拦截的情况

  1. 当viewGroup拦截子view的move或up事件之后,会将当前事件改为cancel事件并发送给子view
  2. 如果当前事件序列还未结束,那些接下来的事件都会交给viewGroup的ouTouchEvent处理
  3. 此时不管是viewGroup还是view的onTouchEvent返回了false,那么将导致整个控件树的dispatchTouchEvent方法返回false
    • 秉承着一个事件序列只能给一个view消费的原则,如果一个view消耗了down事件却在接下来的move或up事件返回了false,那么此事件不会给上层的viewGroup处理,而是直接返回false。

多点触控情况

上面讨论的所有情况,都是不包含多点触控情况的。多点触控的情况,在原有的事件分发流程上,新增了一些特殊情况。这里就不再画图,而是把一些特殊情况描述一下,读者了解一下就可以了。

默认情况下,viewGroup是支持多点触控的分发,但view是不支持多点触控的,需要自己去重写 dispatchTouchEvent 方法来支持多点触控。

多点触控的分发规则如下:

viewGroup在已有view接受了其他触点的down事件的情况下,另一个手指按下产生ACTION_POINTER_DOWN事件传递给viewGroup:

  1. viewGroup会按照ACTION_DOWN的方式去分发ACTION_POINTER_DOWN事件
    • 如果子view消费该事件,那么和单点触控的流程一致
    • 如果子view未消费该事件,那么会交给上一个最后接收down事件的view去处理
  2. viewGroup两个view接收了不同的down事件,那么拦截其中一个view的事件序列,viewGroup不会消费拦截的事件序列。换句话说,viewGroup和其中的view不能同时接收触摸事件。

Activity的事件分发

细心的读者会发现,上述的工作流程并不涉及Activity。我们印象中的事件分发都是 Activity -> Window -> ViewGroup ,那么这是怎么回事?这一切,都是DecorView “惹的祸” 。

DecorView重写viewGroup的 dispatchTouchEvent 方法,当接收到触摸事件后,DecorView会首先把触摸对象传递给内部的callBack对象。没错,这个callBack对象就是Activity。加入Activity这个环节之后,分发的流程如下图所示:

整体上和前面的流程没有多大的不同,Activity继承了Window.CallBack接口,所以也有dispatchTouchEvent和onTouchEvent方法。对上图做个简单的分析:

  1. activity接收到触摸事件之后,会直接把触摸事件分发给viewGroup
  2. 如果viewGroup的dispatchTouchEvent方法返回false,那么会调用Activity的onTouchEvent来处理事件
  3. 第1、2步的处理结果就是activity的dispatchTouchEvent方法的处理结果,并返回给上层

上面的流程不仅适用于Activity,同样适用于Dialog等使用DecorView和callback模式的控件系统。

总结

到这里,事件分发的主要内容也就讲解完了。结合前两篇文章,相信读者对于事件分发有更高的认知。

纸上得来终觉浅,绝知此事要躬行。学了知识之后最重要的就是实践。下一篇文章将简单分析一下如何利用学习到的事件分发知识运用到实际开发中。

最后

在这里我也分享一份由几位大佬一起收录整理的 Flutter进阶资料以及Android学习PDF+架构视频+面试文档+源码笔记 ,并且还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料……

这些都是我闲暇时还会反复翻阅的精品资料。可以有效的帮助大家掌握知识、理解原理。当然你也可以拿去查漏补缺,提升自身的竞争力。
如果你有需要的话,可以前往 GitHub 自行查阅。

原创不易,你的点赞是我创作最大的动力,感谢阅读 ~

Android 事件分发机制

Android 事件分发机制

[TOC]

1、概述

本次分享有一个非常重要的概念:View,虽然说 View 不属于四大组件,但是它的作用堪比四大组件,甚至比 Receiver 和 Provider 的重要性都要大。在 Android 开发中,Activity 承担这可视化的功能,同时 Android 系统提供了很多基础控件,常见的有 Button、TextView、CheckBox 等。很多时候仅仅使用系统提供的控件是不能满足需求的,因此我们就需要能够根据需求进行新控件的定义,而控件的自定义就需要对 Android 的 View 体系有深入的理解,只有这样才能写出完美的自定义控件。同时 Android 手机属于移动设备,移动设备的一个特点就是用户可以直接通过屏幕来进行一系列操作,一个典型的场景就是屏幕的滑动,用户可以通过滑动来切换到不同的界面。很多情况下我们的应用都需要支持滑动操作,当处于不同层级的 View 都可以响应用户的滑动操作时,就会带来一个问题,那就是滑动冲突。如何解决滑动冲突呢?这对于初学者来说的确是个头疼的问题,其实解决滑动冲突本不难,它需要读者对 View 的事件分发机制有一定的了解,在这个基础上,我们就可以利于这个特性从而得出滑动冲突的解决方法。

—— 摘自 Android 开发艺术探索

2、事件分发概述

事件指的是什么呢?就是指用户触摸屏幕产生的 Touch 事件;在 Android 中它被封装成 MotionEvent

3、常用 MotionEvent 分类

  • **ACTION_DOWN:** 按下 View(其他所有事件的开始)。
  • **ACTION_UP:** 抬起 View(与 DOWN 对应)。
  • **ACTION_MOVE:** 滑动 View。
  • **ACTION_CANCEL:** 结束事件(非人为原因)。

其余事件:ACTION_MASKACTION_OUTSIDEACTION_POINTER_DOWNACTION_POINTER_UPACTION_HOVER_MOVEACTION_SCROLLACTION_HOVER_ENTERACTION_HOVER_EXITACTION_BUTTON_PRESSACTION_BUTTON_RELEASEACTION_POINTER_INDEX_MASKACTION_POINTER_INDEX_SHIFT;有兴趣的可以下来自己了解。

3、事件产生顺序

4、Android 事件扭转流程

模拟:

经理分派任务,下属处理这个任务的过程。

  • View 层次示例

  • View 内部事件处理流程图:

5、事件分发流程及其分析

流程图及注释

流程分析

  • 流程1: 在 Activity#dispatchTouchEvent 返回 false/true 当前流程是在Activity#dispatchTouchEvent 拦截并消费事件,不再往下传递

03-26 18:54:58.178 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0

  • 流程2: 在 ViewGroup#dispatchTouchEvent 返回 true: 当前流程是 ViewGroup#dispatchTouchEvent 拦截并消费事件,事件不再往下传递

03-26 19:08:23.268 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:08:23.268 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0

  • 流程3: 在 ViewGroup#dispatchTouchEvent 返回 false: 当前流程是停止往当前 ViewGroup 及其子 View 事件,并将当前事件交由父类 onTouchEvent 处理。

03-26 19:09:15.238 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:09:15.238 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:09:15.238 com.android.api23 I/com.android.api23.MainActivity: onTouchEvent#event:0

  • 流程4: 在 ViewGroup#onInterceptTouchEvent 返回 true:

03-26 19:07:02.918 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:07:02.918 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:07:02.918 com.android.api23 I/com.android.api23.MyViewGroup: onInterceptTouchEvent#event:0 03-26 19:07:02.918 com.android.api23 I/com.android.api23.MyViewGroup: onTouchEvent#event:0 03-26 19:07:02.918 com.android.api23 I/com.android.api23.MainActivity: onTouchEvent#event:0

  • 在 ViewGroup#onInterceptTouchEvent 返回 false 当前流程为系统默认流程即:ViewGroup 不拦截事件,事件将往下一级传递

03-26 19:06:11.008 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:06:11.018 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:06:11.018 com.android.api23 I/com.android.api23.MyViewGroup: onInterceptTouchEvent#event:0 03-26 19:06:11.018 com.android.api23 I/com.android.api23.MyView: dispatchTouchEvent#event:0 03-26 19:06:11.018 com.android.api23 I/com.android.api23.MyView: onTouchEvent#event:0 03-26 19:06:11.018 com.android.api23 I/com.android.api23.MyViewGroup: onTouchEvent#event:0 03-26 19:06:11.018 com.android.api23 I/com.android.api23.MainActivity: onTouchEvent#event:0

  • 流程5: 在 View#dispatchTouchEvent 返回 true: 当前流程是 ViewGroup#dispatchTouchEvent 拦截并消费事件,事件不再往下传递

03-26 19:10:41.048 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:10:41.048 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:10:41.048 com.android.api23 I/com.android.api23.MyViewGroup: onInterceptTouchEvent#event:0 03-26 19:10:41.048 com.android.api23 I/com.android.api23.MyView: dispatchTouchEvent#event:0

  • 流程6: 在 View#dispatchTouchEvent 返回 false:

03-26 19:11:36.458 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:11:36.458 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:11:36.458 com.android.api23 I/com.android.api23.MyViewGroup: onInterceptTouchEvent#event:0 03-26 19:11:36.458 com.android.api23 I/com.android.api23.MyView: dispatchTouchEvent#event:0 03-26 19:11:36.458 com.android.api23 I/com.android.api23.MyViewGroup: onTouchEvent#event:0 03-26 19:11:36.458 com.android.api23 I/com.android.api23.MainActivity: onTouchEvent#event:0

  • 流程7: 在 View#onTouchEvent 返回 true:

03-26 19:12:51.688 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:12:51.688 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:12:51.688 com.android.api23 I/com.android.api23.MyViewGroup: onInterceptTouchEvent#event:0 03-26 19:12:51.688 com.android.api23 I/com.android.api23.MyView: dispatchTouchEvent#event:0 03-26 19:12:51.688 com.android.api23 I/com.android.api23.MyView: onTouchEvent#event:0

  • 在 View#onTouchEvent 返回 false: 当前流程为系统默认流程

03-26 19:13:58.188 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:13:58.188 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:13:58.188 com.android.api23 I/com.android.api23.MyViewGroup: onInterceptTouchEvent#event:0 03-26 19:13:58.188 com.android.api23 I/com.android.api23.MyView: dispatchTouchEvent#event:0 03-26 19:13:58.188 com.android.api23 I/com.android.api23.MyView: onTouchEvent#event:0 03-26 19:13:58.188 com.android.api23 I/com.android.api23.MyViewGroup: onTouchEvent#event:0 03-26 19:13:58.188 com.android.api23 I/com.android.api23.MainActivity: onTouchEvent#event:0

  • 流程8: 在 ViewGroup#onTouchEvent 返回 true:

03-26 19:15:14.608 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:15:14.608 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:15:14.608 com.android.api23 I/com.android.api23.MyViewGroup: onInterceptTouchEvent#event:0 03-26 19:15:14.608 com.android.api23 I/com.android.api23.MyView: dispatchTouchEvent#event:0 03-26 19:15:14.608 com.android.api23 I/com.android.api23.MyView: onTouchEvent#event:0 03-26 19:15:14.608 com.android.api23 I/com.android.api23.MyViewGroup: onTouchEvent#event:0

  • 在 ViewGroup#onTouchEvent 返回 false: 当前流程为系统默认流程

03-26 19:16:48.928 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:16:48.928 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:16:48.928 com.android.api23 I/com.android.api23.MyViewGroup: onInterceptTouchEvent#event:0 03-26 19:16:48.928 com.android.api23 I/com.android.api23.MyView: dispatchTouchEvent#event:0 03-26 19:16:48.928 com.android.api23 I/com.android.api23.MyView: onTouchEvent#event:0 03-26 19:16:48.928 com.android.api23 I/com.android.api23.MyViewGroup: onTouchEvent#event:0 03-26 19:16:48.928 com.android.api23 I/com.android.api23.MainActivity: onTouchEvent#event:0

  • 流程9: 在 Activity#onTouchEvent 返回 true:

03-26 19:18:49.648 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:18:49.648 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:18:49.648 com.android.api23 I/com.android.api23.MyViewGroup: onInterceptTouchEvent#event:0 03-26 19:18:49.648 com.android.api23 I/com.android.api23.MyView: dispatchTouchEvent#event:0 03-26 19:18:49.648 com.android.api23 I/com.android.api23.MyView: onTouchEvent#event:0 03-26 19:18:49.648 com.android.api23 I/com.android.api23.MyViewGroup: onTouchEvent#event:0 03-26 19:18:49.648 com.android.api23 I/com.android.api23.MainActivity: onTouchEvent#event:0

  • 在 Activity#onTouchEvent 返回 false: 当前流程为系统默认流程

03-26 19:20:07.738 com.android.api23 I/com.android.api23.MainActivity: dispatchTouchEvent#event:0 03-26 19:20:07.738 com.android.api23 I/com.android.api23.MyViewGroup: dispatchTouchEvent#event:0 03-26 19:20:07.738 com.android.api23 I/com.android.api23.MyViewGroup: onInterceptTouchEvent#event:0 03-26 19:20:07.738 com.android.api23 I/com.android.api23.MyView: dispatchTouchEvent#event:0 03-26 19:20:07.738 com.android.api23 I/com.android.api23.MyView: onTouchEvent#event:0 03-26 19:20:07.738 com.android.api23 I/com.android.api23.MyViewGroup: onTouchEvent#event:0 03-26 19:20:07.738 com.android.api23 I/com.android.api23.MainActivity: onTouchEvent#event:0

AndroidApi23:Android 事件分发机制测试 Demo

Android事件分发机制

Android事件分发机制

Android事件分发的本质

将点击事件(MotionEvent)传递到某个具体的View并且处理的整个过程

Android事件分发的过程由哪些方法协作完成

dispatchTouchEvent()
onInterceptTouchEvent()
onTouchEvent()

Android事件分发的顺序

Activity–> ViewGroup–>View
需要注意的是:onTouch()执行总是优先于onClick()

Activity中的事件分发

每一个Activity 都持有一个Window对象(getWindow()获取)
而Window是一个抽象类,唯一的实现是PhoneView
PhoneView持有一个DecorView实例继承自FrameLayout,FrameLayout继承自ViewGroup
所以说Activity持有的最顶层的view为DecorView


DecorView中调用的父类的**dispatchTouchEvent()**方法,具体的方法实现位于ViewGroup,至此Activity传出的触摸事件被分发到ViewGroup。
Activity中的事件分发

ViewGroup中的事件分发

  • 首先判断是否需要拦截事件

判断安全策略:view是否在顶部 是否被遮挡 是否设置不在顶部不响应点击事件
判断触摸事件 :ACTION_DOWN

  • 若不拦截则在当前的viewgroup中找到用户点击的View(遍历View子树)
  • 通过**dispatchTouchEvent()**将事件分发给view
    Viewgroup中的事件分发

View中的事件分发

View中的事件分发

同一个事件序列,如果子View(ViewGroup)第一次没有处理(消费)该事件,则后续事件就不会再传递到子View中
事件默认传递流程在**onTouchEvent()**方法中 return true代表消费该事件,retrun false 代表事件未消费 则事件继续向下传递

Android事件分发机制 ViewGroup分析

Android事件分发机制 ViewGroup分析

前言:

事件分发从手指触摸屏幕开始,即产生了触摸信息,被底层系统捕获后会传递给Android的输入系统服务IMS,通过Binder把消息发送到activity,activity会通过phoneWindow、DecorView最终发送给ViewGroup。这里就直接分析ViewGroup的事件分发

整体流程

配合图在看一段伪代码:

public boolean dispatchTouchEvent(MotionEvent ev) :Boolean{
    val result = false  //处理结果,默认是没消费过的

    if (!onInterceptTouchEvent(ev)){ //是否拦截
        result = child.dispatchTouchEvent(ev) // 分发给子view处理
    }

    if (!result){ //事件没有消费
        if (onTouchListener != null) { //先询问是否设置了onTouchListener
            result = onTouchListener.onTouch(ev)
        }
        if (!result) { //还是没有消费就交给onTouchEvent处理
            result = onTouchEvent(ev)
        }
    }

    return result
}

这张图和这段伪代码实际上已经概括了ViewGroup和View对事件处理的整个流程,注意只有ViewGroup有拦截机制即onInterceptTouchEvent

源码分析

在分析源码之前先了解个基本概念 同一事件序列:同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束

public boolean dispatchTouchEvent(MotionEvent ev) {

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        /**
         * step1
         * ACTION_DOWN是一个系列事件的起点,终点是ACTION_UP
         * 如果是ACTION_DOWN会重置一些flag并且会把mFirstTouchTarget置空
         */
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        final boolean intercepted;//变量判断消息是否被拦截
        /**
         * step2
         * 从以下代码可以看出如果事件不是ACTION_DOWN并且mFirstTouchTarget为空的话那么ViewGroup是不能再拦截同一系列的事件了
         * mFirstTouchTarget 代表的就是一个单链表,它会把处理当前这一系列事件的view保存下来
         * 假如当前事件是ACTION_MOVE,并拦截了该事件那么会在step9中把mFirstTouchTarget置空
         *
         * 结论1:
         * 如果View决定拦截一个事件那么该View的 onInterceptTouchEvent 方法不会再被调用了,
         * 同一序列事件后续的所有事件都只能由该View处理(当然前提是事件能分发到该view,有可能在上层被拦截了)
         */
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            /**
             * disallowIntercept表示是否禁用拦截功能,子view通过 requestDisallowInterceptTouchEvent 方法
             * 可以要求父view不准拦截事件,不过该方法在MotionEvent.ACTION_DOWN事件中不起作用,因为在step1中会把所有标志位重置
             *
             */
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            /**
             * 如果进不到上面的if判断则表示当前系列事件viewGroup已经拦截过某个事件了
             * intercepted 直接置为true
             */
            intercepted = true;
        }


        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
                && !isMouseEvent;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        /**
         * step3
         * 看这里如果ViewGroup拦截了该事件则不会进入step3里面了,而是直接走到step9中
         */
        if (!canceled && !intercepted) {
            /**
             * step4
             * 这里我们只考虑单指的点击、移动和抬起
             * ACTION_POINTER_DOWN和多点触控有关,ACTION_HOVER_MOVE和鼠标有关
             * 所以如果当前事件是MOVE也不会走step4也是直接走到step9中找到对应的子view继而分发事件
             * 结论2:如果DOWN事件被某个view消耗那么后续的事件都会直接交给这个view(前提是父view没有拦截)
             */
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x =
                            isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    final float y =
                            isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    /**
                     * step5
                     * 遍历所有的子view
                     */
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        //省略部分代码。。。
                        /**
                         * step6
                         * 当找到一个合适的子view时,在 dispatchTransformedTouchEvent 中会调用子view的dispatchTouchEvent
                         * 如果该子view消耗了事件,会把子view保存到mFirstTouchTarget对应的链表中,并结束for循环
                         *
                         * 结论3:
                         * 如果一个view没有消耗DOWN事件那么后续的事件都不会再分发给该view
                         * 该结论和结论2呼应上了,因为在这个for循环中只有子view的 dispatchTransformedTouchEvent返回true才会被加入到链表中
                         * 下一次的事件并不会再到step4中来了
                         */
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // Child wants to receive touch within its bounds.
                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
                                // childIndex points into presorted list, find original index
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            /**
                             * step7
                             * 把子view保存到链表中,mFirstTouchTarget指向表头
                             * alreadyDispatchedToNewTouchTarget置为true
                             * 结束for循环
                             */
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

                        // The accessibility focus didn''t handle the event, so clear
                        // the flag and do a normal dispatch to all children.
                        ev.setTargetAccessibilityFocus(false);
                    }
                }
            }
        }

        /**
         * step8
         * 如果拦截了事件会把 mFirstTouchTarget 置空这个时候就直接调用viewGroup的super.dispatchTouchEvent
         * 即view中的dispatchTouchEvent
         */
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // Dispatch to touch targets, excluding the new touch target if we already
            // dispatched to it.  Cancel touch targets if necessary.
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            /**
             * step9
             * 如果拦截了就把mFirstTouchTarget置空,没有拦截就找到对应的childView把事件分发下去
             */
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    //注意这里cancelChild如果为true,并且target.child不为空的话,dispatchTransformedTouchEvent会把事件转成CANCEL分发给target.child
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

    }

    return handled;
}

看下cancel事件的由来,这里需要结合上文代码step9看

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                              View child, int desiredPointerIdBits) {
    final boolean handled;
    
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        /**
         * 把事件转换成ACTION_CANCEL
         */
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            /**
             * 如果child不为空就分发给它
             */
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    
    return handled;
}

到此这篇关于Android事件分发机制 ViewGroup分析的文章就介绍到这了,更多相关Android  ViewGroup内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

您可能感兴趣的文章:
  • 一篇文章弄懂Android自定义viewgroup的相关难点
  • Android自定义ViewGroup实现淘宝商品详情页
  • Android自定义ViewGroup实现竖向引导界面
  • Android进阶教程之ViewGroup自定义布局
  • Android自定义ViewGroup实现流式布局
  • Android自定义ViewGroup实现朋友圈九宫格控件
  • Android自定义ViewGroup多行多列效果

Android事件分发机制“不详解”

Android事件分发机制“不详解”

话说:只要掌握了事件分发机制,就能找到好工作、升职加薪、当上总经理、出任ceo、迎娶白富美。
哈哈哈,不啰嗦,让我们开始。

三个重要的方法

和事件分发机制密切相关的三个方法分别是:

  • public boolean dispatchTouchEvent(MotionEvent ev)

用来进行事件的分发。如果事件能够传递到当前View,此方法一定会被调用,返回结果受当前View的onTouchEvent()和下级View的diapatchTouchEvent()方法的影响,表示是否消耗当前事件

  • public boolean onInterceptTouchEvent(MotionEvent event)

用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件

  • public boolean onTouchEvent(MotionEvent event)

dispatchTouchEvent()中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收事件。

三个方法之间的联系:

public boolean dispatchTouchEvent(MotionEvent event) {
  boolean consume = false;
  if(onInterceptTouchEvent(ev)) {
    consume = onTouchEvent(ev);
  } else {
    consume = child.dispatchTouchEvent(ev);
  }
  return consume;
}

通过上面的伪代码,我们大致可以了解点击事件的传递规则:对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这是它的dispatchTouchEvent()就会被调用,如果这个ViewGroup的onInterceptTouchEvent()方法返回true就表示它要拦截当前事件,接着事件就会交给该ViewGroup处理,即执行onTouchEvent()方法。如果onInterceptTouchEvent()方法返回false就表示它不拦截该当前事件,这时事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent()就会被调用,如此反复,直到事件被处理。

一些结论(摘抄自《Android开发艺术探索》)

  1. 同一个事件序列是指从手指触摸屏幕的那一刻起,到手指离开屏幕那一刻结束,在这个过程中产生的一系列事件,这一系列事件以down事件开始,以up事件结束,中间可能有一些move事件。

  2. 正常情况下,一个事件序列只能被一个View拦截并消耗。

  3. 某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent()不会再被调用。

  4. 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent()返回了false),那么同一事件序列中的其他事件都不会再交给它处理,并且事件将重新交由它的父元素去处理,及父元素的onTouchEvent()会被调用。

  5. 如果View不消耗ACTON_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent()并不会被调用,并且当前View可以持续收到后续的事件。最终这些消失的点击事件会传递给Activity处理。

  6. ViewGroup默认不拦截事件。onInterceptTouchEvent()方法默认返回false。

  7. View没有onInterceptTouchEvent()方法,一旦有事件传递给它,那么它的onTouchEvent()方法就会被调用。

  8. View的onTouchevent()默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。

  9. View的enable属性不影响onTouchEvent()的默认返回值。哪怕一个View是disable状态的,只要它的clickable和longClickable有一个为true,那么它的onTouchEvent()就返回true。

  10. onClick会发生的前提是当前View是可点击的,并且它收到了down和up的事件。

  11. 事件传递的方向是由外向内的,即事件总是先传递给父元素,然后由父元素分发给子View。

  12. 当面对ACTION_DOWN事件时,ViewGroup总是会调用自己的onInterceptTouchEvent()方法来询问自己是否要拦截该事件。

  13. 当ACTION_UP方法发生时,会触发performClick()方法,如果View设置了onClickListener,那么performClick()方法内部会调用它的onClick()方法。

流程图

为了把事件分发机制研究明白,我画了一张流程图。

流程图地址

就到这里了。
如果有什么问题,大家可以在评论中提出来,我们一起讨论。
加油,各位!

本文来自:http://qiaoyunrui.github.io/2016/05/23/androidDispatchEvent/

今天关于Android事件分发机制三:事件分发工作流程android事件分发与处理的分享就到这里,希望大家有所收获,若想了解更多关于Android 事件分发机制、Android事件分发机制、Android事件分发机制 ViewGroup分析、Android事件分发机制“不详解”等相关知识,可以在本站进行查询。

本文标签: