Unity虚拟列表无限列表

直接贴代码

using System.Collections.Generic;
using UnityEngine;
//using UnityEngine.EventSystems;
using UnityEngine.UI;
using DG.Tweening;

/// <summary>
/// 虚拟列表1
/// </summary>
public class VirtualScroll : ScrollRect
{
    public delegate void ListItemRenderer(int index, GameObject item);
    /// <summary>
    /// 刷新项的回调函数
    /// </summary>
    public ListItemRenderer itemRenderer;
    /// <summary>
    /// 最大项数
    /// </summary>
    private int _numItems;
    [Header("自定义项")]
    public RectTransform Item;
    [Tooltip("项的大小")]
    public Vector2 ItemSize;
    [Tooltip("间距")]
    public float Spacing;
    [Tooltip("边距")]
    public RectOffset Padding;
    /// <summary>
    /// 当前组件
    /// </summary>
    private VirtualScroll _parent;
    /// <summary>
    /// 创建的项列表,有序排列和显示顺序一致
    /// </summary>
    private List<ItemInfo> _virtualItems = new List<ItemInfo>();
    /// <summary>
    /// 方向
    /// </summary>
    private enum Direction
    {
        Horizontal,
        Vertical
    }
    

    /// <summary>
    /// 自己的滑动方向
    /// </summary>
    private Direction _direction;
    /// <summary>
    /// 项信息
    /// </summary>
    private class ItemInfo
    {
        /// <summary>
        /// 项
        /// </summary>
        public RectTransform item;
        /// <summary>
        /// 列表索引0开始
        /// </summary>
        public int index;
    }

    protected override void Awake()
    {
        base.Awake();
        if (transform.parent)
        {
            _parent = transform.GetComponentInParent<VirtualScroll>();
        }
        _direction = horizontal ? Direction.Horizontal : Direction.Vertical;
    }
    /// <summary>
    /// 变化值
    /// </summary>
    private void ListenerChange(Vector2 changeV)
    {
        RefreshItemInfo();
    }
    #region 计算个数
    /// <summary>
    /// 计算最大个数
    /// </summary>
    private void CalcuglateCount()
    {
        if (_direction == Direction.Vertical)//垂直滑动
        {
            ///初始化容器
            _parent.content.anchorMin = new Vector2(0, 1);
            _parent.content.anchorMax = new Vector2(1, 1);
            _parent.content.anchoredPosition = new Vector2(_parent.content.anchoredPosition.x, 0);
            float ContentHeight = ItemSize.y * _numItems + (_numItems - 1) * Spacing + Padding.top + Padding.bottom;
            _parent.content.sizeDelta = new Vector2(_parent.content.sizeDelta.x, ContentHeight);//设置大小 项越多值越大
                                                                                                //初始化项 对齐顶上
            Item.pivot = new Vector2(0.5f, 1);
            Item.anchorMin = new Vector2(0.5f, 1);
            Item.anchorMax = new Vector2(0.5f, 1);
            //计算可显示的项数
            float height = _parent.GetComponent<RectTransform>().rect.height;
            height -= (Padding.top + Padding.bottom);
            //最少项数
            int LeastItemCount = Mathf.CeilToInt(height / (ItemSize.y + Spacing));
            LeastItemCount += 2;
            LeastItemCount = LeastItemCount > _numItems ? _numItems : LeastItemCount;
            int MaxNum = LeastItemCount - _virtualItems.Count;
            //生成项
            if (MaxNum > 0)
            {
                for (int i = _virtualItems.Count; i < LeastItemCount; i++)
                {
                    GameObject item = GameObject.Instantiate(Item.gameObject);
                    item.transform.SetParent(_parent.content);
                    ItemInfo itemInfo = new ItemInfo();
                    itemInfo.item = item.GetComponent<RectTransform>();
                    itemInfo.index = i;
                    _virtualItems.Add(itemInfo);
                    item.SetActive(false);
                    item.GetComponent<RectTransform>().localScale = Vector3.one;
                }
            }
            else
            {
                MaxNum = -MaxNum;
                for (int i = 0; i < MaxNum; i++)
                {
                    GameObject gameObject = _virtualItems[_virtualItems.Count - 1].item.gameObject;
                    _virtualItems.RemoveAt(_virtualItems.Count - 1);
                    Destroy(gameObject);
                }
            }
            if (_parent)
            {
                _parent.onValueChanged.RemoveAllListeners();
                _parent.onValueChanged.AddListener(ListenerChange);
            }
        }
        else
        {
            _parent.content.anchorMin = new Vector2(0, 0);
            _parent.content.anchorMax = new Vector2(0, 1);
            _parent.content.anchoredPosition = new Vector2(0, _parent.content.anchoredPosition.y);
            float ContentHeight = ItemSize.x * _numItems + (_numItems - 1) * Spacing + Padding.left + Padding.right;
            _parent.content.sizeDelta = new Vector2(ContentHeight, _parent.content.sizeDelta.y);//设置大小 项越多值越大
                                                                                                //初始化项 对齐顶上
            Item.pivot = new Vector2(0f, 0.5f);
            Item.anchorMin = new Vector2(0f, 0.5f);
            Item.anchorMax = new Vector2(0f, 0.5f);
            //计算可显示的项数
            float height = _parent.GetComponent<RectTransform>().rect.width;
            height -= (Padding.left + Padding.right);
            //最少项数
            int LeastItemCount = Mathf.CeilToInt(height / (ItemSize.x + Spacing));
            LeastItemCount += 2;
            LeastItemCount = LeastItemCount > _numItems ? _numItems : LeastItemCount;
            int MaxNum = LeastItemCount - _virtualItems.Count;
            //生成项
            if (MaxNum > 0)
            {
                for (int i = _virtualItems.Count; i < LeastItemCount; i++)
                {
                    GameObject item = GameObject.Instantiate(Item.gameObject);
                    item.transform.SetParent(_parent.content);
                    ItemInfo itemInfo = new ItemInfo();
                    itemInfo.item = item.GetComponent<RectTransform>();
                    itemInfo.item.anchoredPosition = new Vector2(0,0);
                    itemInfo.index = i;
                    _virtualItems.Add(itemInfo);
                    item.SetActive(false);
                    item.GetComponent<RectTransform>().localScale = Vector3.one;
                }
            }
            else
            {
                MaxNum = -MaxNum;
                for (int i = 0; i < MaxNum; i++)
                {
                    GameObject gameObject = _virtualItems[_virtualItems.Count - 1].item.gameObject;
                    _virtualItems.RemoveAt(_virtualItems.Count - 1);
                    Destroy(gameObject);
                }
            }
            if (_parent)
            {
                _parent.onValueChanged.RemoveAllListeners();
                _parent.onValueChanged.AddListener(ListenerChange);
            }
        }
    }
    #endregion
    /// <summary>
    /// 设置总项数,虚拟列表创建显示个数
    /// 最大个数必须大于可视化个数1倍
    /// </summary>
    public int numItems
    {
        get { return _numItems; }
        set
        {
            if (Padding==null)
                throw new System.Exception("VirtualScroll: Set Padding first!");
            if (itemRenderer == null)
                throw new System.Exception("VirtualScroll: Set itemRenderer first!");
            _numItems = value;
            CalcuglateCount();
            RefreshList();
        }
    }
    /// <summary>
    /// 刷新 无方向
    /// </summary>
    private void RefreshItem()
    {
        //直接刷新就全部刷新
        for (int i = 0; i < _virtualItems.Count; i++)
        {
            if (!_virtualItems[i].item.gameObject.activeSelf)
            {
                _virtualItems[i].item.gameObject.SetActive(true);
            }
            CalibrationPos(_virtualItems[i]);
            itemRenderer?.Invoke(_virtualItems[i].index, _virtualItems[i].item.gameObject);//
        }
    }
    /// <summary>
    /// 校准位置
    /// 向上对齐,所以索引越大,负数越大
    /// </summary>
    private void CalibrationPos(ItemInfo itemInfo)
    {
        if (_direction == Direction.Vertical)
        {
            float _oldPos = itemInfo.item.anchoredPosition.y;
            float Y = Padding.top + itemInfo.index * ItemSize.y + itemInfo.index * Spacing;
            Y *= -1;
            if (!Mathf.Approximately(_oldPos, Y))
            {
                itemInfo.item.anchoredPosition = new Vector2(itemInfo.item.anchoredPosition.x, Y);
            }
        }
        else
        {
            float _oldPos = itemInfo.item.anchoredPosition.x;
            float Y = Padding.left + itemInfo.index * ItemSize.x + itemInfo.index * Spacing;
            if (!Mathf.Approximately(_oldPos, Y))
            {
                itemInfo.item.anchoredPosition = new Vector2(Y, itemInfo.item.anchoredPosition.y);
            }
        }
    }
    #region 刷新项信息
    private void RefreshItemInfo()
    {
        if (_direction == Direction.Vertical)
        {
            RefreshVItem();
        }
        else
        {
            RefreshHItem();
        }
    }
    /// <summary>
    /// 刷新垂直
    /// </summary>
    private void RefreshVItem()
    {
        float contentY = _parent.content.anchoredPosition.y;
        contentY -= Padding.top;
        //当前可显示的下标
        int index = (int)Mathf.Floor(contentY / (ItemSize.y + Spacing));
        index = index < 0 ? 0 : index;
        int VirtualIndex = _virtualItems[0].index;
        if (index == VirtualIndex)//第一索引和当前索引一致就返回过滤
        {
            return;
        }
        else if (index < VirtualIndex)
        {
            //根据索引移动元素
            for (int i = 0; i < _virtualItems.Count; i++)
            {
                VirtualIndex -= 1;
                ItemInfo itemInfo = _virtualItems[_virtualItems.Count - 1];
                GameObject gameObject = itemInfo.item.gameObject;
                _virtualItems.RemoveAt(_virtualItems.Count - 1);
                _virtualItems.Insert(0, itemInfo);
                if (VirtualIndex == index)
                {
                    break;
                }
            }
        }
        else
        {
            for (int i = 0; i < _virtualItems.Count; i++)
            {
                VirtualIndex += 1;
                ItemInfo itemInfo = _virtualItems[0];
                GameObject gameObject = itemInfo.item.gameObject;
                _virtualItems.RemoveAt(0);
                _virtualItems.Add(itemInfo);
                if (VirtualIndex == index)
                {
                    break;
                }
            }
        }
        //获取位置为负数
        float startY = (Padding.top + index * (ItemSize.y + Spacing)) * -1;
        int itemIndex = 0;
        for (int i = index; i < _numItems; i++)
        {
            if (_virtualItems.Count <= itemIndex)//最多只允许刷新项那么多
            {
                break;
            }
            if (!_virtualItems[itemIndex].item.gameObject.activeSelf)
            {
                _virtualItems[itemIndex].item.gameObject.SetActive(true);
            }
            if (_virtualItems[itemIndex].index != i)//只有索引不一致才刷新
            {
                _virtualItems[itemIndex].index = i;
                _virtualItems[itemIndex].item.anchoredPosition = new Vector2(_virtualItems[itemIndex].item.anchoredPosition.x, startY);
                itemRenderer?.Invoke(i, _virtualItems[itemIndex].item.gameObject);
            }
            startY -= ItemSize.y + Spacing;
            itemIndex++;
        }

        //多余的项隐藏
        //for (int i = itemIndex; i < _virtualItems.Count; i++)
        //{
        //    if (_virtualItems[i].item.gameObject.activeSelf)
        //    {
        //        _virtualItems[i].item.gameObject.SetActive(false);
        //    }
        //}
    }
    /// <summary>
    /// 刷新水平
    /// </summary>
    private void RefreshHItem()
    {
        float contentY = _parent.content.anchoredPosition.x;
        contentY += Padding.left;
        contentY = -contentY;
        //当前可显示的数据下标
        int index = (int)Mathf.Floor(contentY / (ItemSize.x + Spacing));
        index = index < 0 ? 0 : index;
        int VirtualIndex = _virtualItems[0].index;
        if (index == VirtualIndex)//第一索引和当前索引一致就返回过滤
        {
            return;
        }
        else if (index < VirtualIndex)
        {
            //根据索引移动元素
            for (int i = 0; i < _virtualItems.Count; i++)
            {
                VirtualIndex -= 1;
                ItemInfo itemInfo = _virtualItems[_virtualItems.Count - 1];
                GameObject gameObject = itemInfo.item.gameObject;
                _virtualItems.RemoveAt(_virtualItems.Count - 1);
                _virtualItems.Insert(0, itemInfo);
                if (VirtualIndex == index)
                {
                    break;
                }
            }
        }
        else
        {
            for (int i = 0; i < _virtualItems.Count; i++)
            {
                VirtualIndex += 1;
                ItemInfo itemInfo = _virtualItems[0];
                GameObject gameObject = itemInfo.item.gameObject;
                _virtualItems.RemoveAt(0);
                _virtualItems.Add(itemInfo);
                if (VirtualIndex == index)
                {
                    break;
                }
            }
        }
        //获取位置为负数
        float startY = Padding.left + index * (ItemSize.x + Spacing);
        int itemIndex = 0;
        for (int i = index; i < _numItems; i++)
        {
            if (_virtualItems.Count <= itemIndex)//最多只允许刷新项那么多
            {
                break;
            }
            if (!_virtualItems[itemIndex].item.gameObject.activeSelf)
            {
                _virtualItems[itemIndex].item.gameObject.SetActive(true);
            }
            if (_virtualItems[itemIndex].index != i)//只有索引不一致才刷新
            {
                _virtualItems[itemIndex].index = i;
                _virtualItems[itemIndex].item.anchoredPosition = new Vector2(startY, _virtualItems[itemIndex].item.anchoredPosition.y);
                itemRenderer?.Invoke(i, _virtualItems[itemIndex].item.gameObject);
            }
            startY += ItemSize.x + Spacing;
            itemIndex++;
        }

        //多余的项隐藏
        //for (int i = itemIndex; i < _virtualItems.Count; i++)
        //{
        //    if (_virtualItems[i].item.gameObject.activeSelf)
        //    {
        //        _virtualItems[i].item.gameObject.SetActive(false);
        //    }
        //}
    }
    #endregion
    //===========================================外部调用
    /// <summary>
    /// 校准刷新项
    /// </summary>
    public void CalibrationRefresh()
    {
        RefreshItemInfo();
    }
    /// <summary>
    /// 获取指定索引的项
    /// </summary>
    public GameObject GetListAt(int index)
    {
        ItemInfo itemInfo = _virtualItems.Find(item=>(item.index == index));
        if (itemInfo!=null)
        {
            return itemInfo.item.gameObject;
        }
        return null;
    }
    /// <summary>
    /// 刷新当前显示列表的数据
    /// </summary>
    public void RefreshList()
    {
        RefreshItem();
    }
    /// <summary>
    /// 刷新指定索引的数据
    /// </summary>
    public void RefreshListIndex(int index)
    {
        ItemInfo itemInfo = _virtualItems.Find(item => (item.index == index));
        if (itemInfo==null)
        {
            throw new System.Exception("RefreshListIndex: index out:"+ index);
        }
        else
        {
            itemRenderer?.Invoke(index, itemInfo.item.gameObject);
        }
    }
    /// <summary>
    /// 跳转到指定索引
    /// </summary>
    /// <param name="first"></param>
    public void ScrollToView(ScrollTo scrollTo,bool anim =false)
    {
        if (scrollTo == ScrollTo.First)
        {
            ScrollToView(0, anim);
        }
        else if (scrollTo== ScrollTo.Last)
        {
            ScrollToView(_numItems-1, anim);
        }
        else
        {
            throw new System.Exception("ScrollToView: ScrollTo is null!");
        }
    }
    /// <summary>
    /// 跳转到指定索引
    /// </summary>
    public void ScrollToView(int index, bool anim = false)
    {
        if (index < 0)
        {
            throw new System.Exception("ScrollToView: index < 0!");
        }
        else if (index > (_numItems - 1))
        {
            throw new System.Exception("ScrollToView: index > Max Value!");
        }
        else
        {
            if (_direction == Direction.Vertical)
            {
                float height = (ItemSize.y + Spacing) * index;//+ Padding.top
                //
                float MaxHeight = _parent.content.rect.height - _parent.viewport.rect.height - Padding.bottom;
                if (height > MaxHeight)
                {
                    height = MaxHeight;
                }
                if (anim)
                {
                    _parent.content.DOAnchorPosY(height, 0.5f, true);
                }
                else
                {
                    _parent.content.anchoredPosition = new Vector2(_parent.content.anchoredPosition.x, height);
                }
            }
            else
            {
                float height = (ItemSize.x + Spacing) * index;//+ Padding.left
                float MaxHeight = _parent.content.rect.width - _parent.viewport.rect.width - Padding.right;
                if (height > MaxHeight)
                {
                    height = MaxHeight;
                }
                if (anim)
                {
                    _parent.content.DOAnchorPosX(-height, 0.5f, true);
                }
                else
                {
                    _parent.content.anchoredPosition = new Vector2(-height, _parent.content.anchoredPosition.y);
                }
            }
        }
    }
}
/// <summary>
/// 跳转到
/// </summary>
public enum ScrollTo
{
    /// <summary>
    /// 第一
    /// </summary>
    First,
    /// <summary>
    /// 最后
    /// </summary>
    Last
}

还需要在Editor文件夹下创建一个脚本用于编辑器显示

using UnityEditor;
[CustomEditor(typeof(VirtualScroll))]
public class CustomVirtualScroll : Editor
{
   
}

显示出来是这样的

 下面参数是新加的,类似GridLayoutGroup组件参数,上面参数是原本就有的

参数都可以代码加载,需要自己去添加方法

使用方式也很简单,和FGUI列表使用类似

VirtualScroll VScrollRect;
public void Init()
{
    VScrollRect.itemRenderer = ItemRenderer;
    VScrollRect.numItems = MaxNum;
    //VScrollRect.RefreshList();//全部刷新
}
/// <summary>
/// 渲染刷新
/// </summary>
/// <param name="index">当前索引</param>
/// <param name="item">返回的对象</param>
public void ItemRenderer(int index,GameObject item)
{
    //处理逻辑
}

这个只适用于项的大小一致,不一致的需要自己去修改,我的代码有点乱,不太好看,里面用了DG.Tweening来跳转,不使用的直接注释掉就行了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是 Unity 中实现虚拟列表的示例代码: ```csharp using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; public class VirtualList : MonoBehaviour { public GameObject listItemPrefab; public Transform contentPanel; public ScrollRect scrollRect; public int itemCount = 100; public int visibleItemCount = 10; private List<GameObject> listItemPool = new List<GameObject>(); private List<GameObject> activeListItems = new List<GameObject>(); private Vector2 listItemSize; private int firstVisibleIndex = 0; void Start() { listItemSize = listItemPrefab.GetComponent<RectTransform>().rect.size; contentPanel.GetComponent<RectTransform>().sizeDelta = new Vector2(contentPanel.GetComponent<RectTransform>().sizeDelta.x, listItemSize.y * itemCount); scrollRect.onValueChanged.AddListener(OnScroll); for (int i = 0; i < visibleItemCount; i++) { GameObject listItem = CreateListItem(); listItem.transform.SetParent(contentPanel, false); activeListItems.Add(listItem); } } void OnScroll(Vector2 scrollPos) { int newFirstVisibleIndex = Mathf.FloorToInt(scrollPos.y * (itemCount - visibleItemCount)); if (newFirstVisibleIndex != firstVisibleIndex) { int offset = newFirstVisibleIndex - firstVisibleIndex; for (int i = 0; i < activeListItems.Count; i++) { int newIndex = firstVisibleIndex + i + offset; if (newIndex >= 0 && newIndex < itemCount) { activeListItems[i].GetComponentInChildren<Text>().text = "Item " + newIndex.ToString(); activeListItems[i].SetActive(true); activeListItems[i].GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -newIndex * listItemSize.y); } else { activeListItems[i].SetActive(false); } } firstVisibleIndex = newFirstVisibleIndex; } } GameObject CreateListItem() { GameObject listItem = null; if (listItemPool.Count > 0) { listItem = listItemPool[0]; listItemPool.RemoveAt(0); } else { listItem = Instantiate(listItemPrefab); } return listItem; } void RecycleListItem(GameObject listItem) { listItem.SetActive(false); listItemPool.Add(listItem); } } ``` 这个脚本会在 Start() 方法中计算列表项的大小,设置滚动内容面板的大小,并创建足够数量的列表项。当滚动位置发生变化时,它会检查第一个可见项的索引是否有变化,如果有则更新列表项的内容并将它们放置在正确的位置上。当列表项不再可见时,它会回收它们并将它们添加到池中以供重用。 在这个示例中,列表项是简单的 Text 对象,但你可以根据需要将其替换为任何其他对象。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值