Android事件分发详解

2023-10-27

  一:事件分发基础知识讲解

   1.事件分发的”事件“是指什么?

即 用户触摸屏幕时产生的点击事件(Touch事件)。这个点击事件(Touch事件)会被封装成MotionEvent对象。从手指接触屏幕至手指离开屏幕这个过程产生的一系列事件。一般情况下,事件列都是以DOWN事件开始、UP事件结束,中间有无数的MOVE事件。

MotionEvent 事件类型:

MotionEvent.ACTION_DOWN  手指 初次接触到屏幕 时触发

MotionEvent.ACTION_MOVE    手指 在屏幕上滑动时触发,会多次触发

MotionEvent.ACTION_UP      手指 离开屏幕 时触发

MotionEvent.ACTION_CANCEL  事件 被上层拦截 时触发

2.事件分发的本质

将点击事件(MotionEvent)传递到某个具体的View。及我们说所的事件分发。

 3.事件在哪些对象之间进行传递?事件分发的流程顺序?

事件传递的对象:Activity、ViewGroup、View (所有的控件 都必须具备传递事件的能力)

事件传递的顺序:1个点击事件发生后,事件先传到Activity-->再传到ViewGroup-->再到View

4. 事件分发过程由哪些方法协作完成?(重点)

① dispatchTouchEvent() :  当前事件传递的对象是否把某个点击事件分发传递给他的孩子。                                             

 dispatchTouchEvent()=true :  当前事件传递的对象  当前点击事件分发给下一级ViewGroup,让下一级ViewGroup 再来分发这个点击事件       

 dispatchTouchEvent()=false :   当前点击事件没有往下分发
(表示当前点击事件分发结束了,但是还没有处理,那就需要在 viewGroup.OnTouchEvent()方法来处理这个点击事件).   

 ②onTouchEvent() :  (在dispatchTouchEvent()方法内部执行)                  
   当 前事件传递的对象 对 该点击事件 是否处理        

    true 处理该点击事件

    false  不处理该点击事件                               

 ③ onInterceptTouchEvent()  : (在ViewGroup的dispatchTouchEvent()方法内部执行)                 
   当前事件传递的对象 对 该点击事件是否进行拦截

  onInterceptTouchEvent() =false (默认) 不拦截当前的点击事件,那么该事件会继续向下级子View 传递分发点击事件

  onInterceptTouchEvent() =true (需手动复写设置) 拦截当前的点击事件,即当前点击事件不允许往下传递

dispatchTouchEvent()、 onTouchEvent() 属于消费事件、终结事件传递(返回true)
而onInterceptTouchEvent 并不能消费事件,它相当于是一个分叉口起到分流导流的作用,对后续的ACTION_MOVE和ACTION_UP事件接收起到非常大的作用
接收了ACTION_DOWN事件的函数不一定能收到后续事件(ACTION_MOVE、ACTION_UP)
onTouch()和onTouchEvent()的区别:
该2个方法都是在View.dispatchTouchEvent()中调用
但onTouch()优先于onTouchEvent执行;若手动复写在onTouch()中返回true(即 将事件消费掉),
将不会再执行onTouchEvent()

二:事件分发机制流程详细分析

流程1:Activity对点击事件的分发流程

Android事件分发机制首先会将点击事件传递到Activity中,通过dispatchTouchEvent()方法进行点击事件分发。

dispatchTouchEvent()如何分发 这个点击事件? 这个点击事件分发给谁呢?

(1)Activity对点击事件的分发流程 简单点总结:

  Activity.dispatchTouchEvent()

  true    当前点击事件分发给下一级ViewGroup,让下一级ViewGroup 再来分发这个点击事件

  false    当前点击事件没有往下分发(表示当前点击事件分发结束了),

            (当前点击事件虽然结束了分发,但是还没有处理)那就需要在Acvity.dispatchTouchEvent() 方法内部的执行  Activity.OnTouchEvent()方法来处理这个点击事件, 

            Activity.OnTouchEvent()  =true  被Activity 消费处理了该点击事件(点击事件在Windows边界外)   

              Activity.OnTouchEvent()  =false  Activity  没有处理消费该事件 (点击事件在Windows边界内)

(2)源码分析:这里略

Window类是抽象类,其唯一实现类 = PhoneWindow类
DecorView顶层View.DecorView类是PhoneWindow类的一个内部类.
DecorView继承自FrameLayout,是所有界面的父类.
FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
调用父类ViewGroup.dispatchTouchEvent()方法去处理这个点击事件

所以Activity的事件分发(Activity.dispatchTouchEvent(),最终还是有 ViewGroup.dispatchTouchEvent()方法去处理这个点击事件ViewGroup.dispatchTouchEvent() =false  就是ViewGroup,不会把点击事件往下分发, 那么就执行Activity.onTouchEvent()方法, 有Activity处理这个点击事件。

流程2: ViewGroup对点击事件的分发流程

ViewGroup每次事件分发时,都需先调用viewGroup.onInterceptTouchEvent()询问是否拦截当前点击事件

通过ViewGroup 源码分析:

根据源码我们知道:viewgroup.dispatchTouchEvent()事件分发  返回true 或false 的需要判断条件是:

 (1)判断条件1:boolean disallowIntercept:是否禁用事件拦截的功能

   ① disallowIntercept = true  禁用事件拦截的功能,   那么viewGroup.onInterceptTouchEvent(ev)=false,
   所以不拦截当前的点击事件,那么该事件会继续向下级子View 传递分发点击事件。

 ②disallowIntercept = false(默认是false)  不禁用事件拦截的功能   那么 就执行boolean intercepted = onInterceptTouchEvent(ev)方法 根据这个方法返回true/false 来判断是否拦截当前点击事件

boolean disallowIntercept:是否禁用事件拦截的功能(默认是false),
可通过调用requestDisallowInterceptTouchEvent(boolean disallowIntercept)修改boolean值

(2)判断条件2:  判断 ViewGroup类里的onInterceptTouchEvent(ev)方法 返回true/false来判断是否拦截当前点击事件(这里很重要)  

①boolean intercepted = onInterceptTouchEvent(ev)=false(默认),即不拦截当前点击事件,从而进入到条件判断的内部,当前事件分发传递给孩子View(viewGroup->view)去处理。

 那么这里ViewGroup如何把当前事件传递给孩子View的呢?

通过for循环,遍历当前ViewGroup下的所有子View找到那个被点击的子View,从而找到当前被点击的View,内部调用了childView.dispatchTouchEvent(ev)方法 =  true ,就是实现了点击事件从ViewGroup到子View的传递,子view就处理了此点击事件;如果找不到 viewGroup 会自己处理消费该点击事件::onTouch() -> onTouchEvent() -> performClick() -> onClick()。

②boolean intercepted = onInterceptTouchEvent()=true(手动复写修改true),即拦截事件,从而跳出了该if条件判断,父容器停止当前事件往下传递,那么父容器viewGroup自己处理当前点击事件:

super.dispatchTouchEvent(ev) =  true-->viewonTouch() -> onTouchEvent() -> performClick() -> onClick()

源码分析:

/**
  * 源码分析:ViewGroup.dispatchTouchEvent()
  */ 
  public boolean dispatchTouchEvent(MotionEvent ev) { 

  // 仅贴出关键代码
  ... 

  if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  // 分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
    // 判断值1-disallowIntercept:是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
    // 判断值2-!onInterceptTouchEvent(ev) :对onInterceptTouchEvent()返回值取反
        // a. 若在onInterceptTouchEvent()中返回false,即不拦截事件,从而进入到条件判断的内部
        // b. 若在onInterceptTouchEvent()中返回true,即拦截事件,从而跳出了该条件判断
        // c. 关于onInterceptTouchEvent() ->>分析1

  // 分析2
    // 1. 通过for循环,遍历当前ViewGroup下的所有子View
    for (int i = count - 1; i >= 0; i--) {  
        final View child = children[i];  
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                || child.getAnimation() != null) {  
            child.getHitRect(frame);  

            // 2. 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
            if (frame.contains(scrolledXInt, scrolledYInt)) {  
                final float xc = scrolledXFloat - child.mLeft;  
                final float yc = scrolledYFloat - child.mTop;  
                ev.setLocation(xc, yc);  
                child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

                // 3. 条件判断的内部调用了该View的dispatchTouchEvent()
                // 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面章节介绍的View事件分发机制)
                if (child.dispatchTouchEvent(ev))  { 

                // 调用子View的dispatchTouchEvent后是有返回值的
                // 若该控件可点击,那么点击时dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
                // 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
                // 即该子View把ViewGroup的点击事件消费掉了

                mMotionTarget = child;  
                return true; 
                      }  
                  }  
              }  
          }  
      }  
    }  

  ...

  return super.dispatchTouchEvent(ev);
  // 若无任何View接收事件(如点击空白处)/ViewGroup本身拦截了事件(复写了onInterceptTouchEvent()返回true)
  // 会调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
  // 因此会执行ViewGroup的onTouch() -> onTouchEvent() -> performClick() -> onClick(),即自己处理该事件,事件不会往下传递
  // 具体请参考View事件分发机制中的View.dispatchTouchEvent()

  ... 

}

/**
  * 分析1:ViewGroup.onInterceptTouchEvent()
  * 作用:是否拦截事件
  * 说明:
  *     a. 返回false:不拦截(默认)
  *     b. 返回true:拦截,即事件停止往下传递(需手动复写onInterceptTouchEvent()其返回true)
  */
  public boolean onInterceptTouchEvent(MotionEvent ev) {  
    
    // 默认不拦截
    return false;

  } 
  // 回到调用原处

流程3:View的事件分发机制

View的事件分发机制view.dispatchTouchEvent()返回true/false的核心点在于:
view是否注册了Touch事件监听(mOnTouchListener.onTouch(this, event))

   (1) 没有注册Touch事件监听setOnTouchListener, 怎么处理事件?  
   调用View.onTouchEvent() -> performClick() ->(如果设置了点击事件,就回调) onClick()。
      (2)注册Touch事件监听setOnTouchListener   判断onTouch()的返回值
     ① 返回 onTouch()=false
       事件无被消费,View.dispatchTouchEvent()=false 跳出If,
      事件会继续往下传递,去处理事件, 即调用View.onTouchEvent() -> performClick() ->(如果设置了点击事件,就回调) onClick()。
      
      ②返回onTouch()=true,
      view.dispatchTouchEvent()=true,事件被消费,不会继续往下传递 ,事件分发结束(已经分发到头了),
       View.dispatchTouchEvent()直接返回true;
     所以最终不会调用View.onTouchEvent(),也不会调用onClick()。

这里需要特别注意的是,onTouch()的执行 先于onClick()

三:工作流程总结事件分发流程_Activity_ViewGroup_View_流程图:
1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到View。

四:事件分发的案例场景:

操作情景:

  1. 用户先触摸到屏幕上View C上的某个点(图中黄区)

Action_DOWN事件在此处产生

2.用户移动手指

3.最后离开屏幕

一般的事件传递的几种情况

 场景1:默认情况的事件分发

即不对控件里的方法(dispatchTouchEvent()onTouchEvent()onInterceptTouchEvent())进行重写 或 更改返回值。这3个方法的默认实现:调用下层的方法 & 逐层返回

默认情况的事件分发事件传递情况:(呈U型)

Activity A:dispatchTouchEvent() ->> ViewGroup B:dispatchTouchEvent() ->> View C:dispatchTouchEvent() -->>View C:onTouchEvent() ->> ViewGroup B:onTouchEvent() ->> Activity A:onTouchEvent()

 

 场景2:处理事件分发

View C希望处理该点击事件,即:设置View C为可点击的(Clickable) 或 复写其onTouchEvent()返回true

DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理该事件

因为View C正在处理该事件,那么DOWN事件将不再往上传递给ViewGroup B 和 Activity AonTouchEvent()

其他事件(Move、Up)也将传递给View ConTouchEvent()处理。

场景3:拦截DOWN事件

假设ViewGroup B希望处理该点击事件,即ViewGroup B复写了onInterceptTouchEvent()返回true拦击点击事件不允许往下传递、onTouchEvent()返回true ViewGroup B 自己处理该点击事件

DOWN事件被传递给ViewGroup BonInterceptTouchEvent()=true,表示拦截该事件,即自己处理该事件(事件不再往下传递)

其他事件(Move、Up)将直接传递给ViewGroup BonTouchEvent()

场景4:拦截DOWN的后续事件

ViewGroup B 无拦截DOWN事件(DOWN事件传递到View ConTouchEvent()=true,还是View C来处理DOWN事件),但ViewGroup B拦截了接下来的MOVE事件,viewGroup BonInterceptTouchEvent()=true拦截该MOVE事件,但该事件并没有传递给ViewGroup B,

这个MOVE事件将会被系统变成一个CANCEL事件传递给View ConTouchEvent()来进行处理

只有后续再来了一个MOVE事件,该MOVE事件才会直接传递给ViewGroup BonTouchEvent()=true来进行处理,ViewGroup B就处理了后续的MOVE事件了.

View C再也不会收到该事件列产生的后续事件

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android事件分发详解 的相关文章

  • python基础(5):深入理解 python 中的赋值、引用、拷贝、作用域

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 在 python 中赋值语句总是建立对象的引用值 而不是复制对象 因此 python 变量更像是指针 而不是数据存储区域 这点和大多数 OO 语言类似吧 比如 C java
  • JAVA基础学习笔记1

    教程来源 https www runoob com java java tutorial html 以下是个人学习笔记 用水平线划分小节 我的第一个JAVA程序 通常都写成这样 public class HelloWorld public
  • Spring Boot -01- 快速入门篇(详解图文教程)

    作者 肖朋伟 来源 https blog csdn net qq 40147863 article details 84194493 Spring Boot 01 快速入门篇 图文教程 今天开始不断整理 Spring Boot 2 0 版本
  • Windows安装Ubuntu双系统(Win11+最新Ubuntu22.04.1LTS)

    目录 前言 一 查看基础环境 二 准备安装文件 1 下载Ubuntu 22 04 01 LTS镜像ISO文件 2 下载官方推荐的U盘启动制作工具 3 制作启动U盘 4 新建硬盘分区用来安装Ubuntu系统 5 BIOS设置 三 安装Ubun
  • stata学习笔记①stata基础介绍

    文章目录 一 为什么要学stata 二 软件基本解释 1 软件界面 2 导入示例数据 3 认识几个重要的功能符号 三 数据的基本观测 四 统计性描述 1 codebook 数据字典使用 2 summarize 五 图像初步探索 1 hist
  • 华硕服务器RS720-E10-RS12无法安装win10到M.2的NVME SSD

    进BIOS开启CSM 兼容性支持模块
  • 区块链浏览器与合约代码

    声明 此文系 Vue3 0 Quasar ethers js 和以太坊智能合约交互 系列教程之一 开始 区块链浏览器 在本教程中 我一直在说区块链是去中心化的 它想打造的是一个数据永不可篡改且公开透明的数据世界 那么这样的区块链它最重要的一
  • linemod算法过程理解

    一 提取模板 1 预处理 使用高斯模糊预处理将要作为模板的RGB图 2 模板梯度计算 分别计算RGB三个通道中每个像素点x和y方向的梯度 sobel算子 取幅值最大的作为该像素的梯度 若梯度幅度值小于阈值 则被舍弃 3 梯度离散化及量化 对
  • 01 逻辑回归的理解

    1简介 逻辑回归是一个分类算法 本质是对线性回归做了一个变换 将值域压在0 1的空间 从而可以未每一个特征 估算出一个概率 作预测问题 二分类 逻辑回归问题 本质上就变成 求解变换后的每个特征的权重 ax1 bx2 cx3 0 1 求解模型
  • STM32的PWM和DAC练习

    文章目录 一 输出PWM波形 1 1 实验代码 1 2 调试 一 输出PWM波形 1 1 实验代码 代码来自野火STM32F103 mini开发板资料 1 书籍配套例程 F103RCMINI 32 TIM 高级定时器 3 TIM 高级定时器
  • 【牛客】HJ1 字符串最后一个单词的长度

    三行代码做一道题HJ1 字符串最后一个单词的长度 我的意思是不包括固定代码哦 读题 输出几个单词 以空格隔开 输出最后一个单词的长度 代码 直接写最终解题代码 include
  • vue路由传参的两种方式,实现返回上个页面不刷新

    我的项目是当在新增页面 下面叫A页面 先提交一些数据 然后跳转到下一个页面 下面叫B页面 再填写数据 然后返回到新增的页面 之前我直接跳转回B页面goback 这样的话跳转回来A页面就什么数据都没有了 解决方法有两种 一种是在地址栏里面拿参
  • 最简单自动化搜索的脚本代码

    from selenium import webdriver from selenium webdriver common by import By driver webdriver Chrome 打开的网址一般是get请求 driver
  • C语言学习记录——项目1 交换机后台管理之登录菜单(1)

    C语言学习记录 项目1 交换机后台管理之登录菜单 1 交换机 交换机 Switch 是一种用于电 光 信号转发的网络设备 它可以为接入交换机的任意两个网络节点提供独享的电信号通路 最常见的交换机是以太网交换机 其他常见的还有电话语音交换机
  • mysql 查询出表字段的属性

    SELECT column name 字段名 column comment 字段说明 column type 字段类型 column key 约束 FROM information schema COLUMNS WHERE table na

随机推荐

  • Node.js 基础篇(九):fs.watchFile

    目录 fs watch 监视 filename 的变化 fs watchFile 监视 filename 的变化 fs watch 监视 filename 的变化 fs watch filename options listener fil
  • 机器学习:matlab和python实现SVD(奇异值分解)算法

    1 SVD SVD Singular Value Decomposition 奇异值分解 SVD算法不光可以用于降维算法中的特征分解 还可以用于推荐系统 以及自然语言处理等领域 是很多机器学习算法的基石 假设我们现在有一个矩阵M m n 如
  • 服务器上R调用png显示x11报错怎么办?

    太长不读版 治本的方法 服务器安装pango 之后重新编译R语言 治标的方法 在R的配置文件中增加options bitmapType cairo 服务器上装完R语言之后 发现自己的PNG函数无法正常调用 始终会出现一个X11连接失败的错误
  • 进行模拟点击的时候,利用python完成黑名单和白名单(判断字符串是否包含)

    在做项目的时候 遇到一个需求 就是在进行模拟点击的时候 要求加上一个黑名单和白名单 意思就是 白名单 模拟点击的时候 不能点击白名单里面有的元素 例如 包含什么地址 或者什么数字和特殊的字符串的时候 黑名单 就是不在黑名单里的元素 就不能进
  • 中台到底在共享什么

    一 中台的诞生 中台战略是企业数字化转型过程中的一个热门话题 说到中台转型 企业大多对标阿里巴巴 2015年阿里巴巴提出了 大中台 小前台 的中台战略 提出之初阿里有近 4 亿用户 为超过 1000万各类企业提供服务 业务种类繁多 业务之间
  • @DynamicUpdate 注解 动态更新 和 lombok 插件 @Data 注解使用 ; @Transient 与Dto引入

    比如在实体类中 private Date updateTime 这个属性 在数据库中 我们创建update Time的时候我们 update Time timestamp not null default current timestamp
  • [C++]何为溢出?如何避免?

    总时忘记 以Leetcode69为例 来记录一下吧 给你一个非负整数 x 计算并返回 x 的 算术平方根 由于返回类型是整数 结果只保留 整数部分 小数部分将被 舍去 注意 不允许使用任何内置指数函数和算符 例如 pow x 0 5 或者
  • 惊!一百多万人下载这些vscode摸鱼插件

    分享资料 一起学习 我是小白 微信 tlxx233 备注 888建了个微信程序员学习群 互相解答问题 有需要的同学可以加我微信进群 作为一个程序员 我们的日常工作离不开代码编辑器 有时候编写代码会让人感到无聊和单调 但是好的插件可以让编码变
  • 这个牛逼的Python模块,能让你轻松模拟并记录键盘操作(附零基础学习资料)

    前言 模拟键盘操作执行自动化任务 我们常用的有 pyautowin 等自动化操作模块 但是这些模块有一个很大的缺点 编译的时候非常依赖 windows 的C语言底层模块 文末送福利 今天介绍的这个模块叫做 keyboard 它有一个最大的优
  • SaltStack部署

    目录 一 SaltStack简介 1 基本简介 2 通信方式 3 功能简介 二 资源获取 1 官网地址 2 阿里云地址 三 部署 1 配置环境说明 2 资源配置 一 SaltStack简介 1 基本简介 SaltStack是一种新型的基础设
  • IDEA 如何根据代码自动生成类图

    文件夹右键 gt Diagrams gt show Diagram gt Java classes diagram 选择类图的成员 成员变量 构造器 方法 配置文件 内部类 生成的类图
  • 读书感悟之,从术到道

    最近在看了一些书 因为书的内容和方向有着比较大的出入 自然就有了一些不同层面的感悟 在笔者看来 书籍大抵成三类 层面 1 术的层面 1 科普和不系统的知识类 如 电气知识1000问 各种论坛 帖子 公众号的非连载的知识类 人文社科的更多 这
  • visio绘制自制图案并填充

    1 首先打开 文件 gt gt 选项 gt gt 高级 勾选以开发人员模式运行 2 选择开发工具 选择线条 3 绘画出椭圆与线条 4 选择操作 使用修剪功能或得相交得两个部分 5 按照上述步骤绘制出想要得图案 全选后选择操作中的连接 6 对
  • 尝试在条件“($(MsBuildMajorVersion) < 16)”中对计算结果为“”而不是数字的“$(MsBuildMajorVersion)”进行数值比较

    https learn microsoft com en us nuget consume packages migrate packages config to package reference 我的处理方法简单粗爆 直接将packag
  • mysql映射表的作用_php – MySQL:了解映射表

    当使用多对多关系时 处理此事的唯一现实的方法是使用映射表 说我们有一所有老师和学生的学校 一个学生可以有多个教师 反之亦然 所以我们做3个表 student id unsigned integer auto increment primar
  • 【RabbitMq】05 RabbitMq 消息确认机制-可靠抵达

    一 保证消息不丢失 1 使用事务消息 性能下降250倍 2 消息确认机制 1 publisher confirmCallback 确认模式 2 publisher returnCallback 未投递到queue退回模式 3 consume
  • BLE连接建立过程详解

    同一款手机 为什么跟某些设备可以连接成功 而跟另外一些设备又连接不成功 同一个设备 为什么跟某些手机可以建立连接 而跟另外一些手机又无法建立连接 同一个手机 同一个设备 为什么他们两者有时候连起来很快 有时候连起来又很慢 Master是什么
  • 【解决问题】mysql 数据库字符串分割之后多行输出方法

    背景 项目需要从一张表查询出来数据插入到另一张表 其中有一个字段是用逗号分隔的字符串 需要多行输入到另一张表 那么这个如何实现呢 方案 下面先粘贴下sql语句 select SUBSTRING INDEX SUBSTRING INDEX v
  • Windows Server 2008 R2 实现多用户同时登陆

    Server 版系统一直都支持多用户同时登陆 这是一个很好用的功能 我们来看看怎么实现的 Start gt Administrator tools gt Remote Desktop Services gt Remote Desktop S
  • Android事件分发详解

    一 事件分发基础知识讲解 1 事件分发的 事件 是指什么 即 用户触摸屏幕时产生的点击事件 Touch事件 这个点击事件 Touch事件 会被封装成MotionEvent对象 从手指接触屏幕至手指离开屏幕这个过程产生的一系列事件 一般情况下