[性能优化] 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
{
    //base
    public 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.mfbz.cn/a/607708.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

es使用遇到的bug总结

本来版本7.4.0不行&#xff0c;最后换了个版本7.15.1就可以了&#xff0c;但又出现以下问题了&#xff1a; Beanpublic ElasticsearchClient elasticsearchClient() { // RestClient client RestClient.builder(new HttpHost("localhost", 9200,"http&q…

Duplicate entry ‘asdfg‘ for key ‘clazz.name‘

Mybatis:java.sql.SQLIntegrityConstraintViolationException:Duplicate entry ‘asdfg’ for key ‘clazz.name’ 违反了数据库的唯一约束条件&#xff0c;即插入数据的时候具有唯一约束&#xff08;被unique修饰&#xff09;的列值重复了 在修改的过程中发生错误&#xff0c;…

一文玩转Vue3参数传递——全栈开发之路--前端篇(8)

全栈开发一条龙——前端篇 第一篇&#xff1a;框架确定、ide设置与项目创建 第二篇&#xff1a;介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇&#xff1a;setup语法&#xff0c;设置响应式数据。 第四篇&#xff1a;数据绑定、计算属性和watch监视 第五篇 : 组件…

ORA-02020:过多的数据库链接在使用

一、问题描述 今天同事说&#xff0c;有一个查询功能&#xff0c;同时查了几个子平台的dblink&#xff0c;页面返回报错。 提示ORA-02020&#xff1a;过多的数据库链接在使用&#xff1b; bad SQL grammar 二、解决办法 1&#xff09;分析业务需求 分析业务场景 &#xff0c;发…

基于Vue3与ElementUI Plus的酷企秀场景可视化DIY设计器:前端技术引领下的数字化展示新篇章

一、引言 在当今信息化高速发展的时代&#xff0c;企业对于展示自身形象、提升用户体验以及增强品牌知名度的需求日益迫切。针对这一市场需求&#xff0c;我们推出了基于Vue3与ElementUI Plus的酷企秀场景可视化DIY设计器。该产品不仅具备电子画册、VR全景、地图秀三大核心功能…

Mybatis存储数据将数据转为json

第一种方法 先创建一个表类型如下 创建一个项目&#xff0c;写一个接口 &#xff0c; 写一个JsonTypeHandler类继承BaseTypeHandler public class JsonTypeHandler<T> extends BaseTypeHandler<T> {private Class<T> clazz;//构造函数 --- >接收一个 Cl…

大数据Scala教程从入门到精通第五篇:Scala环境搭建

一&#xff1a;安装步骤 1&#xff1a;scala安装 1&#xff1a;首先确保 JDK1.8 安装成功: 2&#xff1a;下载对应的 Scala 安装文件 scala-2.12.11.zip 3&#xff1a;解压 scala-2.12.11.zip 4&#xff1a;配置 Scala 的环境变量 在Windows上安装Scala_windows安装scala…

多线程三种实现

多线程 线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运作单位。 &#xff08;理解&#xff1a;应用软件中互相独立&#xff0c;可以同时运行的功能&#xff09; 进程 进程是程序的基本执行实体。&#xff08;理解&#…

【动态规划】:路径问题_地下城游戏

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本专栏是关于各种算法的解析&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数据结构专栏&…

【LLM 论文】Least-to-Most Prompting 让 LLM 实现复杂推理

论文&#xff1a;Least-to-Most Prompting Enables Complex Reasoning in Large Language Models ⭐⭐⭐ Google Research, ICLR 2023 论文速读 Chain-of-Thought&#xff08;CoT&#xff09; prompting 的方法通过结合 few-show prompt 的思路&#xff0c;让 LLM 能够挑战更具…

MySQL#MySql表的操作

目录 一、创建表 二、查看表结构 三、修改表 1.修改表的名字 2.新增一个列 3.修改列 4.删除列 5.修改列的名称 四、删除表 一、创建表 语法&#xff1a; CREATE TABLE table_name (field1 datatype,field2 datatype,field3 datatype ) character set 字符集 collate 校…

2042193-77-9,BDP FL甲基四嗪可用于标记细胞和组织样本

1.基本信息&#xff1a; BDP FL甲基四嗪是一种具有独特化学和光学性质的化合物。 2.化学结构&#xff1a; BDP FL甲基四嗪是含有甲基四嗪基团的BDP染料连接体。BDP FL部分是指附着在甲基四嗪上的荧光标记&#xff0c;使其在暴露于特定波长的光时能够发光。 甲基四嗪是一种具有…

C语言【文件操作 2】

文章目录 前言顺序读写函数的介绍fputc && fgetcfputcfgetc fputs && fgetsfputsfgets fprintf && fscanffprintffscanf fwrite && freadfwritefread 文件的随机读写fseek函数偏移量ftell函数rewind函数 文件的结束判断被错误使用的feof 结语 …

鸿蒙开发接口Ability框架:【(StaticSubscriberExtensionAbility)】

StaticSubscriberExtensionAbility StaticSubscriberExtensionAbility模块提供静态订阅者扩展能力的类别的能力。 说明&#xff1a; 本模块首批接口从API version 9 开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 本模块接口仅可在Stage模型下…

多线程学习D10 收尾了应该

线程安全集合类概述 重点介绍java.util.concurrent.* 下的线程安全集合类&#xff0c;可以发现它们有规律&#xff0c;里面包含三类关键词&#xff1a;Blocking、CopyOnWrite、Concurrent Blocking 大部分实现基于锁&#xff0c;并提供用来阻塞的方法 CopyOnWrite 之类容器修改…

iOS 17 / iPad OS 17屏蔽更新

iOS 17 / iPad OS 17屏蔽更新 1&#xff0c;进入屏蔽iOS更新的描述文件下载链接 下载链接 wx 搜索 Geek 前端发送屏蔽更新进行获取 2&#xff0c;复制这段链接&#xff0c;在Safari浏览器中打开&#xff0c;注意打开后别点击下载&#xff01;要先改时间&#xff01; 3&#…

69、oak和华为atlas 200dk A2进行编解码测试

基本思想:将oak深度相机与atlas 200dk A2进行结合,测试其dvpp的编解码能力 cmakelist.txt cmake_minimum_required(VERSION 3.16) project(untitled10) set(CMAKE_CXX_FLAGS "-std=c++11") set(CMAKE_CXX_STANDARD 11) add_definitions(-DENABLE_DVPP_INTERFACE)i…

数据的输入和输出

早期的总线系统 为了解决通信的问题、主板上铺设了一条公共线路、各个设备都连接到这条线路上、不管谁要和谁通信、都能使用它来传输、这条线路就是总线。 总线上有CPU、内存、鼠标、键盘、硬盘、网卡、声卡、显卡等… 说是一条总线、实际上是包含了传输数据的数据总线、传输…

保研面试408复习 4——操作系统、计网

文章目录 1、操作系统一、文件系统中文件是如何组织的&#xff1f;二、文件的整体概述三、UNIX外存空闲空间管理 2、计算机网络一、CSMA/CD 协议&#xff08;数据链路层协议&#xff09;二、以太网MAC帧MTU 标记文字记忆&#xff0c;加粗文字注意&#xff0c;普通文字理解。 1、…

「C++ 内存管理篇 00」指针

目录 一、变量&#xff0c;变量名和指针 1. 什么是变量&#xff1f; 2. 变量名和指针 3. 使用指针获取数据 二、指针变量和数组变量 三、编译器对指针的等级有着严格的检查 四、指针的加减 1. 存放指针的变量的加减 2. 存放指针的变量的自增自减 3. 两个指针相减 一、变量&…
最新文章