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布局

脚本挂载

相关推荐
Thomas_YXQ5 小时前
Unity3D Huatuo技术原理剖析详解
unity·unity3d·游戏开发·性能调优·热更新
火云洞红孩儿6 小时前
基于AI IDE 打造快速化的游戏LUA脚本的生成系统
c++·人工智能·inscode·游戏引擎·lua·游戏开发·脚本系统
虾球xz7 小时前
游戏引擎学习第59天
学习·游戏引擎
zh路西法8 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(二):从FSM开始的2D游戏角色操控底层源码编写
c++·游戏·unity·设计模式·状态模式
橘子遇见BUG11 小时前
Unity Shader学习日记 part 3 线性代数--矩阵变换
学习·线性代数·unity·矩阵·图形渲染
神洛华13 小时前
Y3编辑器教程8:资源管理器与存档、防作弊设置
编辑器·游戏引擎·游戏程序
Moweiii14 小时前
SDL3 GPU编程探索
c++·游戏引擎·图形渲染·sdl·vulkan
Artistation Game14 小时前
一、c#基础
游戏·unity·c#·游戏引擎
成都渲染101云渲染666615 小时前
云渲染,Enscape、D5、Lumion渲染提速教程
运维·服务器·unity·电脑·图形渲染·blender·houdini
超龄魔法少女2 天前
[Unity] ShaderGraph动态修改Keyword Enum,实现不同效果一键切换
unity·技术美术·shadergraph