Android 事件分发机制详解(下)

2.3 View事件分发机制

从上面 ViewGroup 事件分发机制知道,View事件分发机制从 dispatchTouchEvent() 开始

源码分析
/*** 源码分析: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;  }
源码总结

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

核心方法总结

主要包括: dispatchTouchEvent()onTouchEvent()

实例分析

在本示例中,将分析2种情况:

  1. 注册Touch事件监听 且 在 onTouch() 返回false
  2. 注册Touch事件监听 且 在 onTouch() 返回true
    分析1:注册Touch事件监听 且 在onTouch()返回false
代码示例
// 1. 注册Touch事件监听setOnTouchListener 且 在onTouch()返回false
button.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {System.out.println("执行了onTouch(), 动作是:" + event.getAction());return false;}
});
// 2. 注册点击事件OnClickListener()
button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {System.out.println("执行了onClick()");}
});
测试结果
执行了onTouch(), 动作是:0
执行了onTouch(), 动作是:1
执行了onClick()
测试结果说明
  • 点击按钮会产生两个类型的事件-按下View与抬起View,所以会回调两次 onTouch()
  • 因为 onTouch() 返回了false,所以事件无被消费,会继续往下传递,即调用 View.onTouchEvent()
  • 调用 View.onTouchEvent() 时,对于抬起View事件,在调用 performClick() 时,因为设置了点击事件,所以会回调 onClick()
    分析2:注册Touch事件监听 且 在onTouch()返回true
代码示例
// 1. 注册Touch事件监听setOnTouchListener 且 在onTouch()返回false
button.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {System.out.println("执行了onTouch(), 动作是:" + event.getAction());return true;}});
// 2. 注册点击事件OnClickListener()
button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {System.out.println("执行了onClick()");}});
测试结果
执行了onTouch(), 动作是:0
执行了onTouch(), 动作是:1
测试结果说明
  • 点击按钮会产生两个类型的事件-按下View与抬起View,所以会回调两次 onTouch()
  • 因为 onTouch() 返回true,所以事件被消费,不会继续往下传递, View.dispatchTouchEvent() 直接返回true;
  • 所以最终不会调用 View.onTouchEvent() ,也不会调用 onClick()

三. 事件分发机制流程总结

类型相关方法ActivityViewGroupView
事件分发dispatchTouchEvent
事件拦截oninterceptTouchEvent××
事件消费onTouchEvent

这个三个方法均有一个 boolean(布尔) 类型的返回值,通过返回 true 和 false 来控制事件传递的流程。
PS: 从上表可以看到 ActivityView都是没有事件拦截的,这是因为:

  • Activity 作为原始的事件分发者,如果 Activity 拦截了事件会导致整个屏幕都无法响应事件,这肯定不是我们想要的效果。
  • View最为事件传递的最末端,要么消费掉事件,要么不处理进行回传,根本没必要进行事件拦截。
View相关

Question: 为什么 View 会有 dispatchTouchEvent ?

A:View 可以注册很多事件监听器,例如:单击事件(onClick)、长按事件(onLongClick)、触摸事件(onTouch),并且View自身也有onTouchEvent方法,那么问题来了,这么多与事件相关的方法应该由谁管理?毋庸置疑就是 dispatchTouchEvent,所以 View 也会有事件分发。

Question: 与 View 事件相关的各个方法调用顺序是怎样的?

A:如果不去看源码,想一下让自己设计会怎样?

  • 单击事件(onClickListener) 需要两个事件(ACTION_DOWNACTION_UP)才能触发,如果先分配给onClick判断,等它判断完,用户手指已经离开屏幕,黄花菜都凉了,定然造成 View 无法响应其他事件,应该最后调用。(最后)
  • 长按事件(onLongClickListener) 同理,也是需要长时间等待才能出结果,肯定不能排到前面,但因为不需要ACTION_UP,应该排在onClick前面。(onLongClickListener> onClickListener)
  • 触摸事件(onTouchListener) , 如果用户注册了触摸事件,说明用户要自己处理触摸事件,这个应该排在最前面。(最前)、
  • View自身处理(onTouchEvent) 提供了一种默认的处理方式,如果用户已经处理好了,也就不需要了,所以应该排在onTouchListener后面。(onTouchListener > onTouchEvent)
    所以事件的调度顺序应该是 onTouchListener> onTouchEvent > onLongClickListener> onClickListener。
ViewGroup相关

ViewGroup(通常是各种Layout) 的事件分发相对来说就要麻烦一些,因为 ViewGroup 不仅要考虑自身,还要考虑各种ChildView,一旦处理不好就容易引起各种事件冲突,正所谓养儿方知父母难啊。
VIewGroup的事件分发流程又是如何的呢?
我们了解到事件是通过ViewGroup一层一层传递的,最终传递给View,ViewGroup要比它的 ChildView 先拿到事件,并且有权决定是否告诉要告诉ChildView。在默认的情况下 ViewGroup事件分发流程是这样的。

  1. 判断自身是否需要(询问 onInterceptTouchEvent 是否拦截),如果需要,调用自己的 onTouchEvent。
  2. 自身不需要或者不确定,则询问ChildView,一般来说是调用手指触摸位置的 ChildView。
  3. 如果子 ChildView不需要则调用自身的onTouchEvent。

用伪代码应该是这样子的:

// 点击事件产生后
// 步骤1:调用dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {boolean consume = false; //代表 是否会消费事件// 步骤2:判断是否拦截事件if (onInterceptTouchEvent(ev)) {// a. 若拦截,则将该事件交给当前View进行处理// 即调用onTouchEvent()去处理点击事件consume = onTouchEvent (ev) ;} else {// b. 若不拦截,则将该事件传递到下层// 即 下层元素的dispatchTouchEvent()就会被调用,重复上述过程// 直到点击事件被最终处理为止consume = child.dispatchTouchEvent (ev) ;}// 步骤3:最终返回通知 该事件是否被消费(接收 & 处理)return consume;}
}

安卓为了保证所有的事件都是被一个 View 消费的,对第一次的事件( ACTION_DOWN )进行了特殊判断,View 只有消费了 ACTION_DOWN 事件,才能接收到后续的事件(可点击控件会默认消费所有事件),并且会将后续所有事件传递过来,不会再传递给其他 View,除非上层 View 进行了拦截。如果上层 View 拦截了当前正在处理的事件,会收到一个 ACTION_CANCEL,表示当前事件已经结束,后续事件不会再传递过来。

核心要点
  1. 事件分发原理: 责任链模式,事件层层传递,直到被消费。
  2. View 的 dispatchTouchEvent主要用于调度自身的监听器和 onTouchEvent。
  3. View的事件的调度顺序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener。
  4. 不论View自身是否注册点击事件,只要 View 是可点击的就会消费事件。
  5. 事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关。
  6. ViewGroup 中可能有多个 ChildView 时,将事件分配给包含点击位置的 ChildView。
  7. ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),由 ChildView 消费。
  8. 一次触摸流程中产生事件应被同一 View 消费,全部接收或者全部拒绝。
  9. 只要接受 ACTION_DOWN 就意味着接受所有的事件,拒绝 ACTION_DOWN 则不会收到后续内容。
  10. 如果当前正在处理的事件被上层 View 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国科大-自然语言处理复习

自然语言处理复习 实体关系联合抽取流水线式端到端方法 检索式问答系统流水线方式信息检索&#xff08;IR&#xff09;阶段阅读理解&#xff08;RC&#xff09;阶段基于证据强度的重排基于证据覆盖的重排结合不同类型的聚合 端到端方式Retriever-Reader的联合学习基于预训练的R…

用Scala采集出行平台机票价格信息

年关将至&#xff0c;趁着过年&#xff0c;打算拖家带口的出去游玩一番&#xff0c;目前也没有什么计划&#xff0c;去哪里玩也比较随机。正好年底公司项目都已经完成差不多&#xff0c;利用空余时间&#xff0c;用爬虫爬取各大景点飞机票价格信息&#xff0c;选择景点不错机票…

【XR806开发板试用】单总线协议驱动DHT11温湿度传感器

1.昨天刚收到极速社区寄来的全志XR806开发板&#xff0c;之前用过很多全志的SOC芯片&#xff0c;但是像这种无线芯片还是第一次用。这次打算使用XR806芯片驱动一下DHT11温湿度传感器。 2.代码如下&#xff1a; #include "common/framework/platform_init.h" #inclu…

鸿蒙Harmony--AppStorage--应用全局的UI状态存储详解

无所求必满载而归&#xff0c;当你降低期待&#xff0c;降低欲望&#xff0c;往往会得到比较好的结果&#xff0c;把行动交给现在&#xff0c;用心甘情愿的态度&#xff0c;过随遇而安的生活&#xff0c;无论结果如何&#xff0c;都是一场惊喜的获得! 目录 一&#xff0c;定义 …

文献阅读:Large Language Models as Optimizers

文献阅读&#xff1a;Large Language Models as Optimizers 1. 文章简介2. 方法介绍 1. OPRO框架说明2. Demo验证 1. 线性回归问题2. 旅行推销员问题&#xff08;TSP问题&#xff09; 3. Prompt Optimizer 3. 实验考察 & 结论 1. 实验设置2. 基础实验结果 1. GSM8K2. BBH3.…

多测师肖sir___ui自动化测试po框架讲解版

po框架 一、ui自动化po框架介绍 &#xff08;1&#xff09;PO是Page Object的缩写&#xff08;pom模型&#xff09; &#xff08;2&#xff09;业务流程与页面元素操作分离的模式&#xff0c;可以简单理解为每个页面下面都有一个配置class&#xff0c; 配置class就用来维护页面…

如何用GPT/GPT4进行AI绘图?

详情点击链接&#xff1a;如何用GPT/GPT4进行AI绘图&#xff1f; 一OpenAI 1.最新大模型GPT-4 Turbo 2.最新发布的高级数据分析&#xff0c;AI画图&#xff0c;图像识别&#xff0c;文档API 3.GPT Store 4.从0到1创建自己的GPT应用 5. 模型Gemini以及大模型Claude2二定制自…

HCIA的网络地址转换NAT

NAT&#xff1a;网络地址转换 功能&#xff1a; 1.将大量的私有地址转换成公有地址&#xff08;节约IP地址&#xff09; 2.将一个IP地址转换成另一个IP地址&#xff08;公有的&#xff09;&#xff08;增加内部网络设备的安全性&#xff09;&#xff1a;比如有一个内网&#xf…

Python学习从0到1 day4 python格式化输出和输入方法

其实我不是我&#xff0c;我是青山辽阔 ——24.1.14 一、百分号形式的格式化输出 1.普通输出 #1.定义一些变量 name 陈浩南 age 25 address 广州市天河区#2.变量的输出&#xff08;普通输出&#xff09; print(name) print(age) print(address)#3.Python中&#xff0c;还允…

论文阅读:Attention is all you need

【最近课堂上Transformer之前的DL基础知识储备差不多了&#xff0c;但学校里一般讲到Transformer课程也接近了尾声&#xff1b;之前参与的一些科研打杂训练了我阅读论文的能力和阅读源码的能力&#xff0c;也让我有能力有兴趣对最最源头的论文一探究竟&#xff1b;我最近也想按…

Vue2-Vuex中State、Mutation及mapState辅助函数、mapMutations的基本用法

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 个人笔记&#xff0c;仅供参考。 state&#xff1a;全局共享的响应式数据 mutation:声明修改全局响应式数据…

分数1/1-1/2+1/3-1/4+1/5 …… + 1/99 - 1/100 求和

要求&#xff1a;计算1/1-1/21/3-1/41/5 …… 1/99 - 1/100 的值&#xff0c;打印出结果。 法一&#xff1a; 从要求&#xff0c;我们可以看出&#xff0c;计算内容分为正数和负数。对他们求和&#xff0c;可以通过分别求和再相加。 #include<stdio.h> int main() {fl…

9.云原生存储之ceph在k8s中应用及问题处理

云原生专栏大纲 文章目录 ceph应用场景ceph应用在k8s集群外使用块设备ceph客户端配置创建块挂载使用删除pool 在k8s集群内使用块设备创建块池和StorageClass使用存储 块存储映射问题处理问题现象事件分析csi-rbdplugin pod日志分析问题小结CentOS 7 编译安装 nbd 模块nbd内核模…

Chapter 9 运算符重载

目的&#xff1a;运用运算符重载 &#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&#x1f353;&…

MySQL8密码重置——Windows环境

1. 环境变量配置 加入操作会方便点&#xff0c;不用每次都要进入其安装目录 Path环境变量加入&#xff1a;%MYSQL_HOME%\bin 2. 停掉服务及服务安装 3. 创建my.ini MySQL安装目录&#xff08;C:\Program Files\MySQL\MySQL Server 8.0&#xff09;下创建my.ini文件 my.ini文…

有哪些品牌的超声波清洗机是值得入手的?超声波清洗机推荐

作为一个既对生活品质有追求&#xff0c;又只想躺平的懒人&#xff0c;一直在努力尝试让轻松的生活少一点绊脚石&#xff0c;而其中最重要的一个&#xff0c;就是清洗眼镜清洗日常生活的一些小物件&#xff0c;讲真&#xff0c;洗这些东西有时候就跟下班回家的KPI一样。白天不累…

《吐血整理》保姆级系列教程-玩转Fiddler抓包教程(6)-Fiddler状态面板详解

1.简介 按照从上往下&#xff0c;从左往右的计划&#xff0c;今天就轮到介绍和分享Fiddler的状态面板了。 2.状态面板概览 Fiddler的状态面板概览&#xff0c;如下图所示&#xff1a; 3.状态面板详解 Fiddler底端状态栏面板详解&#xff0c;如下图所示&#xff1a; 3.1Captu…

重新分区扩展C盘

电脑 – 管理 使用第三方工具&#xff1a;DiskGenius数据恢复及分区管理软件 要选择完成后重启 &#xff0c;如果这里忘记勾选&#xff0c;后面也会再次提醒并默认勾选重启 "调整后容量"是指图片上显示的非C盘之外的盘符的容量&#xff0c;这里指E盘大小 上面已经利…

vue知识-05

聊天室案例(django接口) # chat.hetm<<script src"/static/axios.js"></script><script src"/static/vue.js"></script><body> <div id"app"><h1>聊天室</h1><button click"handleS…

从事铁路工作保护足部,穿什么劳保鞋更安全

铁路运输在我国交通运输业中起着骨干作用&#xff0c;为国民经济的可持续发展和人口流动做出了巨大贡献。安全是铁路运输不可忽视的问题&#xff0c;在作业场地随处能见到“安全就是生命&#xff0c;责任重于泰山”的安全标语&#xff0c;由此可见安全问题是放在首位的。 铁路施…