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

脚本挂载

相关推荐
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
red_redemption7 小时前
自由学习记录(23)
学习·unity·lua·ab包
/**书香门第*/11 小时前
Cocos creator 3.8 支持的动画 7
学习·游戏·游戏引擎·游戏程序·cocos2d
向宇it1 天前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
Heaphaestus,RC1 天前
【Unity3D】获取 GameObject 的完整层级结构
unity·c#
芋芋qwq1 天前
Unity UI射线检测 道具拖拽
ui·unity·游戏引擎
tealcwu1 天前
【Unity服务】关于Unity LevelPlay的基本情况
unity·游戏引擎
大眼睛姑娘1 天前
Unity3d场景童话梦幻卡通Q版城镇建筑植物山石3D模型游戏美术素材
unity·游戏美术
鹿野素材屋1 天前
Unity Dots下的动画合批工具:GPU ECS Animation Baker
unity·游戏引擎
小春熙子2 天前
Unity图形学之着色器之间传递参数
unity·游戏引擎·技术美术·着色器