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

Unity引擎UI滚动列表——滚动复用基础介绍

  大家好,我是阿赵。

一、滚动复用的介绍

  在制作游戏的过程中,经常会遇到一些需要显示数量比较大的数据的情况。比如说,一个排行榜,需要展示当前服务器前一千个玩家的排名。或者游戏的背包容量特别大,可以有几千个格子。
  在早期的游戏里面,这种情况一般会采取分页的做法,比如一页只显示几十条信息,通过翻页和跳转页,逐页浏览信息。但现在的游戏很少采取分页的做法,而是采用滚动列表的方式,玩家可以上下滑动在同一页里面看完所有的信息。
  以Unity引擎为例,通过Scroll View组件,就能轻松的实现上下滑动的效果。
在这里插入图片描述

  ScrollView的基本用法很简单,把需要展示的Item,都放在Content节点下,然后在Content上挂自动布局的组件和自动适配大小的组件,列表就可以滚动起来了。
在这里插入图片描述

  回到最开始的例子,假如需要展示的内容非常多,比如有1万个,那我们是不是就要生成1万个item放在Content下面呢?这样做显然是不行的。如果我们同时生成1万个item,而且item很复杂,那么生成的时候会很卡,也会很占内存。虽然实际显示的数量可能只有几个,但剩下的九千多个item还是需要通过Mask计算裁剪范围。
  实际的情况是,我们需要同时看到的item,可能只有几个,所以我们没有必要生成这么大数量的item,而只是生成观察范围内的有限几个item就可以了,通过上下滑动,把移除出范围的item移动到新进入范围的位置,并且把数据刷新成刚进入范围的数据显示就可以了。我习惯上把这种做法叫做滚动复用。

Unity引擎UI滚动复用1

  从上面的视频可以看到,我这里真的有1万个数据,而且可以任意的滑动,甚至任意的跳动到某个为止,从视觉上是完全看不出来有什么破绽。然后从Unity的Hierarchy窗口可以看出,实际上在Content节点下面的item数量就只有11个而已。
在这里插入图片描述

  为了方便观察,我把同时显示的数量减少一些,并且把Mask去掉

Unity引擎UI滚动复用6

  从视频的Scene视图可以看到,消失的Item会立刻移动到进入的位置,并且刷新显示。

二、 滚动复用的实现分析

1、计算可以滚动的范围

  滚动复用还是可以使用Unity自带的Scroll View组件。只是在Content节点上面,不需要再挂自动布局和自动大小的组件了。
  首先需要明白的一点是,Scroll View组件可以滚动的范围,是通过Content节点的大小来决定的
在这里插入图片描述

  在不使用滚动复用技术的时候,我们需要加自动布局和自动大小的组件,就是为了自动的计算出这个Content里面的内容总共需要多大的范围来显示,从而决定滚动的实际范围。
既然我们现在不需要自动大小了,所以我们可以通过计算,得出Content的大小。举个例子:
  假如我现在是一个竖向滚动的列表,每个item的宽度是200,高度是60,需要同时显示1000个item,item中间没有空隙。那么要刚好放下这1000个item,Content需要的大小就是宽度200,高度是60x1000=60000。
  假如在item中间需要有5个像素的间隙,那么Content的大小,宽度还是200,高度会变成了60x1000+5x(1000-1) = 64995。
  是不是很简单?只要这样算一下,把Content的大小算出来,并设置。这时候列表已经可以滚动起来了。虽然上面一个item也没有,但实际上它滚动的范围就是1000个item的高度。

2、 规定数据来源

  对于一个滚动显示的列表来说,它必须是有一个输入的数据源,并且是以一维数组的形式表现的。
  我这个例子里面,只是简单的输入了一个字符串的数组,而数组的内容就是“内容{序号}”。在实际的使用中,数据的来源会更复杂,比如是某个数据结构体的数组,结构体里面可以包含很多数据,需要在item上面显示很多内容。
  不过数据是否复杂其实并不影响滚动复用的实现,我这里只是为了说明原理。所以我准备了一个字符串数组,每一个数据对应数组里面的一个Index。在接下来的实现里面,每一个数据其实就是对应一个显示的item了。item的UI预设是预先做好的,对应需要显示的数据内容。

3、 计算每个item的范围

  在计算item范围之前,先要定一个对齐方式。比如我们都以左上角作为对齐
在这里插入图片描述

  然后item也是左上角对齐,并且Pivot的x是0,y是1。
在这里插入图片描述

  这样设置的好处是,当posX和posY都是0的时候,item刚好对齐了Content的左上角。然后随着posX增大item会一直往右移动,随着posY的一直减少(因为画布的Y轴是向上的,所以负数才是向下),item会一直往下移动。
  假如现在所有item已经按照正确的位置排列好了,那么每个item应该有一个相对于Content的坐标。这里需要记录item的左上角和右下角的坐标:
在这里插入图片描述

  如果用Vector4来表示这个item的最大最小值坐标,x和y就是左上角开始点的xy坐标。而z和w,就是右下角结束点的xy坐标。这种记录方法还是看个人习惯的,有些人喜欢记录开始点的xy坐标,还有记录item的宽高,也都是可以的,毕竟结束点的坐标其实就是开始点坐标加上宽度和高度而已。
  不过我为了下面的步骤能快速的得到起点和终点的实际坐标来计算item是否在范围内,所以记录了结束点坐标而没有记录宽高。
  这里需要注意一点,虽然Unity的Y轴是朝向上的,也就是说数据越往下就越小。但我们计算的时候,其实不需要硬要这样算的,我们就正常的算越往后的item坐标越大就行了,包括下面的计算范围也是,把计算的Y轴朝下,计算起来的思维就方便很多。只要在最后给item的坐标赋值的时候,把Y坐标取个负数就行。

4、 计算每个item是否在可以显示的范围内

  假设下图的黑框就是现在Content的实际范围,然后红框就是现在ScrollView的遮罩显示范围:
在这里插入图片描述

  由于Content的y坐标为0的时候,是刚好和遮罩的左上角重合的,所以可以认为,现在黑色的框往上移动的距离,也就是红框的顶部坐标,其实就是Content的posY坐标。然后红框的底部坐标,其实就是顶部坐标加上遮罩的高度。
  得到了当前需要显示范围(也就是红框)的顶部和底部坐标之后,通过之前初始化的时候已经计算好的每个item的开始坐标和结束坐标,就能很简单的对比出item是否在红框的显示范围呢了。

5、 刷新显示

  当知道了哪些index对应的item是在显示范围内的,接下来就很好办了,我们需要记录一个当前正在显示的index列表,然后和新计算出来的正要显示的index列表做对比,就可以知道,有哪些index对应的item是需要隐藏,哪些index对应的item是新显示出来的。对于没有变化的index,我们不需要处理,只需要先把需要隐藏的item隐藏掉,再把新增的item显示出来,然后通过index,得到他们的坐标和数据,把item摆在正确的位置,并且根据数据显示item的内容就行了。
  这里我的做法是维护一个对象池。当item不需要显示的时候,把它们存放到对象池里面,并且隐藏。当item需要新增的时候,从对象池里面取出来,并且显示。

6、 根据滚动事件触发刷新显示

  知道了怎样刷新显示,但在什么时候需要刷新呢?在拖动列表的过程中,按道理我们就需要不停的去检查item是否在显示范围内。
  为了达到拖动的过程中触发刷新,所以需要在Scroll View的OnValueChange回调里面注册一个方法,当值变化的时候,我们就重新计算并刷新显示。不过由于OnValueChange触发得很频繁,所以我们需要降低一下调用的频率:

  1. OnValueChange回调会传入一个Vector2参数,代表当前滑动的方向。因为我们做的例子是上下滑动,所以参数的y坐标为0时,就说明没有滑动,所以不需要刷新。
  2. 给一个调用的频率间隔,当回调的频率过快时也不需要立刻刷新。

7、 跳转到某个item

  在使用滚动列表的需求里面,很多时候会有需要定位的情况。比如在显示1000个玩家信息的排行榜里面,需要定位到自己所在的排名。
  所以在做这个滚动复用的时候,也需要加上一个定位的功能。

Unity引擎UI滚动复用2

  这个功能实现的思路很简单,因为每个item的坐标之前都已经记录了,所以需要跳转到某个item,其实直接去它的坐标,然后加上item的一半高度就可以了。
  不过这里有一种情况,假如index对应的item在最上面或者最下面的一段,而列表是会自动回弹的(比如第一个item不能低于显示范围左上角,如果把第一个item居中,列表就会回弹到左上角),那么就要计算一下,当出现会回弹的情况,直接把y坐标变成0,或者在最下面的就要用Content高度减去mask的高度。

三、 源码

  根据上面的思路,简单写了一个例子,是对应竖向滚动的。各位有兴趣可以自己思考一下,怎样改为横向滚动,或者可以用参数切换横竖向滚动。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class ScrollViewLoopCtrlBase : MonoBehaviour
{public ScrollRect scrollRect;public RectTransform content;protected float viewWidth;protected float viewHeight;private float itemWidth = 200;private float itemHeight = 60;protected float spaceTime = 0.02f;protected float lastTime = 0;protected List<string> dataList;protected float spacing = 0;protected List<int> currentShowIndexList;protected float contentHeight = 0;protected Dictionary<int, TestItem> showList;protected List<Vector4> itemPosList;protected Dictionary<int, List<TestItem>> poolDict;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){}#region 公共方法/// <summary>/// 设置滚动复用的数据/// </summary>/// <param name="dataList">数据源</param>/// <param name="spacing">item之间的间隔</param>public void SetData(List<string> dataList, float spacing = 0){this.dataList = dataList;this.spacing = spacing;currentShowIndexList = new List<int>();RectTransform selfRect = GetComponent<RectTransform>();viewWidth = selfRect.rect.width;viewHeight = selfRect.rect.height;itemPosList = new List<Vector4>();InitData();UpdateView();}public void OnPosChange(Vector2 vec){if(vec.y == 0){return;}if (Time.time - lastTime < spaceTime){return;}lastTime = Time.time;UpdateView();}/// <summary>/// 停止滚动/// </summary>public virtual void StopMove(){if (scrollRect){scrollRect.StopMovement();}}/// <summary>/// 设置列表停留在某个index的item居中/// </summary>/// <param name="ind"></param>public void SetIndexMiddle(int ind){StopMove();SetMiddleFun(ind);UpdateView();}#endregion/// <summary>/// 根据传入的数据初始化滚动列表/// </summary>protected virtual void InitData(){int count = dataList.Count;for (int i = 0; i < count; i++){float startPos = i * itemHeight;if (i > 0){startPos += spacing * i;}itemPosList.Add(new Vector4(0, startPos, 0, startPos + itemHeight));}contentHeight = itemHeight * count;if (count > 1){contentHeight += spacing * (count - 1);}content.sizeDelta = new Vector2(viewWidth, contentHeight);}/// <summary>/// 设置index居中的具体实现/// </summary>/// <param name="ind"></param>protected virtual void SetMiddleFun(int ind){if (dataList == null || dataList.Count == 0){return;}if (ind < 0){ind = 0;}else if (ind >= dataList.Count){ind = dataList.Count - 1;}float halfScreenHeight = viewHeight / 2;float posY = itemPosList[ind].y - halfScreenHeight + (itemPosList[ind].w - itemPosList[ind].y) / 2;if (posY < 0){posY = 0;}else if (contentHeight - posY < viewHeight){posY = contentHeight - viewHeight;}content.anchoredPosition = new Vector2(0, posY);}/// <summary>/// 刷新列表显示/// </summary>protected virtual void UpdateView(){List<int> newShowList = new List<int>();float contentStartY = content.anchoredPosition.y;float contentEndY = contentStartY + viewHeight;for (int i = 0; i < dataList.Count; i++){if (CheckItemIsInArea(contentStartY, contentEndY,i)){newShowList.Add(i);}}if (currentShowIndexList == null){AddItemsToShow(newShowList);currentShowIndexList = newShowList;return;}List<int> removeList = new List<int>();for (int i = 0; i < currentShowIndexList.Count; i++){if (newShowList.IndexOf(currentShowIndexList[i]) < 0){removeList.Add(currentShowIndexList[i]);}}if (removeList.Count > 0){RemoveItemsFromShowList(removeList);}List<int> newList = new List<int>();for (int i = 0; i < newShowList.Count; i++){if (currentShowIndexList.IndexOf(newShowList[i]) < 0){newList.Add(newShowList[i]);}}if (newList.Count > 0){AddItemsToShow(newList);}currentShowIndexList = newShowList;}/// <summary>/// 把对应index的item移除显示/// </summary>/// <param name="indList"></param>protected void RemoveItemsFromShowList(List<int> indList){if (showList == null || showList.Count == 0){return;}for (int i = 0; i < indList.Count; i++){if (showList.ContainsKey(indList[i])){TestItem item = showList[indList[i]];showList.Remove(indList[i]);ReturnToPool(item);}}}/// <summary>/// 把对应index的item添加到显示/// </summary>/// <param name="indList"></param>protected void AddItemsToShow(List<int> indList){if (showList == null){showList = new Dictionary<int, TestItem>();}for (int i = 0; i < indList.Count; i++){int id = indList[i];if (showList.ContainsKey(id) == false){TestItem item = GetFromPoolById(id);showList.Add(id, item);item.SetData(id, dataList[id]);item.rect.anchoredPosition = new Vector2(itemPosList[id].x, -itemPosList[id].y);}}}/// <summary>/// 检查某个序号的item是否在显示范围内/// </summary>/// <param name="contentStartY"></param>/// <param name="contentEndY"></param>/// <param name="itemIndex"></param>/// <returns></returns>protected bool CheckItemIsInArea(float contentStartY, float contentEndY, int itemIndex){Vector4 itemPos = itemPosList[itemIndex];if (itemPos.y <= contentStartY && itemPos.w >= contentStartY){return true;}if (itemPos.y >= contentStartY && itemPos.w <= contentEndY){return true;}if (itemPos.y <= contentEndY && itemPos.w >= contentEndY){return true;}return false;}#region 对象池/// <summary>/// 通过item类型从对象池获取对象/// </summary>/// <param name="itemType"></param>/// <returns></returns>protected TestItem GetFromPool(int itemType){if (poolDict == null || poolDict.ContainsKey(itemType) == false || poolDict[itemType].Count == 0){string itemName = GetItemNameByItemType(itemType);Object obj = Resources.Load(itemName);GameObject go = (GameObject)GameObject.Instantiate(obj, content.transform);TestItem item = go.GetComponent<TestItem>();item.type = itemType;return item;}else{List<TestItem> poolList = poolDict[itemType];TestItem item = poolList[0];poolList.RemoveAt(0);item.gameObject.SetActive(true);return item;}}/// <summary>/// 这里是临时测试资源,写死了几个item类型对应的item名字,用于加载/// </summary>/// <param name="itemType"></param>/// <returns></returns>private string GetItemNameByItemType(int itemType){string itemName = "";switch (itemType){case 1:itemName = "testItem";break;case 2:itemName = "testItem2";break;case 3:itemName = "testItem3";break;}return itemName;}/// <summary>/// 把Item回收到对象池/// </summary>/// <param name="item"></param>protected void ReturnToPool(TestItem item){item.gameObject.SetActive(false);if (poolDict == null){poolDict = new Dictionary<int, List<TestItem>>();}if (poolDict.ContainsKey(item.type) == false){poolDict.Add(item.type, new List<TestItem>());}List<TestItem> poolList = poolDict[item.type];if (poolList.IndexOf(item) < 0){poolList.Add(item);}}/// <summary>/// 正常的滚动复用可能会使用到不同的Item,这里定义一个通过id获取item的方法,用于在不同需求下重写/// </summary>/// <param name="id"></param>/// <returns></returns>protected virtual TestItem GetFromPoolById(int id){return GetFromPool(1);}#endregion}

相关文章:

Unity引擎UI滚动列表——滚动复用基础介绍

大家好&#xff0c;我是阿赵。 一、滚动复用的介绍 在制作游戏的过程中&#xff0c;经常会遇到一些需要显示数量比较大的数据的情况。比如说&#xff0c;一个排行榜&#xff0c;需要展示当前服务器前一千个玩家的排名。或者游戏的背包容量特别大&#xff0c;可以有几千个格子。…...

在 Windows 11 WSL (Ubuntu 24.04.1 LTS) | Python 3.12.x 下部署密码学库 charm

1. 在 Windows 11 上部署 Ubuntu (WSL) 由于作者没有高性能的 Ubuntu 服务器或个人电脑&#xff0c;且公司或学校提供的 Ubuntu 服务器虽然提供高性能 GPU 等硬件配置但通常不会提供 root 权限&#xff0c;因而作者通过在搭载了 Windows 11 的个人电脑上启动 Ubuntu (WSL) 来进…...

【六足机器人】01功能开发

包含&#xff1a;WIFI模块、GPS模块、语言模块、调试信息接口。 一、硬件连接 huart4&#xff08; PA0、 PA1 &#xff09;与GPS模块连接。 huart3&#xff08;PB10、PB11&#xff09;与ESP8266模块连接。 huart2&#xff08; PA2、 PA3 &#xff09;与语音模块连接。 hu…...

notepad++安装教程(超详细)

1.下载地址&#xff08;可以私信博主&#xff09; https://notepad-plus.en.softonic.com/download 2.解压安装...

创建简单的 PL/pgSQL 存储过程

文章目录 创建简单的 PL/pgSQL 存储过程CREATE OR REPLACE FUNCTIONadd_two_numbers(a integer, b integer)RETURNS integerAS$$ ... $$函数体LANGUAGE plpgsql 创建带有 IN 和 OUT 参数的存储过程创建修改数据的存储过程创建带有异常处理的复杂存储过程 在 PostgreSQL 中&…...

Java项目实战II基于微信小程序的无中介租房系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 随着城市化进程的加速&#xff0c;租房市场日益繁荣&a…...

Node.js实现WebSocket教程

Node.js实现WebSocket教程 1. WebSocket简介 WebSocket是一种在单个TCP连接上提供全双工通信的协议&#xff0c;允许服务器和客户端之间进行实时、双向通信。本教程将详细讲解如何在Node.js中实现WebSocket。 2. 技术选型 我们将使用ws库来实现WebSocket服务器&#xff0c;…...

Docker Compose实战一( 轻松部署 Nginx)

通过过前面的文章&#xff08;Docker Compose基础语法&#xff09;你已经掌握基本语法和常用指令认识到Docker Compose作为一款强大工具的重要性&#xff0c;它极大地简化了多容器Docker应用程序的部署与管理流程。本文将详细介绍如何使用 Docker Compose 部署 Nginx&#xff0…...

hive分区分桶、数据倾斜总结

一、hive的基本概念 hive是一个构建在hadoop上的数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表并提供数据查询功能 二、hive的特点 &#xff08;1&#xff09;数据是存储在hdfs上 &#xff08;2&#xff09;底层是将sql转换为MapReduce任务进行计算 …...

unity打包到安卓帧率降低

这个问题遇到过很多次了我的做法就是直接设置Application.targetFrameRate60 参考...

【Python3】装饰器 自动更新缓存

自动更新缓存的需求场景 在某些应用中&#xff0c;我们可能需要定期从外部数据源&#xff08;如 Redis 或者远程接口&#xff09;拉取数据&#xff0c;并将其缓存在内存中。当有其他代码需要访问这些数据时&#xff0c;可以立刻从内存获取最新数据&#xff0c;而无需每次都进行…...

通过EPEL 仓库,在 CentOS 7 上安装 OpenResty

通过EPEL 仓库&#xff0c;在 CentOS 7 上安装 OpenResty 通过EPEL 仓库&#xff0c;在 CentOS 7 上安装 OpenResty步骤 1: 安装 EPEL 仓库步骤 2: 安装 OpenResty步骤 3: 启动 OpenResty步骤 4: 设置开机自启步骤 5: 验证安装说明 通过EPEL 仓库&#xff0c;在 CentOS 7 上安装…...

[RabbitMQ] RabbitMQ常见应用问题

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...

每日速记10道java面试题13-MySQL篇

其他资料 每日速记10道java面试题01-CSDN博客 每日速记10道java面试题02-CSDN博客 每日速记10道java面试题03-CSDN博客 每日速记10道java面试题04-CSDN博客 每日速记10道java面试题05-CSDN博客 每日速记10道java面试题06-CSDN博客 每日速记10道java面试题07-CSDN博客 每…...

乐鑫科技嵌入式面试题及参考答案(3万字长文)

嵌入式开发为什么用 C 语言,而不用 C++ 语言? 在嵌入式开发中,C 语言被广泛使用而 C++ 相对少用有以下一些原因。 首先,C 语言具有更高的效率。嵌入式系统通常资源受限,包括处理器速度、内存容量等。C 语言的代码生成效率高,能够生成紧凑的机器码,占用较少的内存空间和处…...

Leetcode 每日一题 56.合并区间

目录 问题描述 示例 示例 1 示例 2 问题分析 算法设计 步骤 1&#xff1a;排序 步骤 2&#xff1a;合并区间 步骤 3&#xff1a;返回结果 过题图片 代码实现 复杂度分析 题目链接 结语 问题描述 给定一个区间数组 intervals&#xff0c;其中每个区间由两个整数 s…...

【Vue】v-model、ref获取DOM

目录 v-moel v-model的原理 v-model用在组件标签上 方式 defineModel()简写 ref属性 获取原生DOM 获取组件实例 nextTick() v-moel v-model&#xff1a;双向数据绑定指令 数据变了&#xff0c;视图跟着变&#xff08;数据驱动视图&#xff09;视图变了&#xff0c;数…...

Python 类的设计(以植物大战僵尸为例)

关于类的设计——以植物大战僵尸为例 一、设计类需满足的三要素1. 类名2. 属性和方法 二、以植物大战僵尸的为例的类的设计1. 尝试分类2. 创建对象调用类的属性和方法*【代码二】*3. 僵尸的继承 三、代码实现 一、设计类需满足的三要素 1. 类名 类名&#xff1a;某类事物的名…...

python中权重剪枝,低秩分解,量化技术 代码

目录 python中权重剪枝,低秩分解,量化技术 代码 权重剪枝 低秩分解 scipy 量化技术 python中权重剪枝,低秩分解,量化技术 代码 权重剪枝 权重剪枝可以通过PyTorch的torch.nn.utils.prune模块实现。以下是一个简单的例子: import torch import torch.nn as nn impor…...

调用matlab用户自定义的function函数时,有多个输出变量只输出第一个变量

很多朋友在使用matlab时&#xff0c;会使用或自己编辑多个function函数&#xff0c;来满足自己对任务处理的要求&#xff0c;但是在调用function函数时&#xff0c;会出现这个问题&#xff1a;调用matlab用户自定义的function函数时&#xff0c;有多个输出变量只输出第一个变量…...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践

6月5日&#xff0c;2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席&#xff0c;并作《智能体在安全领域的应用实践》主题演讲&#xff0c;分享了在智能体在安全领域的突破性实践。他指出&#xff0c;百度通过将安全能力…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

Docker拉取MySQL后数据库连接失败的解决方案

在使用Docker部署MySQL时&#xff0c;拉取并启动容器后&#xff0c;有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致&#xff0c;包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因&#xff0c;并提供解决方案。 一、确认MySQL容器的运行状态 …...