Unity UGUI实现无限滚动列表

Demo链接​​​https://download.csdn.net/download/qq_41973169/89364284http://Unity UGUI无限滚动列表

在游戏开发中,列表视图是一个常见的UI组件。实现一个高效的列表视图尤其重要,尤其是在需要展示大量数据时。本文将介绍如何在Unity中实现一个高效的无限滚动列表,包括两个关键脚本:InfiniteScroll 和 ListItem 两个脚本 话不多说直接上代码

Unity版本2022.3.X

InfiniteScroll.cs

cs 复制代码
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System;

public class InfiniteScroll : MonoBehaviour
{
    public delegate void ItemDelegate(int itemDataIndex, int slotID);

    public ItemDelegate OnUpdateItemImpl;// 更新Item回调函数

    public enum ScrollDirection
    {
        Vertical,
        Horizontal
    }

    public GameObject itemPrefab;
    [Header("横向滑动表示宽度; 纵向滑动表示高度")]
    public float itemSize;
    [Header("列表项间距")]
    public float spacing; // 列表项间距
    [Header("最大可见列表项数量")]
    public int maxVisibleItemCount;
    [Header("滑动方向")]
    public ScrollDirection scrollDirection = ScrollDirection.Vertical;

    private ScrollRect _scrollRect;
    private RectTransform _contentRectTransform;
    private int _firstVisibleIndex; // 第一个可见列表项的索引
    private int _lastVisibleIndex; // 最后一个可见列表项的索引
    private List<GameObject> _itemList; // 列表项对象列表
    private float _itemTotalSize; // 列表项总尺寸(包括间距)
    private int _totalItemDataCount; // item总共数据数量


    private void Awake()
    {
        _itemTotalSize = itemSize + spacing;
        _itemList = new List<GameObject>();
        _scrollRect = GetComponent<ScrollRect>();
        _contentRectTransform = _scrollRect.content.GetComponent<RectTransform>();
        _scrollRect.onValueChanged.AddListener(OnScrollChanged);
    }

    public void Init()
    {
        for (int i = 0; i < maxVisibleItemCount; i++)
        {
            int slotID = i + 1;
            GameObject obj = Instantiate(itemPrefab, _contentRectTransform);
            ListItem listItem = obj.GetComponent<ListItem>();
            listItem.Init(this);
            listItem.SetSlotID(slotID);

            obj.name = slotID.ToString();
            SetPivot(obj, _itemTotalSize * i);
            _itemList.Add(obj);
        }

        itemPrefab.SetActive(false);
    }

    private void SetPivot(GameObject obj, float position)
    {
        RectTransform rect = obj.GetComponent<RectTransform>();
        if (scrollDirection == ScrollDirection.Vertical)
        {
            rect.pivot = new Vector2(0.5f, 1);
            rect.anchorMax = new Vector2(0.5f, 1);
            rect.anchorMin = new Vector2(0.5f, 1);
            rect.anchoredPosition = new Vector2(0, -position);
        }
        else
        {
            rect.pivot = new Vector2(0, 0.5f);
            rect.anchorMax = new Vector2(0, 0.5f);
            rect.anchorMin = new Vector2(0, 0.5f);
            rect.anchoredPosition = new Vector2(position, 0);
        }
    }

    private void OnScrollChanged(Vector2 position)
    {
        if (scrollDirection == ScrollDirection.Vertical)
        {
            Func<bool> condition1 = () =>
            {
                // 如果Content的Y轴坐标大于上面第二个Item 并且最后的下标不是数据的最后一个则表示向下滑动可以更新Item
                return _contentRectTransform.anchoredPosition.y > (_firstVisibleIndex + 1) * _itemTotalSize && _lastVisibleIndex < _totalItemDataCount - 1;
            };
            Func<bool> condition2 = () =>
            {
                // 如果Content的Y轴坐标小于上面第一个Item 并且最上面的索引不为0 则表示向上滑动可以更新Item
                return _contentRectTransform.anchoredPosition.y < _firstVisibleIndex * _itemTotalSize && _firstVisibleIndex > 0;
            };
            UpdateItems(condition1, condition2);
        }
        else
        {
            Func<bool> condition1 = () =>
            {
                // 如果Content的X轴坐标大于右边第二个Item 并且最后的下标不是数据的最后一个则表示向右滑动可以更新Item
                return Mathf.Abs(_contentRectTransform.anchoredPosition.x) > (_firstVisibleIndex + 1) * _itemTotalSize && _lastVisibleIndex < _totalItemDataCount - 1;
            };
            Func<bool> condition2 = () =>
            {
                // 如果Content的X轴坐标小于左边第一个Item 并且最上面的索引不为0 则表示向左滑动可以更新Item
                return Mathf.Abs(_contentRectTransform.anchoredPosition.x) < _firstVisibleIndex * _itemTotalSize && _firstVisibleIndex > 0;
            };
            UpdateItems(condition1, condition2);
        }
    }

    private void UpdateItemUI(GameObject obj, int dataIndex)
    {
        ListItem listItem = obj.GetComponent<ListItem>();
        listItem.SetDataIndex(dataIndex);
        listItem.UpdateUI();
    }

    private void UpdateItems(Func<bool> condition1, Func<bool> condition2)
    {
        while (condition1())
        {
            GameObject first = _itemList[0];
            RectTransform rectTrans = first.GetComponent<RectTransform>();
            _itemList.RemoveAt(0);
            _itemList.Add(first);
            rectTrans.anchoredPosition = scrollDirection == ScrollDirection.Horizontal ?
                new Vector2((_lastVisibleIndex + 1) * _itemTotalSize, 0) :
                new Vector2(0, -(_lastVisibleIndex + 1) * _itemTotalSize);
            _firstVisibleIndex += 1;
            _lastVisibleIndex += 1;

            UpdateItemUI(first, _lastVisibleIndex + 1);
        }

        while (condition2())
        {
            GameObject last = _itemList[_itemList.Count - 1];
            RectTransform rectTrans = last.GetComponent<RectTransform>();
            _itemList.RemoveAt(_itemList.Count - 1);
            _itemList.Insert(0, last);
            rectTrans.anchoredPosition = scrollDirection == ScrollDirection.Horizontal ? 
                new Vector2((_firstVisibleIndex - 1) * _itemTotalSize, 0) :
                new Vector2(0, -(_firstVisibleIndex - 1) * _itemTotalSize);
            _firstVisibleIndex -= 1;
            _lastVisibleIndex -= 1;

            UpdateItemUI(last, _firstVisibleIndex + 1);
        }
    }

    public void RefreshList()
    {
        _firstVisibleIndex = 0;
        for (int i = 0; i < _itemList.Count; i++)
        {
            GameObject obj = _itemList[i];
            obj.SetActive(i < _totalItemDataCount);
            if (i < _totalItemDataCount)
            {
                _lastVisibleIndex = i;
                UpdateItemUI(obj, i + 1);
            }
        }

        float size = _itemTotalSize * _totalItemDataCount - spacing;
        if (scrollDirection == ScrollDirection.Vertical)
        {
            _contentRectTransform.sizeDelta = new Vector2(_contentRectTransform.sizeDelta.x, size);
        }
        else
        {
            _contentRectTransform.sizeDelta = new Vector2(size, _contentRectTransform.sizeDelta.y);
        }

        _contentRectTransform.anchoredPosition = Vector2.zero;
    }

    public void SetTotalItemDataCount(int count)
    {
        _totalItemDataCount = count;
    }
}

ListItem.cs

cs 复制代码
using UnityEngine;
using UnityEngine.UI;

public class ListItem : MonoBehaviour
{
    public Text itemText;

    private InfiniteScroll _infiniteScroll;
    private int _slotID;
    private int _dataIndex;

    public void Init(InfiniteScroll infiniteScroll)
    {
        _infiniteScroll = infiniteScroll;
    }

    public void SetSlotID(int slotID)
    {
        _slotID = slotID;
    }

    public void SetDataIndex(int dataIndex)
    {
        _dataIndex = dataIndex;
    }

    public void UpdateUI()
    {
        itemText.text = $"{_dataIndex} SlotID{_slotID}";
        if (_infiniteScroll.OnUpdateItemImpl != null)
        {
            _infiniteScroll.OnUpdateItemImpl.Invoke(_dataIndex, _slotID);
        }
        else
        {
            Debug.LogError("InfiniteScroll.OnUpdateItemImpl == null");
        }
    }
}

测试代码

MyTest.cs

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MyTest : MonoBehaviour
{
    public InfiniteScroll horizontalInfiniteScroll;
    public InfiniteScroll verticalInfiniteScroll;
    void Start()
    {
        horizontalInfiniteScroll.Init();
        verticalInfiniteScroll.Init();
        horizontalInfiniteScroll.OnUpdateItemImpl = (dataIndex, slotID) =>
        {
            Debug.LogError($"horizontalInfiniteScroll dataIndex:{dataIndex}, slotID:{slotID}");
        };
        verticalInfiniteScroll.OnUpdateItemImpl = (dataIndex, slotID) =>
        {
            Debug.LogError($"verticalInfiniteScroll dataIndex:{dataIndex}, slotID:{slotID}");
        };

        horizontalInfiniteScroll.SetTotalItemDataCount(100);
        verticalInfiniteScroll.SetTotalItemDataCount(100);
        horizontalInfiniteScroll.RefreshList();
        verticalInfiniteScroll.RefreshList();
    }
}

测试效果

场景树UI布局

脚本挂载

相关推荐
Artistation Game19 小时前
九、怪物行为逻辑
游戏·unity·游戏引擎
百里香酚兰20 小时前
【AI学习笔记】基于Unity+DeepSeek开发的一些BUG记录&解决方案
人工智能·学习·unity·大模型·deepseek
妙为20 小时前
unreal engine5制作动作类游戏时,我们使用刀剑等武器攻击怪物或敌方单位时,发现攻击特效、伤害等没有触发
游戏·游戏引擎·虚幻·碰撞预设
dangoxiba1 天前
[Unity Demo]从零开始制作空洞骑士Hollow Knight第十三集:制作小骑士的接触地刺复活机制以及完善地图的可交互对象
游戏·unity·visualstudio·c#·游戏引擎
先生沉默先2 天前
使用Materialize制作unity的贴图,Materialize的简单教程,Materialize学习日志
学习·unity·贴图
十画_8242 天前
Visual Studio 小技巧记录
unity·visual studio
red_redemption2 天前
cpp,git,unity学习
git·unity·游戏引擎
tealcwu2 天前
【Unity踩坑】Unity更新Google Play结算库
unity·游戏引擎
先生沉默先2 天前
unity 默认渲染管线材质球的材质通道,材质球的材质通道
unity·游戏引擎·材质
白鹭float.2 天前
【Unity AI】基于 WebSocket 和 讯飞星火大模型
人工智能·websocket·unity