[性能优化] ScrollView视图优化为循环列表

问题描述:

原先商城的物品栏中的item 是load在一个scrollView 下,用于滑动查看。仅仅在父级panel下是使用了NGUI原生的scrollview 组件,随着商场物品列表中新物品的增多。panel下加载的实例也非常庞大。而大部分的实例用户也无法看到(scrollView工作原理是只渲染了content区域内的实例),可以看到item.get方法每次也需跑满循环,一帧的情况下调用多达数万次。
image.png

解决思路:

于是,问题的根源来自于我们创建了太多实例。
那么实际上需不要要创建那么多呢,答案是否定的。对于每个item物品信息,实例本身是可以复用的,不同点在于它初始化的数据不一样,以及详情点击、以及右侧展示面板的信息不一样
image.png
那么设想,我们是否可以在滑动到指定位置时,重新初始化实例的信息,达到类似 ”滚动视图“的障眼法呢。

RecyclableScrollView自定义组件实现

原理以及主要组成部分

循环列表组件基于NGUI框架开发,可支持多行或多列的循环列表。仅创建少数Item,通过变更位置的方式实现的Item复用,以优化性能损耗。
原理如下(双端队列)
image.png
上滑时判断【队头元素是否需要移动到队尾】
第一屏实际只加载了k(图中k = 9)个item,在向上滚动时,当第【1】个item出了显示的范围,第【1】个transform.position += k * item.height,就变即将要显示的第【10】个了。
继续上滑,下面依次判断第【2】【3】…是否需要下移至末尾

下滑时同理,判断【队尾元素是否需要移动到对头】

主要的核心组件为RecyclableScrollView和RecyclableGridControl:
image.png

使用方法

  1. 面板中依次创建Panel->Grid结点,并在添加RecyclableScroll View.cs 以及 Vertical Recyclable Grid Control.cs
  2. 在Panel层 或父级 ,加入一个Init的方法。即刷数据的方法即可。后续的滚动显示流程 已封装在RecyclableScrollView中,低耦合。

SetData方法

这是刷新数据的核心方法,需要给item身上的脚本继承RecyclableItem.cs
这里,主要是封装一些操作,然后规定一个setdata的流程
因为需要一开始获取item的位置信息,即transform,需要继承mono

using System;
using UnityEngine;public abstract class RecyclableItem : MonoBehaviour
{private Transform m_TransformCache = null;public Transform itemTrans{get{if (m_TransformCache == null)m_TransformCache = GetComponent<Transform>();return m_TransformCache;}}private int m_Index;public int index{get => m_Index;set => m_Index = value;}public Action<int> OnItemClickCb { get; set; }protected virtual void Awake(){if (m_TransformCache == null)m_TransformCache = GetComponent<Transform>();}public virtual void SetActive(bool active){if (gameObject.activeSelf == active) return;gameObject.SetActive(active);}public virtual void SetSelected(bool isSelected) {}public abstract void SetData<T>(T data);public virtual void ClearData() { }public virtual void OnItemClicked(){OnItemClickCb?.Invoke(index);}
}

拿法宝为例,override一个自己setdata的方法,因为需要从哪个缓存池拿数据,怎么填。这都是个体的过程

public class MagicWeaponListItem : RecyclableItem
{
//----其他方法-----//public override void SetData<T>(T data){var itemData = data as MagicWeaponItemData;if (itemData != null){try{switch (itemData.pageStatus){case SHOWITEM_TYPE.OUTLOOK_CHOOSE_TYPE:InitMagicWeaponItem(MagicOutlook.TalismanOutController()?.m_magicWeaponItemList[itemData.magicWeaponId],SHOWITEM_TYPE.OUTLOOK_CHOOSE_TYPE);MagicOutlook.TalismanOutController()?.m_magicWeaponItemObjects.Add(this); break;case SHOWITEM_TYPE.FVELEMENT_JINGJIN:InitMagicWeaponItemForFVElem(TalismanFVElementsLogic.Instance()?.m_magicWeaponItemList[itemData.magicWeaponId],SHOWITEM_TYPE.FVELEMENT_JINGJIN,GameManager.PlayerDataPool.TalismanList);TalismanFVElementsLogic.Instance()?.m_magicWeaponItemObjects.Add(this);break;case SHOWITEM_TYPE.CHOOSE_MAGICWEAPON_TYPE:InitMagicWeaponItem(AllMagicWeaponList.Instance()?.m_magicWeaponItemList[itemData.magicWeaponId],SHOWITEM_TYPE.CHOOSE_MAGICWEAPON_TYPE,false);break;}SetCastResAct(false);   }catch (Exception e){Debug.LogWarning(e);throw;}}}
}

现在每个item初始化数据格式的方法有了。现在 整个循环列表用什么规则去初始化下面的item呢

这里就定义了一个controller.cs
RecyclableGridController.cs 它主要是持有子级的gird 在grid布局下 垂直/水平生成 item ,需要实例多少个item,以及上文说到 通过双端链表 去改变每个item的位置,来实现”滚动显示“都是它去做的事情。

这里给出主要方法

public abstract class RecyclableGridControl : MonoBehaviour
{//basepublic GameObject prefabItem; //实例的泛型public RecyclableScrollView scrollView;//自定义的循环列表组件public UIPanel scrollViewPanel;public UIGrid grid;//布局/// <summary>/// 更新Item数据域位置/// </summary>protected virtual void UpdateItem(RecyclableItem item, int index){item.index = index;bool isNull = m_DataList == null || index < 0 || index >= m_DataList.Count;item.SetData(isNull ? null : m_DataList[index]);//每个item 刷新数据在这里item.SetActive(!isNull);item.SetSelected(m_CurSelectedIndex == index);UpdateItemPosition(item, index);}/// <summary>/// 设定数据/// </summary>public void SetData<T>(List<T> list, bool resetPosition){// 防止还没来得及初始化就设值的情况TryInit();m_DataList.Clear();//赋值的数据if (IsNullOrEmpty(list) == false){for (int x = 0, count = list.Count; x < count; x++){m_DataList.Add(list[x]);}}//调整当前grid下item的位置if (resetPosition){ResetGrid();return;}RefreshGrid();}}

RecyclableGridControll 的 SetData方法 (注意每个item自己也有一个setdata方法,两者是总体与 个体区别)就是循环列表更新数据的逻辑。

  • **TryInit 阶段:**判定是否是第一次创建Grid,如果是,那么实例化几个prefab,是需要先去做的.其次就是 检查每个prefab的索引是否需要变化,【在移动到第2~6时,第一个的索引应该调整为7,即在grid下变成序号7的child为后续统一位置信息做铺垫】。
  • 缓存需要加载的全部信息:全部子项数据从 list 里进入m_datalist缓存,为下一步赋值进行准备工作
  • 赋值以及计算实时位置:按照Grid每个item 的编号 通过postion.y = (index -1) * item.height 更新真正位置。
  • 完成数据项自己的初始化信息的过程,即updateItem 方法,此方法包含在ResetGrid流程的最后一步

至此,循环列表的制作就完成了。
3.gif

可以看到右侧Grid下只创建了七个实例,但左侧可以显示理论上 无穷的信息。

性能分析

对比循环列表和普通列表,创建1000个子物体,具体实现方法如下:
image.png
第一次初始化的耗时,循环列表耗时约17ms,普通列表耗时约342ms,性能提升约20倍左右,如下:
image.png
第二次更新列表数据时,由于循环列表仅需修改数据,而普通列表需要先清空子物体再生成,耗时差距进一步拉大,循环列表耗时约0.3ms,普通列表耗时约672ms,性能提升约2000倍左右,如下
image.png

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

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

相关文章

机器学习——2.损失函数loss

基本概念 损失函数也叫代价函数。损失函数就是计算预测结果和实际结果差距的函数&#xff0c;机器学习的过程就是试图将损失函数的值降到最小。 图左&#xff1a;&#xff5c;t_p - t_c&#xff5c; 图右&#xff1a;&#xff08;t_p - t_c&#xff09;**2 代码实…

LLM——用于微调预训练大型语言模型(LLM)的GPU内存优化与微调

前言 GPT-4、Bloom 和 LLaMA 等大型语言模型&#xff08;LLM&#xff09;通过扩展至数十亿参数&#xff0c;实现了卓越的性能。然而&#xff0c;这些模型因其庞大的内存需求&#xff0c;在部署进行推理或微调时面临挑战。这里将探讨关于内存的优化技术&#xff0c;旨在估计并优…

【Elasticsearch<四>✈️✈️】SpringBoot 项目整合 Elasticsearch

目录 &#x1f378;前言 &#x1f37b;一、Elasticsearch 本地环境启动 &#x1f37a;二、SpringBoot 项目整合 Elasticsearch 2.1 引入 ES 依赖 2.2 配置 ES 属性 2.3 创建实体类 2.4 操作 ES 的工具类 2.5 操作 ES 的业务层 &#x1f379;三、接口测试 3.1 编写测试类 3…

跟TED演讲学英文:What moral decisions should driverless cars make by Iyad Rahwan

What moral decisions should driverless cars make? Link: https://www.ted.com/talks/iyad_rahwan_what_moral_decisions_should_driverless_cars_make Speaker: Iyad Rahwan Date: September 2016 文章目录 What moral decisions should driverless cars make?Introduct…

Mac跑llama.cpp过程中遇到的问题

原repo 在华为手机上安装termux、下载库&#xff1a;顺利在电脑上安装Android NDK&#xff1a;先下载Android Studio&#xff0c;再在里面下载Android SDK 安装Android Studio时&#xff0c;SDK的某些组件总是下载不成功。后来关了梯子、改了hosts&#xff0c;重新安装就成功了…

年轻人刮疯了,刮刮乐断货了

年轻人刮疯了 刮刮乐缺货了。 00后彩票店老板陆诗等得有点着急。她的福彩店开在深圳&#xff0c;今年4月才开门营业&#xff0c;但从开业到今天&#xff0c;刮刮乐总共就来了一回货——开业时发的20本。 那之后&#xff0c;刮刮乐就彻底断供了。原本&#xff0c;陆诗想把刮刮…

5G Advanced and Release18简述

5G Advanced 5G-Advanced, formally defined in 3GPP Release 18, represents an upgrade to existing 5G networks. 先睹robot总结的5G Advanced的advancements: Enhanced Mobility and Reliability: 5G-Advanced will support advanced applications with improved mobility…

客户管理软件排行榜:对比18款CRM

本文将对比18个客户管理软件&#xff1a;纷享销客、Zoho CRM、Salesforce、HubSpot CRM、Pipedrive、Freshsales、Microsoft Dynamics 365 CRM、Insightly CRM、Nimble CRM、Apptivo CRM、SugarCRM、白码CRM、简信CRM、销帮帮CRM、Teamface企典CRM、神州云动CRM、悟空CRM、八百…

The forms of the layered MVP in Acise

在Layered MVP架构&#xff0c;Model负责业务逻辑&#xff0c;View负责用户界面&#xff0c;Presenter处于Model与View之间&#xff0c;一方面将Model数据转换成界面数据&#xff0c;另一方面将用户界面输入投递到Model层。 一般地&#xff0c;Model数据显示涉及到对象树、视图…

私域流量引流方式有哪些?

私域流量引流的方法无非是营销渠道投放、各平台KOL投放、自有自媒体平台账号内容引流、线下引流、老客户转介绍裂变等几个方面&#xff0c;下面对各种不同方法进行简单介绍。 1、营销渠道投放&#xff1a;选择广点通、粉丝通、某些app的信息流和dou等大平台自带的推广渠道工具…

el-select 点击按钮滚动到选择框顶部

主要代码是在visibleChange 在这个 popper 里面找到 .el-select-dropdown__list let popper ref.$refs.popper const ref this.$refs.select let dom popper.querySelector(.el-select-dropdown__list) setTimeout(() > { dom.scrollIntoView() }, 800) <templat…

蓝牙连接手机播放音乐的同时传输少量数据,那些蓝牙芯片可以实现呢

简介 蓝牙连接手机播放音乐的同时连接另一蓝牙芯片传输少量数据&#xff0c;那些蓝牙芯片可以实现呢&#xff1f; 这个需求&#xff0c;其实就是双模的需求 简单描述就是:播放音乐的同时&#xff0c;还可以连接ble&#xff0c;进行数据的传输。二者同时进行&#xff0c;互不…