【UGUI】如何解决列表创建1万个item卡顿的问题

思路

如果直接通过Instantiate创建1万item,性能肯定是很糟糕的,所以我们可以只创建几个item,然后在列表拖动的时候修改其anchoredPosition,然后更新item的数据即可。比如:最多只需要展示3条数据,那么我们可以只创建4个item,当往上拖动的时候将最顶端的item位置变换到最底端去,如果是往下拖动,则将最底端的item位置变换到最顶端。通过这种方式可以实现1万条数据的展示,性能消耗降低。

创建ItemRender

首先肯定要为Item创建一个预制体,直接创建一个空的UI,锚点设置为左上角,轴点设置为(0,0)。
在这里插入图片描述
为其添加子物体:Image、Text、Button
在这里插入图片描述

为其写个脚本:ItemRender,这样就可以在脚本中对单独的item数据初始化了。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ItemRender : MonoBehaviour
{
    private Text m_oText;
    private Button chooseBtn;
    private string data;
    private RectTransform rect;
    public Vector2 ItemPosi
    {
        set
        {
            rect.anchoredPosition = value;
        }
        get
        {
            return rect.anchoredPosition;
        }
    }
    private void Awake()
    {
        m_oText = this.transform.Find("Text").GetComponent<Text>();
        chooseBtn = this.transform.Find("ChooseBtn").GetComponent<Button>();
        chooseBtn.onClick.AddListener(OnChooseBtnClickEvent);
        rect = this.GetComponent<RectTransform>();

    }
    // Start is called before the first frame update
    void Start()
    {
        
    }   

    public void SetData(string data)
    {
        this.data = data;
        m_oText.text = data;
    }

    private void OnChooseBtnClickEvent()
    {
        print(data);
    }

   
}

创建ListView

我们不同ScrollRect,直接创建个空UI,添加组件Rect Mask 2D用于遮罩,Rect Mask 2D不会增加DrawCall,我们的UI主要是矩形,用Rect Mask 2D足够了。
在这里插入图片描述
编写脚本:LoopList

using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class LoopList : MonoBehaviour,IDragHandler
{
    public float itemHeight;//高度
    public float itemPadding;//间隔
    public int dataCount;//数据长度
    public int itemShowCount;//显示个数
    public ItemRender itemRender;//模板
    private ItemRender[] items;
    private string[] datas;
    private float minPosiY;
    private float maxPosiY;
    private float scrollNum;//拖动的距离(实时计算,用于表示当前的拖动进度)
    private float maxScrollNum;//最大拖动距离
    private void Awake()
    {
        items = new ItemRender[itemShowCount];
        datas = new string[dataCount];
        for(int i =0;i<dataCount;i++)
        {
            datas[i] = i.ToString();
        }
        maxScrollNum = dataCount * (itemHeight + itemPadding) - itemHeight * (itemShowCount -1);
    }

    // Start is called before the first frame update
    void Start()
    {
       for(int i=0;i<itemShowCount;i++)
        {
            ItemRender item = Instantiate(itemRender,this.transform);
            item.ItemPosi = new Vector2(0,-(i+1)*(itemHeight+itemPadding));
            items[i] = item;
            item.SetData(datas[i]);
        }
        minPosiY = items[0].ItemPosi.y + (itemHeight + itemPadding);
        maxPosiY = items[items.Length - 1].ItemPosi.y;
    }

    public void OnDrag(PointerEventData eventData)
    {
        //判断一下是否可以拖动
        bool canDrag = (eventData.delta.y < 0 && scrollNum > 0) || (eventData.delta.y > 0 && scrollNum < maxScrollNum);
        if(canDrag)
        {
            float fDeltaY = eventData.delta.y;
            scrollNum += fDeltaY;
            //限制一下拖动的范围,类似于scrollrect的不可回弹
            if(scrollNum<0)
            {
                fDeltaY -= scrollNum;
                scrollNum = 0;
            }
            else if (scrollNum > maxScrollNum)
            {
                fDeltaY -= scrollNum - maxScrollNum;
                scrollNum = maxScrollNum;
            }
            for(int i =0;i<items.Length;i++)
            {
                items[i].ItemPosi += new Vector2(0,fDeltaY);
                if (items[i].ItemPosi.y > minPosiY)
                {
                    items[i].ItemPosi += new Vector2(0,-itemShowCount * (itemPadding + itemHeight));
                }
                else if(items[i].ItemPosi.y < maxPosiY)
                {
                    items[i].ItemPosi += new Vector2(0, itemShowCount * (itemPadding + itemHeight));
                }
                /*假如item是处于一个高度为dataCount * (itemHeight + itemPadding)的Panel里面,那么通过-items[i].ItemPosi.y + scrollNum可以计算
                出当前item的位置,然后除以(itemHeight + itemPadding)可以得出item的索引,因为是从0开始计数的,所以减1*/
                int index = (int)Mathf.Round((-items[i].ItemPosi.y + scrollNum)/(itemHeight + itemPadding)) - 1;
                if(index >=0 && index < dataCount)
                {
                    items[i].SetData(datas[index]);
                }
            }
        }
    }

    //跳转到某个索引所在的位置
    public void ScrollToIndex(int index)
    {
        if(itemShowCount >= dataCount)
        {
            return;
        }
        //根据index的值,判断是否需要修改一下索引值(比如现在数据总数为50,index为49,显示的数据量为5,那么需要将index修改,47,才不会产生数据溢出)
        if(dataCount - index < itemShowCount -1)
        {
            int offset = itemShowCount -(dataCount - index)-2;
            index -= offset;
        }
        scrollNum = (index - 1) * (itemHeight + itemPadding);//更新一下scrollNum,便于记录滚动的位置
        //刷新item
        for (int i =0;i<itemShowCount;i++,index ++)
        {
            items[i].ItemPosi = new Vector2(0,-(i+1) * (itemHeight + itemPadding));
            if(index -1 >=0 && index -1 <dataCount)
            {
                items[i].SetData(datas[index -1]);
            }
        }
    }


}

实现跳转到某一行数据

给画布随便编写个脚本,主要是触发LoopList下的函数ScrollToIndex(int index)。
在画布下创建一个Button,绑定一下点击事件,图省事,我就直接在编辑器里面绑定了
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestPanel : MonoBehaviour
{
    public int scrollIndex = 50;
    private LoopList loopList;
    // Start is called before the first frame update
    void Start()
    {
        loopList = this.transform.Find("LoopList").GetComponent<LoopList>();
        
    }

    public void GotoIndex()
    {
        loopList.ScrollToIndex(scrollIndex);
    }
}

效果

1673430310594

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现一个item列表可以通过 Unity3D 中的UGUI组件来完成,以下是一个简单的实现步骤: 1. 创建一个ScrollView对象,它会成为你的item列表的容器。 2. 在ScrollView对象下创建一个Content对象,用于放置所有的item。 3. 创建一个item的Prefab,包含你需要显示的元素。 4. 在运行时,动态生成多个item对象,将它们放置在Content对象下,以此来构建item列表。 5. 根据需要,可以对item列表进行滚动、添加或删除item等操作。 具体实现可以参考以下步骤: 1. 创建ScrollView和Content对象 在场景中创建一个空对象,命名为ScrollView。将Canvas组件的Render Mode设置为Screen Space - Overlay,然后将ScrollView对象的RectTransform组件的Anchors和Pivot都设置为(0, 0)。这样,ScrollView对象的左下角就会位于屏幕左下角。在ScrollView对象下创建一个空对象,命名为Content。将Content对象的RectTransform组件的Anchors和Pivot也都设置为(0, 0),以便于它能够与ScrollView对象的位置重合。 2. 创建item的Prefab 在项目资源中创建一个新的Prefab,将你需要显示的元素放入其中。例如,可以在Prefab中添加一个Text对象,用于显示item的标题。确保这个Prefab的RectTransform组件的Anchors和Pivot都设置为(0, 0),以便于在生成item时它们能够正确地布局。 3. 动态生成item对象 在脚本中,使用Instantiate()方法动态生成多个item对象,并将它们作为Content对象的子对象。例如: ```csharp public GameObject itemPrefab; public int itemCount = 20; void Start() { for (int i = 0; i < itemCount; i++) { GameObject item = Instantiate(itemPrefab, content.transform); // 设置item的位置和大小 item.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -i * item.GetComponent<RectTransform>().rect.height); } } ``` 这段代码会生成20个item对象,将它们放置在Content对象下,并设置它们的位置和大小。这里假设item的高度是固定的。 4. 对item列表进行滚动 为了让item列表能够滚动,需要将ScrollView对象下的Scrollbar组件与Content对象的RectTransform组件相绑定。在ScrollView对象下添加一个Scrollbar组件,将它的Direction设置为Vertical,并将它的Size设置为0.2(或根据需要调整)。然后将Scrollbar组件的Value属性绑定到Content对象的RectTransform组件的anchoredPosition.y属性上。这样,当拖动Scrollbar时,Content对象就会相应地向上或向下滚动。 5. 添加或删除item 如果需要动态地添加或删除item,可以在脚本中使用Instantiate()和Destroy()方法来完成。例如: ```csharp public void AddItem() { GameObject item = Instantiate(itemPrefab, content.transform); // 设置新的item的位置和大小 item.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -itemCount * item.GetComponent<RectTransform>().rect.height); itemCount++; } public void RemoveItem() { if (itemCount > 0) { Destroy(content.transform.GetChild(itemCount - 1).gameObject); itemCount--; } } ``` 这样,就可以在运行时动态地添加或删除item了。当添加一个新的item时,只需生成一个新的GameObject,并将它放置在Content对象下;当删除一个item时,只需销毁Content对象下的最后一个子对象即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值