【Android】View的事件分发机制

文章目录

  • 分发顺序
    • Activity
    • ViewGroup
    • View
  • 协作方法
  • 整体流程
    • 注意
  • Activity事件分发
  • ViewGroup事件分发
  • View点击事件
  • 总结

分发顺序

Activity->ViewGroup->View

Activity

  • 分发事件Activity 通过 dispatchTouchEvent 方法分发事件,首先尝试将事件传递给 Window 及其根视图(DecorView)。
  • 消费事件:如果根视图及其子视图没有消费事件,Activity 会调用自己的 onTouchEvent 方法处理事件。因此,Activity 具备兜底消费事件的能力。

ViewGroup

  • 分发事件ViewGroup 通过 dispatchTouchEvent 方法将事件分发给子视图(或进一步嵌套的子视图)。
  • 拦截事件ViewGroup 拥有 onInterceptTouchEvent 方法,可以决定是否拦截事件。拦截事件后,事件不会再传递给子视图,而是直接由 ViewGroup 自己处理。
  • 消费事件:当 ViewGroup 需要处理自己捕获的事件时,最终会调用其 onTouchEvent 方法来消费事件。因此,ViewGroup 可以在适当情况下选择消费事件。

View

  • 消费事件View 只能消费事件,而没有分发和拦截事件的能力。当 dispatchTouchEvent 将事件传递给 View 时,View 只能选择在 onTouchEvent 中处理和消费该事件,或者将事件交回父视图。
  • Activity:负责整体的事件分发和兜底消费。
  • ViewGroup:负责在视图层级中分发事件,具备拦截和消费事件的灵活性。
  • View:仅具备事件消费能力。

协作方法

  1. 分发事件 (dispatchTouchEvent)

    • dispatchTouchEvent(MotionEvent event) 方法是事件分发的入口。
    • 每当事件产生时(如点击、滑动),系统会将该事件封装成一个 MotionEvent 对象,并通过 dispatchTouchEvent 方法传递给根视图(通常是 Activity 中的 DecorView)。
    • dispatchTouchEvent 中,事件会根据层级逐层传递给子视图,直到找到可以处理事件的视图为止。
    • dispatchTouchEvent 返回 true,则事件处理停止;若返回 false,则事件会继续传递。
  2. 拦截事件 (onInterceptTouchEvent)

    • onInterceptTouchEvent(MotionEvent event) 主要用于 ViewGroup 及其子类。
    • ViewGroup 可以选择是否拦截事件并防止它传递给子视图。
    • 如果 onInterceptTouchEvent 返回 true,则事件会直接交由 ViewGroup 自己处理,子视图将无法接收到事件;如果返回 false,则事件会继续传递到子视图。
    • 常见场景是滑动容器(如 ScrollView)在检测到用户滑动手势时,会选择拦截触摸事件,使得事件不再传递给子视图。
  3. 处理事件 (onTouchEvent)

    • onTouchEvent(MotionEvent event) 方法是实际处理事件的地方。
    • 视图可以在该方法中根据 MotionEvent 的类型(如 ACTION_DOWNACTION_MOVEACTION_UP 等)进行具体的操作(如点击处理、滑动等)。
    • 如果 onTouchEvent 返回 true,表示视图已处理该事件;如果返回 false,则事件会继续向上层视图传递,直到被某个视图消费或到达根视图为止。

整体流程

  1. 事件从 ActivitydispatchTouchEvent 开始。
  2. 事件传递给根视图(通常是一个 ViewGroup),然后通过 dispatchTouchEvent 传递到子视图。
  3. ViewGroup 中调用 onInterceptTouchEvent 判断是否拦截事件。
  4. 如果 onInterceptTouchEvent 返回 false,则事件继续向下传递;否则由 ViewGrouponTouchEvent 处理。
  5. 最终,事件在目标视图的 onTouchEvent 中被消费。

注意

  • 事件的消费:当某个视图返回 true,表示事件被消费,后续事件(如 ACTION_MOVEACTION_UP)会继续传递给该视图。
  • 父视图与子视图的冲突:父视图可以通过拦截事件来管理事件的流向,避免子视图误处理事件。
  • requestDisallowInterceptTouchEvent(boolean disallowIntercept):子视图可以请求父视图不要拦截事件,适用于处理特殊的事件需求(如嵌套滑动)。

Activity事件分发

public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);
}
  1. getWindow().superDispatchTouchEvent(ev)
if (getWindow().superDispatchTouchEvent(ev)) {return true;
}
Activity#dispatchTouchEvent() ->
PhoneWindow#superDispatchTouchEvent() ->
DecorView#superDispatchTouchEvent() ->
ViewGroup#dispatchTouchEvent()
  • getWindow()返回当前ActivityWindow对象(Window对象的唯一实现类是PhoneWindow类),调用其superDispatchTouchEvent方法来进一步分发事件
  • DecorView#superDispatchTouchEvent() 方法内部会将事件传递给根视图(一般是 DecorView),并由该视图将事件沿视图层次分发下去,此方法调用父类ViewGroup#dispatchTouchEvent()

ViewGroupdispatchTouchEvent

  • ViewGroup.dispatchTouchEvent 返回 true 表示事件已经在 ViewGroup 或其子视图中被消费,不再向上传递。
  • ViewGroup.dispatchTouchEvent 返回 false 表示事件未被处理,最终会由 Activity 兜底处理。
  1. onTouchEvent(ev)
return onTouchEvent(ev);
  • 如果 superDispatchTouchEvent(ev) 返回 false,即所有的视图和组件都未处理该事件,dispatchTouchEvent 会将事件传递给 Activity 自身的 onTouchEvent 方法。
  • onTouchEventActivity 处理事件的最后一步,通常用于处理默认的触摸行为。

ViewGroup事件分发

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {   // 如果是 ACTION_DOWN 或者存在 mFirstTouchTarget(表示当前视图或子视图已经接收了一个触摸事件),则可以继续检查是否拦截。final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;// 如果拦截事件(disallowIntercept 为 false),调用onInterceptTouchEvent,该事件交给viewGroup处理 // 不拦截事件则设置intercepted为false,后续继续向下传递给子视图if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action);} else {intercepted = false;}} else {intercepted = true;}//...
}
  • ViewGroup.onInterceptTouchEvent()

    返回false:不拦截(默认)

    返回true:拦截,即事件停止往下传递(需手动复写onInterceptTouchEvent()其返回true)

// 从后往前遍历子视图
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);// 检查子视图是否可以接收指针事件以及触摸点是否在其边界内if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue; // 跳过不接收事件的子视图}// 获取当前子视图的触摸目标newTouchTarget = getTouchTarget(child);if (newTouchTarget != null) {// 子视图已经在处理触摸事件,更新指针ID位newTouchTarget.pointerIdBits |= idBitsToAssign;break; // 退出循环}// 将触摸事件分发给当前子视图if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// 更新最后的触摸状态mLastTouchDownTime = ev.getDownTime();if (preorderedList != null) {for (int j = 0; j < childrenCount; j++) {if (children[childIndex] == mChildren[j]) {mLastTouchDownIndex = j;break;}}} else {mLastTouchDownIndex = childIndex;}mLastTouchDownX = x; mLastTouchDownY = y;newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; // 退出循环}
}
  1. 遍历子视图

    • 首先,代码会遍历 ViewGroup 的所有子视图,从后向前(通常是为了优先处理最上层的视图),以确保能够找到可以接收触摸事件的视图。
  2. 判断是否能够接收点击事件

    • 判断子视图是否能够接收点击事件主要考虑两个条件:
      • 动画状态:如果子视图正在播放动画,它可能不希望接收触摸事件。在这种情况下,该视图将被跳过。
      • 触摸坐标:需要检查触摸事件的坐标是否落在子视图的区域内。通过计算触摸点与子视图边界的关系来判断。
  3. 传递触摸事件

    • 如果找到一个满足条件的子视图,该视图将接收触摸事件。此时,会调用 dispatchTransformedTouchEvent 方法,实际上是调用了子视图的 dispatchTouchEvent 方法,将触摸事件传递给它进行处理。

image-20241103204811747

如果子元素view返回了true,表示被子元素消耗了,那么此时就会跳出循环

img

View点击事件

View事件分发机制从dispatchTouchEvent()开始

public boolean dispatchTouchEvent(MotionEvent event) {  if ( (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener != null &&  mOnTouchListener.onTouch(this, event)) {  return true;  } return onTouchEvent(event);  
}
// 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent()
//   1. (mViewFlags & ENABLED_MASK) == ENABLED
//   2. mOnTouchListener != null
//   3. mOnTouchListener.onTouch(this, event)
// 下面对这3个条件逐个分析
/*** 条件1:(mViewFlags & ENABLED_MASK) == ENABLED* 说明:*    1. 该条件是判断当前点击的控件是否enable*    2. 由于很多View默认enable,故该条件恒定为true(除非手动设置为false)*/
/*** 条件2:mOnTouchListener != null* 说明:*   1. mOnTouchListener变量在View.setOnTouchListener()里赋值*   2. 即只要给控件注册了Touch事件,mOnTouchListener就一定被赋值(即不为空)*/
public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l;  
} 
/*** 条件3:mOnTouchListener.onTouch(this, event)* 说明:*   1. 即回调控件注册Touch事件时的onTouch();*   2. 需手动复写设置,具体如下(以按钮Button为例)*/
button.setOnTouchListener(new OnTouchListener() {  @Override  public boolean onTouch(View v, MotionEvent event) {  return false;  // 若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束// 若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent()中跳出If,执行onTouchEvent(event)// onTouchEvent()源码分析 -> 分析1}  
});
/*** 分析1:onTouchEvent()*/
public boolean onTouchEvent(MotionEvent event) {  ... // 仅展示关键代码// 若该控件可点击,则进入switch判断中if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  // 根据当前事件类型进行判断处理switch (event.getAction()) { // a. 事件类型=抬起View(主要分析)case MotionEvent.ACTION_UP:  performClick(); // ->>分析2break;  // b. 事件类型=按下Viewcase MotionEvent.ACTION_DOWN:  postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  break;  // c. 事件类型=结束事件case MotionEvent.ACTION_CANCEL:  refreshDrawableState();  removeTapCallback();  break;// d. 事件类型=滑动Viewcase MotionEvent.ACTION_MOVE:  final int x = (int) event.getX();  final int y = (int) event.getY();  int slop = mTouchSlop;  if ((x < 0 - slop) || (x >= getWidth() + slop) ||  (y < 0 - slop) || (y >= getHeight() + slop)) {  removeTapCallback();  if ((mPrivateFlags & PRESSED) != 0) {  removeLongPressCallback();  mPrivateFlags &= ~PRESSED;  refreshDrawableState();  }  }  break;  }  // 若该控件可点击,就一定返回truereturn true;  }  // 若该控件不可点击,就一定返回falsereturn false;  
}
/*** 分析2:performClick()*/  
public boolean performClick() {  if (mOnClickListener != null) {// 只要通过setOnClickListener()为控件View注册1个点击事件// 那么就会给mOnClickListener变量赋值(即不为空)// 则会往下回调onClick() & performClick()返回trueplaySoundEffect(SoundEffectConstants.CLICK);  mOnClickListener.onClick(this);  return true;  }  return false;  
}

img

总结

img



感谢您的阅读
如有错误烦请指正


参考:

  1. Android 事件分发机制详解(上)
  2. Android 事件分发机制详解(下)_montouchlistener-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1096.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于vue框架的的考研网上辅导系统ao9z7(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,公告信息,课程分类,考研资料,考研视频,课程信息,院校建议,教师 开题报告内容 基于Vue框架的考研网上辅导系统开题报告 一、研究背景与意义 随着高等教育的普及和就业竞争的加剧&#xff0c;考研已成为众多大学生提升学历、增强就…

分析 std::optional 的使用与常见错误

文章目录 引言常见错误及解决方案1. 错误使用 std::optional 变量进行算术运算2. 错误检查 std::optional 是否有值3. 忽视 std::optional 的默认值 结论 引言 std::optional 是 C17 引入的一个模板类&#xff0c;用于表示可能有也可能没有值的情况。它特别适用于函数返回值&a…

本地搭建php包依赖管理工具,使用satis搭建私有composer仓库

一、总体设计 dns服务器nginxsatis web 静态页面satis manage 管理程序 二、nginx配置 1、nginx.conf # For more information on configuration, see: # * Official English Documentation: http://nginx.org/en/docs/ # * Official Russian Documentation: http://ngi…

软件测试学习笔记丨SeleniumPO模式

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/22525 本文为霍格沃兹测试开发学社的学习经历分享&#xff0c;写出来分享给大家&#xff0c;希望有志同道合的小伙伴可以一起交流技术&#xff0c;一起进步~ 说明&#xff1a;本篇博客基于sel…

Ubuntu-22.04 虚拟机安装

1. Ubuntu安装方式 1.1. 基于物理介质安装 光盘安装&#xff1a;通过将 Ubuntu 镜像刻录到光盘&#xff0c;在计算机 BIOS/UEFI 中设置光盘为第一启动项&#xff0c;然后按照安装程序的提示进行语言选择、分区、用户信息设置等操作来完成安装。这种方式需要有光盘刻录设备和空…

软件设计师-上午题-12、13 软件工程(11分)

软件工程题号一般为17-19和29-36题&#xff0c;分值一般为11分。 目录 1 软件过程 1.1 CMM(能力成熟度模型) 1.1.1 真题 1.2 CMMI(能力成熟度模型集成) 1.2.1 真题 2 软件过程模型 2.1 瀑布模型 2.2 V模型 2.2.1 真题 2.3 增量模型 2.3.1 真题 2.4 演化模型 2.5 …

Bartender 5 for Mac 菜单栏管理软件 安装教程【保姆级教程,操作简单小白轻松上手使用】

Mac分享吧 文章目录 Bartender 5 for Mac 菜单栏管理软件 安装完成&#xff0c;软件打开效果一、Bartender 5 菜单栏管理软件 Mac电脑版——v5.2.3⚠️注意事项&#xff1a;1️⃣&#xff1a;下载软件2️⃣&#xff1a;安装软件3️⃣&#xff1a;打开软件&#xff0c;根据自己…

C#/.NET/.NET Core优秀项目和框架2024年10月简报

前言 每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架&#xff08;每周至少会推荐两个优秀的项目和框架当然节假日除外&#xff09;&#xff0c;推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等&#xff08;打不开或者打开GitHub很慢的同学可以优先查看…

OWASP TOP10 OSS 风险:开源软件安全指南

OWASP OSS 列表提供了旨在绕过 CVE 目录等滞后指标的建议&#xff0c;并为安全从业者提供了安全使用 OSS 组件的指南。 在最近的一些暴露的漏洞和风险之后&#xff0c;对开源软件 &#xff08;OSS&#xff09;的安全和使用方式进行批判性审视的呼声越来越高&#xff0c;特别是 …

数据转换 | Matlab基于SP符号递归图(Symbolic recurrence plots)一维数据转二维图像方法

目录 基本介绍程序设计参考资料获取方式 基本介绍 Matlab基于SP符号递归图&#xff08;Symbolic recurrence plots&#xff09;一维数据转二维图像方法 符号递归图(Symbolic recurrence plots)是一种一维时间序列转图像的技术&#xff0c;可用于平稳和非平稳数据集;对噪声具有…

01.如何用DDD重构老项目

学习资料来源&#xff1a;DDD独家秘籍视频合集 https://space.bilibili.com/24690212/channel/collectiondetail?sid1940048&ctype0 文章目录 动机DDD与重构实践重构? 重写从一开始就采用DDD重构步骤1. 添加领域模块2.分离出有价值的代码3.迁移到领域模块4.重复2,3 动机 …

【uni-app】创建自定义模板

1. 步骤 打开自定义模板文件夹 在此文件夹下创建模板文件&#xff08;注意后缀名&#xff09; 重新点击“新建页面” 即可看到新建的模板 2. 注意事项 创建的模板必须文件类型对应&#xff08;vue模板就创建*.vue文件, uvue模板就创建*.uvue文件&#xff09;

本地部署开源在线即时通讯软件Fiora打造个人私密聊天室

文章目录 前言1.关于Fiora2.安装Docker3.本地部署Fiora4.使用Fiora5.cpolar内网穿透工具安装6.创建远程连接公网地址7.固定Uptime Kuma公网地址 前言 相信大家在聊天时候总是很没安全感&#xff0c;比如在和小姐妹背着男朋友聊一些不能说的坏话&#xff0c;或者背着女朋友和兄…

【开发工具——依赖管理工具——Maven】

1. Maven介绍 Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model&#xff0c;POM) 的概念&#xff0c;Maven 可以从一条中心信息管理项目的构建、报告和文档。 对于开发者来说&#xff0c;Maven 的主要作用主要有 3 个&#xff1a; …

bootstrap应用2——计算第n个观测在(or 不在)自助法样本里的概率

#计算第四个观测在自助法样本里的概率 store<-rep(NA,10000) for (i in 1:10000){store[i]<-sum(sample(1:100,repTRUE)4)>0 } #讨论第四个观测 mean(store) #计算第n个观测在自助法样本里的概率 boot <- function(n, N){return(1-(1-1/N)^N) }#计算第n个观测在自助…

基于 GADF+Swin-CNN-GAM 的高创新扰动信号识别模型!

往期精彩内容&#xff1a; Python-电能质量扰动信号数据介绍与分类-CSDN博客 Python电能质量扰动信号分类(一)基于LSTM模型的一维信号分类-CSDN博客 Python电能质量扰动信号分类(二)基于CNN模型的一维信号分类-CSDN博客 Python电能质量扰动信号分类(三)基于Transformer的一…

Docker可视化工具 Portainer 安装及配置

文章目录 拉取镜像安装和启动容器访问 Portainer设置密码完后即代表安装完毕安装完成 拉取镜像 rootyx-PowerEdge-R730:~# docker pull portainer/portainer Using default tag: latest latest: Pulling from portainer/portainer Digest: sha256:47b064434edf437badf7337e516…

废品回收小程序搭建,互联网回收行业的特点

随着社会经济的快速发展&#xff0c;人们的生活水平大幅提高&#xff0c;废品、可回收物也在逐年增加&#xff0c;为行业的发展提高了基础。同时&#xff0c;国家对回收行业的扶持力度在不断增加&#xff0c;废品回收市场拥有广阔的发展机遇。对于入局者来说&#xff0c;行业隐…

【JAVA 笔记】08 ch05_program_control_structure

第5章 程序控制结构 程序流程控制介绍 if 分支 switch 分支结构 for 循环控制 while 循环控制 do..while 循环控制 跳转控制语句-break 跳转控制语句-continue 跳转控制语句-return 第5章 程序控制结构 程序流程控制介绍 顺序控制 分支控制 循环控制 if 分支 switch 分支结…

配置elk插件安全访问elk前台页面

编辑els配置文件vim elasticsearch.yml,添加以下配置文件 用elk用户&#xff0c;启动els服务 关闭防火墙&#xff0c;查看els启动是否成功&#xff0c;通过是否启动java进程来判断 或者通过查看是否启动9200和9300端口来判断是否启动 交互模式启动密码配置文件interactive表示交…