当前位置: 首页 > news >正文

Andorid 事件分发机制案例实操与解析

文章目录

  • 为什么要理解Android事件分发机制?
      • 滑动冲突类问题
  • 我们以什么开始?
    • 代码如下:
      • activity xml 代码:
      • Activity代码:
      • item_user.xml代码
    • 修改后代码如下:
      • Activity xml
      • activity代码
      • item_gift.xml
    • 问题出现了
  • Android 事件分发机制
  • 实现我们的功能
    • 再看一下我们面临的问题
      • 尝试解决
        • 1、 从OnTouchListener入手
        • 2、重写RecyclerView的OnTouch方法
        • 3、重写RecyclerView的OnTouch方法并调用其onTouch方法。
      • 尝试其他方案
        • 1、 在onInterceptTouchEvent中直接返回false?
        • 2、直接在dispatchTouchEvent中处理可以吗?
        • 3、在OnTouch中依然直接返回false,同时自己重置scrollState可以吗?

为什么要理解Android事件分发机制?

我最开始去了解Android事件分发机制,是在遇到滑动冲突的时候,相信很多朋友也是在遇到滑动冲突时候去深入了解该机制的,但是实际上,除了解决滑动冲突,它还能处理很多复杂的问题。以下将列出一系列相关问题,后面会根据举几个笔者最近遇到的问题,以及解决的思路。

滑动冲突类问题

  1. RecyclerView 与 ScrollView嵌套使用,同方向上的滑动冲突
  2. ViewPager 与 水平滚动相关View的联合使用
    事件传递时机类问题:
  3. 如何让RecyclerView无法滚动?
  4. 如何让RecyclerView的子项无法点击?
  5. 如何在RecyclerView重叠使用时,设置了点击事件的View都可以响应?
    ……

我们以什么开始?

这里我先设定一个需求背景:
我有一个用户头像列表,上边会展示用户的基本信息:用户头像,头像装饰,用户名,点击用户头像可以弹出用户信息(这里用Toast代替)。目前实现的UI如下
在这里插入图片描述

代码如下:

activity xml 代码:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".event.EventDispatchActivity"><androidx.recyclerview.widget.RecyclerViewandroid:layout_marginTop="20dp"android:id="@+id/rv_users"android:layout_width="match_parent"android:layout_height="match_parent"/></FrameLayout>

Activity代码:

class EventDispatchActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_event_dispatch)val itemDecoration = object : ItemDecoration(){override fun getItemOffsets(outRect: Rect,view: View,parent: RecyclerView,state: RecyclerView.State) {super.getItemOffsets(outRect, view, parent, state)outRect.bottom = 40}}findViewById<RecyclerView>(R.id.rv_users)?.let {it.adapter = UserAdapter()it.layoutManager = GridLayoutManager(it.context, 4)it.addItemDecoration(itemDecoration)}}class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {class ViewHolder(view: View): RecyclerView.ViewHolder(view) {val ivUser = itemView.findViewById<ImageView>(R.id.iv_user)val tvUser = itemView.findViewById<TextView>(R.id.tv_name)}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false))}override fun onBindViewHolder(holder: ViewHolder, position: Int) {val name = "user $position"holder.ivUser.setOnClickListener {Toast.makeText(holder.ivUser.context, name, Toast.LENGTH_SHORT).show()}holder.tvUser.text = name}override fun getItemCount(): Int {return 16}}
}

item_user.xml代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:gravity="center_horizontal"><ImageViewandroid:layout_width="48dp"android:layout_height="48dp"android:id="@+id/iv_user"android:src="@drawable/user"app:tint="@color/purple_500" /><TextViewandroid:layout_marginTop="10dp"android:layout_width="wrap_content"android:layout_height="20dp"android:id="@+id/tv_name"android:text="用户名"android:singleLine="true"/></LinearLayout>

现在有一个新的需求,需要在每两个头像之间加上红包展示,如果点击红包可以弹出红包弹窗(此处用Toast代替)。方案当然可以选择,在原来的Item上加中间的红包,但是此处,我选择的方案是,新创建一个RecyclerView,专门展示红包。(不要考虑方案的优劣,只是当时情况需要选择此方案)新的UI展示如下:

在这里插入图片描述

修改后代码如下:

Activity xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".event.EventDispatchActivity"><androidx.recyclerview.widget.RecyclerViewandroid:layout_marginTop="20dp"android:id="@+id/rv_users"android:layout_width="match_parent"android:layout_height="match_parent"/><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_user_gift"android:layout_marginTop="20dp"android:layout_width="match_parent"android:layout_height="match_parent"/></FrameLayout>

activity代码

class EventDispatchActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_event_dispatch)val itemDecoration = object : ItemDecoration(){override fun getItemOffsets(outRect: Rect,view: View,parent: RecyclerView,state: RecyclerView.State) {super.getItemOffsets(outRect, view, parent, state)outRect.bottom = 40}}findViewById<RecyclerView>(R.id.rv_users)?.let {it.adapter = UserAdapter()it.layoutManager = GridLayoutManager(it.context, 4)it.addItemDecoration(itemDecoration)}findViewById<RecyclerView>(R.id.rv_user_gift)?.let {it.adapter = GiftAdapter()it.layoutManager = GridLayoutManager(it.context, 2)it.addItemDecoration(itemDecoration)}}class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {class ViewHolder(view: View): RecyclerView.ViewHolder(view) {val ivUser = itemView.findViewById<ImageView>(R.id.iv_user)val tvUser = itemView.findViewById<TextView>(R.id.tv_name)}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false))}override fun onBindViewHolder(holder: ViewHolder, position: Int) {val name = "user $position"holder.ivUser.setOnClickListener {Toast.makeText(holder.ivUser.context, name, Toast.LENGTH_SHORT).show()}holder.tvUser.text = name}override fun getItemCount(): Int {return 16}}class GiftAdapter : RecyclerView.Adapter<GiftAdapter.ViewHolder>() {class ViewHolder(view: View): RecyclerView.ViewHolder(view) {val giftView = itemView.findViewById<ImageView>(R.id.iv_gift)}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_gift, parent, false))}override fun onBindViewHolder(holder: ViewHolder, position: Int) {val gift = "gift $position"holder.giftView.setOnClickListener {Toast.makeText(holder.giftView.context, gift, Toast.LENGTH_SHORT).show()}}override fun getItemCount(): Int {return 8}}}

item_gift.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="78dp"android:orientation="vertical"android:gravity="center_horizontal"><ImageViewandroid:id="@+id/iv_gift"android:layout_width="36dp"android:layout_height="36dp"android:layout_marginTop="6dp"android:src="@drawable/gift"/></LinearLayout>

问题出现了

按照上面的实现,展示上是没问题的,但是用户操作上出现了问题。
首先说一下我想要的结果:

  1. 用户头像可点击,点击后可出用户信息弹窗
  2. 红包可点击,点击后可出红包弹窗

现在的问题是:
红包可点击(符合需求),但是用户头像不可点击(不符合需求)了。

问题原因推测:

  1. 只有用户列表时,用户信息是可以点击的
  2. 加上新的列表后用户信息点不了,应该是点击事件被顶层的红包层拦截了。
  3. 目标方案:顶层只有红包区域可点击,其他位置要正常传递给下面的头像。

但是,我应该从哪开始改起呢?带着上面的问题,我们开始研究分发机制,达到最终上面的效果。

Android 事件分发机制

Android 的触摸事件,是从屏幕硬件触发,最终到达目前正在展示的Activity的,至于这期间如何传递的,此处不做讨论,我们讨论的起点,从Activity接收到触摸事件开始。
流程图
参考文档:
Android 事件传递相关流程图
源码分析
参考文档:
Android 事件分发源码解析(基于API31)

实现我们的功能

事件传递相关的流程与 源码分析在上面的文档中已经看到了,接下来,我们来实现一下,文章开头提到的实现目标。

根据问题的前提,我们知道这是两个RecyclerView重叠摆放的问题导致的,当然我们可以自定义ViewGroup,重新定义两个View的摆放规则,但是那不在我们本次讨论范围内,我们只考虑当前的实现方案,如何最终实现这个功能。

再看一下我们面临的问题

最上层的RecyclerView会拦截所有的事件分发,所以最上层的RecyclerView 中的View会被分发到事件,而下边的RecyclerView不能接收到事件。

在这里插入图片描述

那么有没有什么办法,可以让上边的RecyclerView 只在有View的地方响应点击事件?

尝试解决

1、 从OnTouchListener入手

首先肯定是不会考虑重写一个View的,那么最简单的方案是设置一个OnTouchListener,但是这种方案可不可以呢?不可以。
原因:如果我在OnTouchListener的onTouch方法返回true,那么礼物的点击事件无法执行了,如果返回false,和没加一样。

2、重写RecyclerView的OnTouch方法

既然没法子通过OnTouchListener处理,那么修改onTouchEvent方法,直接返回false,也就是不处理点击事件,但是让RecyclerView只分发,不处理点击事件,这可行不可行呢?

class DispatchRecyclerView : RecyclerView {constructor(context: Context) : super(context)constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr)override fun onTouchEvent(e: MotionEvent?): Boolean {return false}
}

尝试修改了一下,是可行的。
但是出现了一个新的问题,多次极端滑动(一直往下滑,但实际上它下面没有内容了)之后,不再执行到RecyclerView的onTouch方法了,因为它的滑动状态一直没有被重置成IDLE,一直是DRAGING状态。DRAGING状态时,它的OnTouch里边返回false也没用,因为在拦截那一步,已经被处理成拦截了。

也就是:在某一状态下,RecyclerView的scrollState 变成了 DRAGGING,且,onInterceptTouchEvent反回了true。而RecyclerView 没有重写dispatchTouchEvent,那么哦我们知道,如果“onInterceptTouchEvent”返回了true那么它将不会进行事件分发。

原因是,在onInterceptToucEvent中,RecyclerView把scrollState置成了DRAGGING,在ACTION_UP的时候,是在onTouch里边重置的滚动状态,而我们直接在onTouch中返回了false,导致重置scrollState步骤没有执行到。

3、重写RecyclerView的OnTouch方法并调用其onTouch方法。

class DispatchRecyclerView : RecyclerView {constructor(context: Context) : super(context)constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr)override fun onTouchEvent(e: MotionEvent?): Boolean {super.onTouchEvent(e) // 执行操作return false // 同时让父布局继续往下分发}
}

当我们添加了该方法,发现问题解决了~

尝试其他方案

1、 在onInterceptTouchEvent中直接返回false?

在这个方法中返回false,不行,因为我们知道,即使不拦截,如果RecyclerView发现没有可以处理事件的子View,最后,仍然会回调自己的onTouchEvent。

2、直接在dispatchTouchEvent中处理可以吗?

其实是可以的,这是所有事件分发的入口,但是如果我们重写整个方法,需要把整个找子View的过程都写一遍,这得不偿失。

3、在OnTouch中依然直接返回false,同时自己重置scrollState可以吗?

这种方式是可以的,但需要写的代码可能会更多。

相关文章:

Andorid 事件分发机制案例实操与解析

文章目录为什么要理解Android事件分发机制&#xff1f;滑动冲突类问题我们以什么开始&#xff1f;代码如下&#xff1a;activity xml 代码&#xff1a;Activity代码&#xff1a;item_user.xml代码修改后代码如下&#xff1a;Activity xmlactivity代码item_gift.xml问题出现了An…...

Git 版本控制/项目迭代

一、Git的作用/为什么要进行版本控制&#xff1f; 什么是项目迭代&#xff1f; 搞开发的时候我们不是一次性就做好平台的所有功能&#xff0c;而是先上线一个功能差不多的版本让用户用着&#xff0c;然后不断迭代、修改&#xff0c;上线新的版本&#xff0c;所以一个项目就会…...

智慧农业大数据项目建设方案

智慧农业大数据项目建设方案 目录 项目概述.................................... 6...

【数据结构专栏】动态扩容顺序栈详解

&#x1f48c; 博客内容&#xff1a;顺序栈的原理详解 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准前段&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&#xff01; &#x1f496; 欢迎大家&#xff1a;这…...

Linux命令·ifconfig

许多windows非常熟悉ipconfig命令行工具&#xff0c;它被用来获取网络接口配置信息并对此进行修改。Linux系统拥有一个类似的工具&#xff0c;也就是ifconfig(interfaces config)。通常需要以root身份登录或使用sudo以便在Linux机器上使用ifconfig工具。依赖于ifconfig命令中使…...

大器晚成我服刘邦,48岁才开始创业

读史使人明智&#xff0c;周末放下手机&#xff0c;静下心来读点人文历史。大器晚成我最佩服刘邦&#xff0c;48岁才开始创业 。在此之前&#xff0c;他是一个出身平凡的农民&#xff0c;早年曾多次失败和受挫。刘邦最后能够战胜项羽&#xff0c;常常让人觉得匪夷所思&#xff…...

AndroidStudio快捷键

动态演示&#xff1a;https://blog.csdn.net/weixin_67276852/article/details/124159843?spm1000.2115.3001.6382&utm_mediumdistribute.pc_feed_v2.none-task-blog-hot-11.pc_personrec&depth_1-utm_sourcedistribute.pc_feed_v2.none-task-blog-hot-11.pc_personre…...

机械硬盘的工作原理

每个磁盘的表面都有高速扫过的记录磁头。 每个磁盘上都覆盖着一层薄薄的微小的磁化金属粒。 数据以一种肉眼无法分辨的形式存在。很多组微小颗粒形成的磁化图案记录形成了数据。每一组&#xff0c;又称之为比特&#xff08;bit&#xff09;。 所有微粒都按照自身的磁性排列…...

掌握TypeScript:10个最佳实践提高代码质量

TypeScript 是一种强类型的 JavaScript 超集&#xff0c;提供了很多优秀的工具和语言特性&#xff0c;可以帮助开发者提高代码质量和开发效率。在本文中&#xff0c;我们将介绍 10 个 TypeScript 最佳实践&#xff0c;帮助初级和中级的 Web 前端开发工程师更好地使用 TypeScrip…...

【面试】Kafka面试题

文章目录1、Kafka是什么&#xff1f;2、partition的数据文件&#xff08;offffset&#xff0c;MessageSize&#xff0c;data&#xff09;3、数据文件分段 segment&#xff08;顺序读写、分段命令、二分查找&#xff09;4、负载均衡&#xff08;partition会均衡分布到不同broker…...

【C++学习】map和set的使用

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《C学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; map和set的使用&#x1f308;关联式容器⚡键对值&#x1f308;set⚡构造函数⚡增删查改&#x1f308;…...

企业电子招投标采购系统——功能模块功能描述+数字化采购管理 采购招投标

​ 功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外…...

4.6--计算机网络之TCP篇之TCP的连接建立--(复习+深入)---好好沉淀,加油呀

1.TCP 三次握手过程是怎样的&#xff1f; TCP 是面向连接的协议&#xff0c;所以使用 TCP 前必须先建立连接&#xff0c;而建立连接是通过三次握手来进行的 1.一开始&#xff0c;客户端和服务端都处于 CLOSE 状态。先是服务端主动监听某个端口&#xff0c;处于 LISTEN 状态 2…...

Pytorch 数据产生 DataLoader对象详解

目录 1、Pytorch读取数据流程 2、DataLoader参数 3、DataLoader&#xff0c;Sampler和Dataset 4、sampler和batch_sampler 5、源码解析 6、RandomSampler(dataset)、 SequentialSampler(dataset) 7、BatchSampler(Sampler) 8、总结 9、自定义Sampler和BatchSampler 研…...

Linux文件系统介绍

一、简介 文件系统就是分区或磁盘上的所有文件的逻辑集合。 文件系统不仅包含着文件中的数据而且还有文件系统的结构&#xff0c;所有Linux 用户和程序看到的文件、目录、软连接及文件保护信息等都存储在其中。 不同Linux发行版本之间的文件系统差别很少&#xff0c;主要表现在…...

Java高频必背面试题基础篇02

一、Java 语⾔中关键字 static 的作⽤是什么&#xff1f; static 的主要作⽤有两个&#xff1a; &#xff08;1&#xff09;为某种特定数据类型或对象分配与创建对象个数⽆关的单⼀的存储空间。 &#xff08;2&#xff09;使得某个⽅法或属性与类⽽不是对象关联在⼀起&#xf…...

蓝桥杯—stm32g431rbt6串口中断和定时器输出pwm学习

目录 串口中断 定时器中断 输出pwm 串口中断 配置异步模式&#xff0c;使能中断&#xff0c;选择波特率。 串口接收中断开启 HAL_UART_Receive_IT(&huart1,data, 3); 回调函数&#xff1a; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huar…...

zed驱动的安装 及 遇到问题 及 ros标定

安装zed相机驱动 zed驱动官网 下载.run文件 chmod x ZED_SDK_Ubuntu18_cuda10.2_v4.0.1.zstd.run #换自己的版本号 ./ZED_SDK_Ubuntu18_cuda10.2_v4.0.1.zstd.run #换自己的版本号当遇到 zstd: not found … Decompression failed. 重新安装&a…...

打车代驾顺风车货车租运系统开发功能(司机端)

随着社会经济水平的提高&#xff0c;人们对于打车代驾服务要求也不断提高&#xff0c;更多的人愿意在手机上通过打车代驾APP小程序软件来预约叫车&#xff0c;选择打车代驾服务。打车代驾软件开发是基于广大用户的要求而产生的新型服务方式&#xff0c;满足大众预约出行需要&am…...

CT剂量及描述方法详细介绍

CT剂量和普通放射剂量的区别 普通放射剂量分布区域大&#xff0c;但一般集中在皮肤入射表面&#xff0c;用患者入射表面剂量&#xff08;ESD)来表征射线剂量&#xff1b; CT剂量分布在窄带内&#xff0c;边缘与中心分布不均匀&#xff1b;且属于多层扫描&#xff1b; 1、在理想…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...