Demo链接: https://download.csdn.net/download/qq_41973169/89364284
在游戏开发中,列表视图是一个常见的UI组件。实现一个高效的列表视图尤其重要,尤其是在需要展示大量数据时。本文将介绍如何在Unity中实现一个高效的无限滚动列表,包括两个关键脚本:InfiniteScroll 和 ListItem 两个脚本 话不多说直接上代码
Unity版本2022.3.X
InfiniteScroll.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);_itemList.Add(obj);}itemPrefab.SetActive(false);}private void SetPivot(GameObject obj){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);}else{rect.pivot = new Vector2(0, 0.5f);rect.anchorMax = new Vector2(0, 0.5f);rect.anchorMin = new Vector2(0, 0.5f);}}private void OnScrollChanged(Vector2 position){if (scrollDirection == ScrollDirection.Vertical){Func<bool> condition1 = () =>{// 如果Content的Y轴坐标大于上面第二个Item 并且最后的下标不是数据的最后一个则表示向下滑动可以更新Itemreturn _contentRectTransform.anchoredPosition.y > (_firstVisibleIndex + 1) * _itemTotalSize && _lastVisibleIndex < _totalItemDataCount - 1;};Func<bool> condition2 = () =>{// 如果Content的Y轴坐标小于上面第一个Item 并且最上面的索引不为0 则表示向上滑动可以更新Itemreturn _contentRectTransform.anchoredPosition.y < _firstVisibleIndex * _itemTotalSize && _firstVisibleIndex > 0;};UpdateItems(condition1, condition2);}else{Func<bool> condition1 = () =>{// 如果Content的X轴坐标大于右边第二个Item 并且最后的下标不是数据的最后一个则表示向右滑动可以更新Itemreturn Mathf.Abs(_contentRectTransform.anchoredPosition.x) > (_firstVisibleIndex + 1) * _itemTotalSize && _lastVisibleIndex < _totalItemDataCount - 1;};Func<bool> condition2 = () =>{// 如果Content的X轴坐标小于左边第一个Item 并且最上面的索引不为0 则表示向左滑动可以更新Itemreturn 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 position = _itemTotalSize * i;RectTransform rect = obj.GetComponent<RectTransform>();if (scrollDirection == ScrollDirection.Vertical){rect.anchoredPosition = new Vector2(0, -position);}else{rect.anchoredPosition = new Vector2(position, 0);}}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
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
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布局
脚本挂载