Android事件分发机制源码和实例解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

1.事件分发过程的理解

1.1. 概述

1.2. 主要方法

1.3. 核心行为

1.4. 特殊情况

2.案例分析

2.1. 案例1:均不消费 down 事件

2.2. 案例2:View0 消费 down 事件

2.3. 案例3:ViewGroup2nd 消费 down 事件

3.down 事件分发图

1. 事件分发过程的理解

1.1. 概述

事件主要有 down(MotionEvent.ACTION_DOWN),move(MotionEvent.ACTION_MOVE),up(MotionEvent.ACTION_UP)。

基本上的手势均由 down 事件为起点,up 事件为终点,中间可能会有一定数量的move 事件。这三种事件是大部分手势动作的基础。

事件和相关信息(比如坐标)封装成 MotionEvent。

大体的分发过程为:首先传递到 Activity,然后传给了 Activity 依附的 Window,接着由 Window 传给视图的顶层 View 也就是 DecorView,最后由 DecorView 向整个 ViewTree 分发。分发还会有回溯的过程。最后还会回到 Activity 的调用中。

Activity 的分发事件源码


  
  
  1. public boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) { 
  2.         onUserInteraction(); 
  3.     }    if (getWindow().superDispatchTouchEvent(ev)) {        return true
  4.     }    return onTouchEvent(ev); 

在 getWindow().superDispathTouchEvent 就是用来分发事件到 DecorView 中。如果整个 ViewTree 没有消费事件,会调用 Activity 的 onTouchEvent。

1.2. 主要方法

1.2.1. 概览

主要涉及到的 View 或 ViewGroup 的方法有:

dispatchTouchEvent,该方法封装了事件分发的整个过程。是事件分发的 调度者 和 指挥官 。的核心过程均在该方法中。下面的 onInterceptTouchEvent 和 onTouchEvent 的回调的调用就在该方法体中。是否传递事件到

onInterceptTouchEvent 和 onTouchEvent 由 dispatchTouchEvent 决定。

onInterceptTouchEvent,该方法决定了是否拦截事件。只有 ViewGroup 有该回调。返回 true 表示拦截,返回 false 表示不拦截。自定义 View 的时候,可以重载该方法,通过一些特定的逻辑来决定是否拦截事件。如果拦截,接下来会调用该 ViewGroup 的 onTouchEvent 来处理事件。

onTouchEvent,该方法处理了事件,并决定是否继续消费后续事件。该方法调用的前置条件:

  • 该 View 拦截了事件
  • 子 View 都不消费事件
  • 没有子 View

该方法正式处理 MotionEvent。返回 true 表示消费,返回 false 不消费。如果消费,接下来的事件还会传递到该 View 的 dispatchTouchEvent 中;如果不消费,后面的事件不会再传过来。

onTouchListener 的 onTouch 回调,和 onTouchEvent 一样,优先级比 onTouchEvent 高,如果有设置该监听,并且 onTouch 返回 true,就不会再调用 onTouchEvent 了。如果返回 false,事件还是会传递到 onTouchEvent 中。

1.2.2. dispatchTouchEvent 方法中的一些细节处理:

大部分手势的起点为 down 事件,dispatchTouchEvent 如果收到 down 事件,会重新设置一些变量和标记

重置变量和标记的源码


  
  
  1. // Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) {    // Throw away all previous state when starting a new touch gesture. 
  2.     // The framework may have dropped the up or cancel event for the previous gesture 
  3.     // due to an app switch, ANR, or some other state change. 
  4.     cancelAndClearTouchTargets(ev); 
  5.     resetTouchState(); 

实际的源码中,ViewGroup 继承于 View。 当子 View 不消费事件或者 ViewGroup 拦截了事件会传空值到 dispatchTransformedTouchEvent 中,内部会调用 super.dispatchTouchEvent,最终把事件传给 onTouchEvent 进行处理。

dispatchTransformedTouchEvent 关键部分


  
  
  1. // Perform any necessary transformations and dispatch.if (child == null) { 
  2.     handled = super.dispatchTouchEvent(transformedEvent); 
  3. else {    final float offsetX = mScrollX - child.mLeft;    final float offsetY = mScrollY - child.mTop; 
  4.     transformedEvent.offsetLocation(offsetX, offsetY);    if (! child.hasIdentityMatrix()) { 
  5.         transformedEvent.transform(child.getInverseMatrix()); 
  6.     } 
  7.  
  8.     handled = child.dispatchTouchEvent(transformedEvent); 

也就是 dispatchTransformTouchEvent 完成了分发的最后过程:

a. 传入的 child 不为空,转化坐标为 child 的坐标系,调用 child.dispatchTouchEvent向 child 分发事件

b. 传入的 child 为空,调用 super.dispatchTouchEvent 分发事件到 onTouchEvent 中

1.2.3 方法的主要关系

对于一个 ViewGroup 来说,几个重要方法的关系如下

几个重要方法关系伪代码


  
  
  1. public boolean dispatchTouchEvent(MotionEvent e) {    boolean consumed = false;    if (onInterceptTouchEvent(e)) { 
  2.         consumed = onTouchEvent(e); 
  3.     } else {        for (View view: childs) { 
  4.             consumed = view.dispatchTouchEvent(e);            if (consumed) {                break; 
  5.             } 
  6.         }        if (!consumed) { 
  7.             consumed = onTouchEvent(e); 
  8.         } 
  9.     }     
  10.     return consumed; 

这是事件分发过程的简单描述,具体远比这复杂的多。

1.3. 核心行为

View 或 ViewGroup 有两个核心的行为:拦截(intercept) 和 消费(consume)。这两者是相互独立的,拦截不一定消费。是否要拦截看 onIntercepTouchEvent。是否要消费看 onTouchEvent。

注意:是否拦截还有其他因素影响。如果不是 down 事件,并且 mFirstTouchTarget 为空值,就会直接拦截事件。

在 dispatchTouchEvent 中有这样的代码

拦截的关键源码


  
  
  1. // Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN 
  2.          || mFirstTouchTarget != null) {       final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    if (!disallowIntercept) { 
  3.         intercepted = onInterceptTouchEvent(ev); 
  4.         ev.setAction(action); // restore action in case it was changed 
  5.     } else { 
  6.         intercepted = false
  7.        } 
  8. else {    // There are no touch targets and this action is not an initial down 
  9.     // so this view group continues to intercept touches. 
  10.     intercepted = true

从上面的源码可以看出,在不是 down 事件,并且 mFirstTouchTarget 为空的情况下,不会走 onInterceptTouchEvent 而是直接拦截。如果满足了,还会看 FLAG_DISALLOW_INTERCEPT 标记,如果不允许拦截(disallowIntercept 为 true),也不会走onInterceptTouchEvent,直接标记不拦截。

处理调用 onTouchEvent 的源码


  
  
  1. boolean result = false
  2.  
  3. ... 
  4.  
  5. ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null 
  6.            && (mViewFlags & ENABLED_MASK) == ENABLED 
  7.         && li.mOnTouchListener.onTouch(this, event)) { 
  8.     result = true
  9. }if (!result && onTouchEvent(event)) { 
  10.     result = true

可以看出,在该 View 为 ENABLE 的状态并且有 mTouchListener,会先调用 onTouch。在onTouch 返回 false 时才会继续调用 onTouchEvent。

onTouch 或者 onTouchEvent 的处理结果有:

  • 返回 true,会继续消费后续事件。意味着,后面的事件将会继续传递到该 View 的 dispatchTouchEvent 方法中进行调度。父 View 会为该 View 创建一个 TouchTarget 实例加入链表中,链表的第一项为 mFirstTouchTarget。后续的 move 和 up 事件会直接交给该 View 的 dispatchTouchEvent。
  • 返回 false,不再消费后续事件。意味着,后面的事件将会被父 View 拦截,而不再传递下来。

1.4. 特殊情况

比较特殊的情况有,子 View 可以使用 requestDisallowInterceptTouchEvent 影响去父 View 的分发,可以决定父 View 是否要调用 onInterceptTouchEvent 。比如,requestDisallowInterceptTouchEvent(true),父 View 就不用调用 onInterceptTouchEvent 来判断拦截,而就是不拦截。

该方法可以用来解决手势冲突。比如子 View 先消费了事件,但是后面父 View 也满足了手势触发的条件而拦截事件,导致子 View 手势执行一半后无法继续响应。可以使用 requestDisallowInterceptTouchEvent(true),这样后面的事件,父 View 不会走 onInterceptTouchEvent 回调来判断是否要拦截事件,而是直接把事件继续传下来。

2. 案例分析

下面举三个简单的例子,三个类 ViewGroup1st,ViewGroup2nd 和 View0,层级关系为


  
  
  1. <ViewGroup1st> 
  2.     <ViewGroup2nd> 
  3.         <View0 /> 
  4.     </ViewGroup2nd></ViewGroup1st> 

这三个类有两层 ViewGroup,最底层为 View,这几个例子主要理解 消费 行为,所以不做事件的拦截。

2.1. 案例1:均不消费 down 事件

在触摸屏幕中 View0 的区域后,输出 log 信息如下


  
  
  1. 12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup1st: dispatchTouchEvent before12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup1st: onInterceptTouchEvent return:false12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup2nd: dispatchTouchEvent before12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup2nd: onInterceptTouchEvent return:false12-30 14:06:03.694 31323-31323/lyn.demo D/View0: dispatchTouchEvent before12-30 14:06:03.694 31323-31323/lyn.demo D/View0: onTouchEvent return:false12-30 14:06:03.694 31323-31323/lyn.demo D/View0: dispatchTouchEvent return:false12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup2nd: onTouchEvent return:false12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup2nd: dispatchTouchEvent return:false12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup1st: onTouchEvent return:false12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup1st: dispatchTouchEvent return:false 

当 down 事件从 DecorView 开始了分发过程:

ViewGroup1st 收到事件,执行 onInterceptTouchEvent 返回 false,不拦截,于是调用 ViewGroup2nd 的 dispatchTouchEvent 向 ViewGroup2nd分发。

ViewGroup2nd 收到事件,dispatchTouchEvent 重复 ViewGroup1st 的分发策略。因为都不拦截,所以调用了 View0 的 dispatchTouchEvent。

View0 收到事件,而 View0 不是 ViewGroup 类型,所以把事件直接交给了 onTouchEvent。

View0 不消费事件,onTouchEvent 返回 false,dispatchTouchEvent 方法因此也返回 false。

ViewGroup2nd 因为 View0 的 dispatchTouchEvent 返回 false,确定了子类不消费事件,于是把事件传递给 onTouchEvent。但本身也不消费事件,所以 onTouchEvent 也返回 false,继续把事件上抛到 ViewGroup1st。

ViewGroup1st 重复了 ViewGroup2nd 的过程。

随后,move 事件不会再往下传了,而是直接被 Activity 拦截。

2.2. 案例2:View0 消费 down 事件

首先是 down 事件的传递,log 如下


  
  
  1. 12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup1st: dispatchTouchEvent before12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup1st: onInterceptTouchEvent return:false12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup2nd: dispatchTouchEvent before12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup2nd: onInterceptTouchEvent return:false12-30 14:14:09.384 7350-7350/lyn.demo D/View0: dispatchTouchEvent before12-30 14:14:09.384 7350-7350/lyn.demo D/View0: onTouchEvent return:true12-30 14:14:09.384 7350-7350/lyn.demo D/View0: dispatchTouchEvent return:true12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup2nd: dispatchTouchEvent return:true12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup1st: dispatchTouchEvent return:true 

ViewGroup1st 和 ViewGroup2st 的传递和案例1一样。区别在于 View0 onTouchEvent 返回 true 消费后续事件后,View0 的 dispatchTouchEvent 也返回 true,ViewGroup2nd 和 ViewGroup1st 不执行 onTouchEvent 也直接返回 true

然后稍微移动一下手指,move 事件往下传递


  
  
  1. 12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup1st: dispatchTouchEvent before12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup1st: onInterceptTouchEvent return:false12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup2nd: dispatchTouchEvent before12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup2nd: onInterceptTouchEvent return:false12-30 14:14:09.484 7350-7350/lyn.demo D/View0: dispatchTouchEvent before12-30 14:14:09.484 7350-7350/lyn.demo D/View0: onTouchEvent return:true12-30 14:14:09.484 7350-7350/lyn.demo D/View0: dispatchTouchEvent return:true12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup2nd: dispatchTouchEvent return:true12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup1st: dispatchTouchEvent return:true 

过程和 down 事件的传递一样。因为同样会经过 ViewGroup2nd 的 onInterceptTouchEvent,如果这时候 ViewGroup2nd 有拦截行为,move 事件就不会传到 View0 了。要避免这种情况发生,需要调用 View0 的requestDisallowInterceptTouchEvent,可见 1.4 部分。

2.3. 案例3:ViewGroup2nd 消费 down 事件

首先是 down 事件的传递,log 如下


  
  
  1. 12-30 14:25:30.074 18848-18848/lyn.demo D/ViewGroup1st: dispatchTouchEvent before12-30 14:25:30.074 18848-18848/lyn.demo D/ViewGroup1st: onInterceptTouchEvent return:false12-30 14:25:30.084 18848-18848/lyn.demo D/ViewGroup2nd: dispatchTouchEvent before12-30 14:25:30.084 18848-18848/lyn.demo D/ViewGroup2nd: onInterceptTouchEvent return:false12-30 14:25:30.084 18848-18848/lyn.demo D/View0: dispatchTouchEvent before12-30 14:25:30.084 18848-18848/lyn.demo D/View0: onTouchEvent return:false12-30 14:25:30.084 18848-18848/lyn.demo D/View0: dispatchTouchEvent return:false12-30 14:25:30.084 18848-18848/lyn.demo D/ViewGroup2nd: onTouchEvent return:true12-30 14:25:30.084 18848-18848/lyn.demo D/ViewGroup2nd: dispatchTouchEvent return:true12-30 14:25:30.084 18848-18848/lyn.demo D/ViewGroup1st: dispatchTouchEvent return:true 

由于 View0 不消费事件,dispatchTouchEvent 返回 false,所以执行了 ViewGroup2nd 的 onTouchEvent 方法。

ViewGroup2nd 消费事件,onTouchEvent 返回 true,之后 ViewGroup2nd 和 ViewGroup1st 的 dispatchTouchEvent 均返回 true。

动一下手指,move 事件接着传


  
  
  1. 2-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup1st: dispatchTouchEvent before12-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup1st: onInterceptTouchEvent return:false12-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup2nd: dispatchTouchEvent before12-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup2nd: onTouchEvent return:true12-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup2nd: dispatchTouchEvent return:true12-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup1st: dispatchTouchEvent return:true 

这时候,ViewGroup2nd 直接拦截了 move 事件,不再经过 onInterceptTouchEvent,也不再向 View0 分发,而是直接调用 onTouchEvent 进行处理。

3. down 事件分发图

在每个 View 都不拦截 down 事件的情况下,down 事件是这样传递的






本文作者:佚名
来源:51CTO
目录
相关文章
|
2月前
|
Java 开发工具 Android开发
Android与iOS开发环境搭建全解析####
本文深入探讨了Android与iOS两大移动操作系统的开发环境搭建流程,旨在为初学者及有一定基础的开发者提供详尽指南。我们将从开发工具的选择、环境配置到第一个简单应用的创建,一步步引导读者步入移动应用开发的殿堂。无论你是Android Studio的新手还是Xcode的探索者,本文都将为你扫清开发道路上的障碍,助你快速上手并享受跨平台移动开发的乐趣。 ####
|
1月前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
1月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
2月前
|
Linux Android开发 iOS开发
深入探索Android与iOS的多任务处理机制
在移动操作系统领域,Android和iOS各有千秋,尤其在多任务处理上展现出不同的设计理念和技术实现。本文将深入剖析两大平台在后台管理、资源分配及用户体验方面的策略差异,揭示它们如何平衡性能与电池寿命,为用户带来流畅而高效的操作体验。通过对比分析,我们不仅能够更好地理解各自系统的工作机制,还能为开发者优化应用提供参考。
|
2月前
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
|
2月前
|
安全 Java Linux
深入解析Android系统架构及其对开发者的意义####
【10月更文挑战第21天】 本文旨在为读者揭开Android操作系统架构的神秘面纱,探讨其如何塑造现代移动应用开发格局。通过剖析Linux内核、硬件抽象层、运行时环境及应用程序框架等关键组件,揭示Android平台的强大功能与灵活性。文章强调了理解Android架构对于开发者优化应用性能、提升用户体验的重要性,并展望了未来技术趋势下Android的发展方向。 ####
59 0
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
103 2
|
3月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
90 0
|
20天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
20天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多