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

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 循环滑动列表实现思路及简单代码实现

前言&#xff1a; 自己之前其实比着书上实现过一个循环滑动列表&#xff0c;并且商业化到了项目里&#xff0c;上线后也在用。可后来怎么也想不起来细节&#xff0c;看着之前的代码也看不很懂。这次复习一下&#xff0c;希望真能理解它的本质&#xff0c;也记录一下&#xff0…...

贪心算法(1)--经典贪心算法

目录 一、活动安排问题 二、最优装载问题 三、分数背包问题 四、多机调度问题 一、活动安排问题 1、策略 活动安排问题&#xff1a;设有n个活动的集合E{1,2,...,n}&#xff0c;每个活动i都有一个使用该资源的起始时间和一个结束时间&#xff0c;且。如果选择了活动i则它在…...

Nginx负载均衡和备份和故障转移

如果你想要两台 Nginx 服务器配置访问同一个链接&#xff0c;通常意味着你可能想要以下几种配置&#xff1a; 负载均衡&#xff1a;两台 Nginx 服务器都工作&#xff0c;当访问者请求资源时&#xff0c;流量会在这两台服务器之间进行均衡分配。备份和故障转移&#xff1a;其中…...

Android-Framework 三方应用默认权限都不弹窗

代码位置&#xff1a;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 是一款完全可编程的丰富编辑控件&#xff0c;它在专为 Visual Stu…...

使用Go语言测试Redis性能

1. 前言 Redis是一个高性能的键值存储数据库&#xff0c;常用于缓存、队列、排行榜等场景。在实际应用中&#xff0c;我们需要对Redis的性能进行测试&#xff0c;以便了解其在不同场景下的表现。本文将介绍如何使用Go语言测试Redis的性能。 2. 环境准备 在开始测试前&#x…...

【Javascript】运算符(赋值,算术,自增,自减)

目录 赋值 算术 单个变量&#xff1a; 多个变量&#xff1a; 在字符串&#xff0c;数组中充当连接符 自符串与字符串 数组与数组 数组与字符串 自增与自减 前置 自增 自减 后置 自增 自减 赋值 var a 1;算术 单个变量&#xff1a; var a 1;a 1;console.l…...

Redis数据类型——list类型数据的扩展操作

1.list阻塞式数据获取 2.list类型数据业务场景...

[论文笔记]NEZHA

引言 今天带来华为诺亚方舟实验室提出的论文NEZHA,题目是 针对中文中文语言理解神经网络上下文表示(NEural contextualiZed representation for CHinese lAnguage understanding),为了拼出哪吒。 预训练语言模型由于具有通过对大型语料库进行预训练来捕获文本中深层上下文信…...

【Linux】认识协议

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

Hadoop3教程(三十四):(生产调优篇)MapReduce生产经验汇总

文章目录 &#xff08;164&#xff09;MR跑得慢的原因&#xff08;165&#xff09;MR常用调优参数Map阶段Reduce阶段 &#xff08;166&#xff09;MR数据倾斜问题参考文献 &#xff08;164&#xff09;MR跑得慢的原因 MR程序执行效率的瓶颈&#xff0c;或者说当你觉得你的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之串口

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

【J-Long Group Limited】申请1500万美元纳斯达克IPO上市

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于中国香港的J-Long Group Limited&#xff08;简称&#xff1a;J-Long&#xff09;近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯达克IPO上市&…...

上传文件到google drive

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

用VLOOKUP快速合并两个表格

一、前言 上周五微信收到运营提过来的需求&#xff0c;第一句话&#xff1a;帮我提取一下1号门店的库存数据&#xff0c;马上登录系统下载一份库存数据给到他然后专心读代码&#xff0c;过一会微信第二句话&#xff1a;帮我提取一下1号门店商品半年/一年的销量数据&#xff0c…...

Vue ref属性

Vue中的ref属性可以用来对HTML元素或者是对组件进行唯一标识。 一、设置ref属性 只需要在元素或者是组件后跟上如下语法即可&#xff1a; ref"标识名" 二、获取元素或对象 我们可以用如下方法获取我们设置ref的元素或组件&#xff1a; this.$refs.标识名 第一个输…...

【python入门】函数,类和对象

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍python入门的函数&#xff0c;高阶函数&#xff0c;python中的类和对象&#xff0c;模块的作用等。 后续会继续分享其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关…...

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

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机

这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机&#xff0c;因为在使用过程中发现 Airsim 对外部监控相机的描述模糊&#xff0c;而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置&#xff0c;最后在源码示例中找到了&#xff0c;所以感…...

AI语音助手的Python实现

引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...

若依登录用户名和密码加密

/*** 获取公钥&#xff1a;前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...

WEB3全栈开发——面试专业技能点P4数据库

一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库&#xff0c;基于 mysql 库改进而来&#xff0c;具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点&#xff1a; 支持 Promise / async-await&#xf…...

深度解析云存储:概念、架构与应用实践

在数据爆炸式增长的时代&#xff0c;传统本地存储因容量限制、管理复杂等问题&#xff0c;已难以满足企业和个人的需求。云存储凭借灵活扩展、便捷访问等特性&#xff0c;成为数据存储领域的主流解决方案。从个人照片备份到企业核心数据管理&#xff0c;云存储正重塑数据存储与…...