Unity UGUI 循环滑动列表实现思路及简单代码实现
前言:
自己之前其实比着书上实现过一个循环滑动列表,并且商业化到了项目里,上线后也在用。可后来怎么也想不起来细节,看着之前的代码也看不很懂。这次复习一下,希望真能理解它的本质,也记录一下,分享给所有需要的同学。
我们看源代码之所以看不懂,是因为没有理解它想干什么,只有理解了一段代码的思想,才能够顺着代码去读懂它的本意。要分清楚,什么是主要的,什么是附加的,就先弄明白一件事,它到底要做一件什么事。
为什么需要循环列表,很多面试在问,项目中也在用,但其实很多项目可能并没有真正遇到过这个问题。一般来说一个循环列表里元素在一百个以下,是没有什么问题的,在真机上也不会卡,直接把所有元素初始化然后塞到ScrollRect的content里就完事了,简单好用,还容易扩展。但我之前的项目,一个scrollrect里要塞500个元素,每个元素里,有几张图片,几个文本,性能就扛不住了,一滑动就卡的不行,那种情况下,才真正需要用到循环滑动列表。
其实循环滑动列表也不是唯一的选择,很多rpg根本就不做这个。而是用分页的方式显示背包元素,配合展示界面。所以最开始还是弄清需求,弄清数据规模,然后再进行技术选型。
思路:
之所以直接塞太多元素会卡顿,本质上还是因为,里边的元素的可见性都是true的,只是通过ScrollRect的Mask把它们裁剪掉了。而通过循环列表,不可见的元素并不会参与运算,所以性能会得到较大提高。所以核心思路还是计算,哪些元素可见,哪些元素不可见。
目前主流的做法都是,让Content的Size等于真实的Size,也就是说,你有100个元素,那content的大小就是100个元素布局后的宽和高。只是里边的元素并不都显示,我这里说的也是依赖于ScrollRect做的一种方法。因为自带的ScrollRect,可以方便的帮你处理好回弹。如果是自己不依赖于ScrollRect去通过监听鼠标滑动来实现,会麻烦很多。
所以核心要解决的问题,就是计算剔除,但其中又分为两部分,第一部分是初始化,第二部分是滑动的处理。两部分一起思考复杂度会高很多,因此我先推荐一种简单的思路,理解后自己举一反三即可。
我们首先要做的一件事,就是记录可视区域。可视区域可以用一个Rect结构来存储。可视区域指哪呢,一般就是指ViewPort,或者ScrollRect的大小本身。在这个矩形内的元素,可见,不在这个矩形内的元素,不可见。这里请注意,如果你的可视区域始终定义为(0, 0, width, height)那你里边的元素在滑动时坐标就需要配合Content的坐标做偏移。如果你的可视区域,是相对于content的左上角来计算的,那滑动过程中,指定index的元素的坐标是不会变的,但可视区域的起始坐标就一直在变。这点注意好即可。
然后我说一下最简单的理解核心算法的方法, 不考虑效率。首先假设你的滑动列表里要显示100个元素。那么你直接从0到100去遍历,每个元素的位置是不是可以算出来?这个相对简单,知道每行每列有多少个元素(可以走设置,也可以自动计算),那么指定的index位置你就可以算出来,然后看这个元素的rect和可视区域是否有交集,有的话,这个元素就需要被显示,否则就需要隐藏。显示的元素你从一个池子里去拿,不需要显示的元素你给它塞回池子里。这不就可以了?最开始不建议思考什么滑动的时候,下边的元素弄到上边去,不要这样理解,会增加复杂度,而是不可见的元素塞到池子里。可见的元素从池子里拿。
其实根据上边的思想,最简单的代码实现就有了,后边所处理的细节都是和外部系统进行沟通,以及算法优化而已。因为比如上述算法明显有一个缺点就是,每次滑动关心的元素太多,可见的元素在滑动过程中并没有离开可视界面,如果也从池子里从新拿一份出来,效率太差。所以一般第一步优化就是,记录哪些已经是可见的元素,在滑动过程中,那些仍然可见的就保留。不可见的塞回池子,然后除了之前可见的元素外,新变的可见的再从池子里拿即可。然后就是滑动的方向性。从下往上滑动的时候,小的index会变的不可见,大的index会变得可见,因此一部分元素是不需要计算的等等。
说的不是很清楚,反正先记录一下吧,后续再优化,有想交流的可以私信给我。后续可能考虑做个视频讲解实现,我不太擅长写图文并茂的文章,写文章功底还差得远。
贴代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;public class CircleScrollView : MonoBehaviour
{public float ItemWidth = 100;public float ItemHeight = 100;public float SpaceX = 10;public float SpaceY = 10;public CircleScrollItemBase ItemPrefab;private float ViewWidth;private float ViewHeight;private int ColNum;private int RowNum;private Rect VisibleRect;private ScrollRect _scroll;public ScrollRect Scroll{get{if (_scroll == null){_scroll = GetComponent<ScrollRect>();}return _scroll;}}void Awake(){InitSet();}void InitSet(){Rect rect = GetComponent<RectTransform>().rect;ViewWidth = rect.width;ViewHeight = rect.height;ColNum = (int)((ViewWidth + SpaceX) / (ItemWidth + SpaceX));Scroll.vertical = true;Scroll.horizontal = false;VisibleRect = new Rect(0, 0, ViewWidth, ViewHeight);_list = new List<CircleScrollItemBase>();_pool = new Stack<CircleScrollItemBase>();Scroll.onValueChanged.AddListener(OnScroll);}[NonSerialized]public int DataCount = 0;private int VisibleCount = 0;public void SetData(List<int> datas){SetData(datas.Count);}public void SetData(int count){DataCount = count;CalContentSize();CalItemCount();FillItems();_lastPosition = Scroll.content.anchoredPosition;}private void CalItemCount(){int rowNum = (int)(Math.Ceiling(ViewHeight + SpaceY) / (ItemHeight + SpaceY));VisibleCount = (rowNum + 1) * ColNum;}public void FillItems(){//至少保证if (_list.Count < VisibleCount){int diff = VisibleCount - _list.Count;for (int i = 0; i < diff; ++i){_list.Add(GetFromPool());}}for (int i = 0; i < _list.Count; ++i){int startRow = i / ColNum;int startCol = i % ColNum;if (i < DataCount){_list[i].gameObject.SetActive(true);_list[i].Index = i;_list[i].Refresh(i);_list[i].LeftTop = new Vector2(startCol * (SpaceX + ItemWidth), -startRow * (SpaceY + ItemHeight));}}}private List<CircleScrollItemBase> _list;private Stack<CircleScrollItemBase> _pool;private void CalContentSize(){RowNum = (int) Mathf.Ceil((float)DataCount / ColNum);Vector2 vec = new Vector2(ColNum * ItemWidth + (ColNum - 1) * SpaceX,RowNum * ItemHeight + (RowNum - 1) * SpaceY);vec.x = Math.Max(vec.x, ViewWidth);vec.y = Math.Max(vec.y, ViewHeight);Scroll.content.sizeDelta = vec;Scroll.content.pivot = new Vector2(0, 1);Scroll.content.anchorMin = new Vector2(0, 1);Scroll.content.anchorMax = new Vector2(0, 1);Scroll.content.anchoredPosition = Vector2.zero;}private CircleScrollItemBase GetFromPool(){if (_pool.Count > 0){return _pool.Pop();}else{GameObject go = Instantiate<GameObject>(ItemPrefab.gameObject, Scroll.content, false);go.SetActive(false);return go.GetComponent<CircleScrollItemBase>();}}private void ReleaseItem(CircleScrollItemBase item){item.gameObject.SetActive(false);item.Reset();_pool.Push(item);}private Vector2 _lastPosition;private Rect GetGridRect(int index){int startRow = index / ColNum;int startCol = index % ColNum;Vector2 startPos = new Vector2(startCol * (SpaceX + ItemWidth), startRow * (SpaceY + ItemHeight)) - Scroll.content.anchoredPosition;Rect rect = new Rect(startPos.x, startPos.y, ItemWidth, ItemHeight);return rect;}private void OnScroll(Vector2 delta){for (int i = _list.Count - 1; i >= 0 ; --i){Rect rect = GetGridRect(_list[i].Index);if (!Visible(rect)){ReleaseItem(_list[i]);_list.RemoveAt(i);}else{_list[i].gameObject.SetActive(true);}}if (_list.Count == 0){for (int i = 0; i < DataCount; ++i){Rect rect = GetGridRect(i);if (Visible(rect)){CircleScrollItemBase item = AppendItem(i);_list.Add(item);}}}else{int index = _list[0].Index - 1;int afterIndex = _list[_list.Count - 1].Index + 1;while (index >= 0){Rect rect = GetGridRect(index);if (!Visible(rect))break;CircleScrollItemBase item = AppendItem(index);index--;_list.Insert(0, item);}while (afterIndex < DataCount){Rect rect = GetGridRect(afterIndex);if (!Visible(rect))break;CircleScrollItemBase item = AppendItem(afterIndex);afterIndex++;_list.Add(item);}}}private CircleScrollItemBase AppendItem(int index){int startRow = index / ColNum;int startCol = index % ColNum;CircleScrollItemBase item = GetFromPool();item.gameObject.SetActive(true);item.Index = index;item.Refresh(index);item.LeftTop = new Vector2(startCol * (SpaceX + ItemWidth), -startRow * (SpaceY + ItemHeight));return item;}private bool Visible(Rect rect){return rect.Overlaps(VisibleRect);}
}
翻译
搜索
复制
相关文章:
Unity UGUI 循环滑动列表实现思路及简单代码实现
前言: 自己之前其实比着书上实现过一个循环滑动列表,并且商业化到了项目里,上线后也在用。可后来怎么也想不起来细节,看着之前的代码也看不很懂。这次复习一下,希望真能理解它的本质,也记录一下࿰…...

贪心算法(1)--经典贪心算法
目录 一、活动安排问题 二、最优装载问题 三、分数背包问题 四、多机调度问题 一、活动安排问题 1、策略 活动安排问题:设有n个活动的集合E{1,2,...,n},每个活动i都有一个使用该资源的起始时间和一个结束时间,且。如果选择了活动i则它在…...
Nginx负载均衡和备份和故障转移
如果你想要两台 Nginx 服务器配置访问同一个链接,通常意味着你可能想要以下几种配置: 负载均衡:两台 Nginx 服务器都工作,当访问者请求资源时,流量会在这两台服务器之间进行均衡分配。备份和故障转移:其中…...
Android-Framework 三方应用默认权限都不弹窗
代码位置:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java -1853,10 1853,10 public class PackageManagerService extends IPackageManager.StubmPermissionCallback);}- final String packageName res.pkg.application…...

TX Text Control.NET For WPF 32.0 Crack
TX Text Control 支持VISUAL STUDIO 2022、.NET 5 和 .NET 6 支持 .NET WPF 应用程序的文档处理 将文档编辑、创建和 PDF 生成添加到您的 WPF 应用程序中。 视窗用户界面 功能齐全的文档编辑器 TX Text Control 是一款完全可编程的丰富编辑控件,它在专为 Visual Stu…...
使用Go语言测试Redis性能
1. 前言 Redis是一个高性能的键值存储数据库,常用于缓存、队列、排行榜等场景。在实际应用中,我们需要对Redis的性能进行测试,以便了解其在不同场景下的表现。本文将介绍如何使用Go语言测试Redis的性能。 2. 环境准备 在开始测试前&#x…...

【Javascript】运算符(赋值,算术,自增,自减)
目录 赋值 算术 单个变量: 多个变量: 在字符串,数组中充当连接符 自符串与字符串 数组与数组 数组与字符串 自增与自减 前置 自增 自减 后置 自增 自减 赋值 var a 1;算术 单个变量: var a 1;a 1;console.l…...

Redis数据类型——list类型数据的扩展操作
1.list阻塞式数据获取 2.list类型数据业务场景...
[论文笔记]NEZHA
引言 今天带来华为诺亚方舟实验室提出的论文NEZHA,题目是 针对中文中文语言理解神经网络上下文表示(NEural contextualiZed representation for CHinese lAnguage understanding),为了拼出哪吒。 预训练语言模型由于具有通过对大型语料库进行预训练来捕获文本中深层上下文信…...

【Linux】认识协议
目录 一、应用层二、协议三、序列化和反序列化 一、应用层 之前的socket编程,都是在通过系统调用层面,如今我们来向上打通计算机网络。认识应用层的协议和序列化与反序列化 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应…...

Hadoop3教程(三十四):(生产调优篇)MapReduce生产经验汇总
文章目录 (164)MR跑得慢的原因(165)MR常用调优参数Map阶段Reduce阶段 (166)MR数据倾斜问题参考文献 (164)MR跑得慢的原因 MR程序执行效率的瓶颈,或者说当你觉得你的MR程…...

Unity⭐️Win和Mac安卓打包环境配置
文章目录 🟥 配置Android SDK1️⃣ 配置 SDK Platforms2️⃣ 配置 SDK Tools🎁 Android SDK Build-Tools🎁 Android SDK Command-line Tools(latest)🎁 Android SDK Tools(Obsolete)🟧 配置NDK🟩 配置JDK前情提示: 此方法适用于Windows/Mac 在配置时注意开启 🪜 …...

STM32F4XX之串口
一、标准串口(UART)介绍 1、通信协议相关概念 1.1同步通信和异步通信 (1)同步通信:两个器件之间共用一个时钟线,要发送的数据在时钟的作用下一位一位发送出去。 (2)异步通信:指两个器件之间没…...

【J-Long Group Limited】申请1500万美元纳斯达克IPO上市
来源:猛兽财经 作者:猛兽财经 猛兽财经获悉,总部位于中国香港的J-Long Group Limited(简称:J-Long)近期已向美国证券交易委员会(SEC)提交招股书,申请在纳斯达克IPO上市&…...

上传文件到google drive
参考:使用 Python 将文件上传到 Google 云端硬盘_迹忆客 第 1 步:Google API Playground 我们可以通过搜索 Google 找到更多关于 Google API Playground 的信息。 我们必须单击第一个链接才能继续前进。 选择第一个链接后,我们会自动进入下一…...

用VLOOKUP快速合并两个表格
一、前言 上周五微信收到运营提过来的需求,第一句话:帮我提取一下1号门店的库存数据,马上登录系统下载一份库存数据给到他然后专心读代码,过一会微信第二句话:帮我提取一下1号门店商品半年/一年的销量数据,…...

Vue ref属性
Vue中的ref属性可以用来对HTML元素或者是对组件进行唯一标识。 一、设置ref属性 只需要在元素或者是组件后跟上如下语法即可: ref"标识名" 二、获取元素或对象 我们可以用如下方法获取我们设置ref的元素或组件: this.$refs.标识名 第一个输…...
【python入门】函数,类和对象
【大家好,我是爱干饭的猿,本文重点介绍python入门的函数,高阶函数,python中的类和对象,模块的作用等。 后续会继续分享其他重要知识点总结,如果喜欢这篇文章,点个赞👍,关…...
alibaba.fastjson的使用(二)-- jar包导入
目录 1. 在pom文件中引入依赖: 2.fastjsonv2的使用: 1. 在pom文件中引入依赖: <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.14</version> </dependency>2.fastjsonv2的使用…...

A_搜索(A Star)算法
A*搜索(A Star) 不同于盲目搜索,A算法是一种启发式算法(Heuristic Algorithm)。 上文提到,盲目搜索对于所有要搜索的状态结点都是一视同仁的,因此在每次搜索一个状态时,盲目搜索并不会考虑这个状态到底是有利于趋向目标的&#x…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...

Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...