Calmer的文章

  • 首页
  • 文章归档
  • 关于页面

  • 搜索
体验游戏 笔记 推荐 工具链 工具使用 小游戏 插件 UI 软件 教程

循环列表的实现(UI优化)

发表于 2020-04-10 | 分类于 游戏开发 | 0 | 阅读次数 914

前言

在我们使用UGUI的时候一定用到列表框,而如果一个列表框的数据非常的多,比如说游戏中的背包系统,可能会有上千个数据,如果我们给每一个数据创建一个GameObject来表现,那就会产生性能瓶颈。
我们常常采用便是循环列表。


循环列表

ScrollView.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public struct Direction
{
    public float left;
    public float right;
    public float top;
    public float bottom;
}

public class ScrollView<T> where T:ScrollViewItem,new()
{
    //结构是 ScrollView ViewPort Content FirstItem
    //
    
    //物体View
    private GameObject view;
    private ScrollRect scrollRect;

    //内容框
    private RectTransform content;
    private GameObject contentGo;

    //模板Go
    private GameObject firstItemGo;

    //是否为垂直滚动
    private bool isVertical;

    private Vector2 initPosition; //初始位置
    private Vector2 showSize;  //viewport的大小
    private Vector2 cellSize; //item的大小
    private Vector2 spacing;  //item间隔
    private Direction padding;//上下左右的间隔

    private int maxRow;
    private int maxColumn;

    private List<ScrollViewItem> itemAry;
    private List<ScrollViewItem> recycleItemAry;

    private List<UnityEngine.Object> itemDataAry;

    private int lastItemDataAryNum;

    private int beginIndex;
    private int endIndex;

    private int tailItemCount;

    private bool alignmentCenter;

    private Action<Vector2> onValueChange;

    public ScrollView(GameObject view,ScrollRect rect,params UnityEngine.Object[] otherParam)
    {
        this.view=view;
        this.scrollRect=rect;
        Init();
    }

    public void Init()
    {
        itemAry=new List<ScrollViewItem>();
        recycleItemAry=new List<ScrollViewItem>();
        itemDataAry=new List<UnityEngine.Object>();

        lastItemDataAryNum=0;

        content=scrollRect.content;
        contentGo=content.gameObject;

        RectTransform viewport=scrollRect.viewport;
        GridLayoutGroup gridLayoutGroup = contentGo.GetComponent<GridLayoutGroup>();

        if(viewport==null)
        {
            Debug.LogError("[ScrollView] Init GridView的ScrollRect必须设置viewport");
        }

        if(gridLayoutGroup==null)
        {
            Debug.LogError("[ScrollView] Init Content 下必须加入 GridLayoutGroup");
        }

        int contentChildNum=contentGo.transform.childCount;

        if(contentChildNum>1)
        {
            while(contentGo.transform.childCount>1)
            {
                GameObject.DestroyImmediate(contentGo.transform.GetChild(1).gameObject);
            }
        }
        else if(contentChildNum<1)
        {
            Debug.LogError("[ScrollView] Init Content下必须有一个对象");
        }

        firstItemGo = contentGo.transform.GetChild(0).gameObject;

        if(firstItemGo.GetComponent<LayoutElement>()!=null)
        {
            Debug.LogError("[ScrollView] Init Item 下不要加入LayoutElement");
        }

        if(contentGo.GetComponent<ContentSizeFitter>()!=null)
        {
            Debug.LogError("[ScrollView] Init Content 下不要加入 ContentSizeFitter");
        }

        if(scrollRect.vertical==scrollRect.horizontal)
        {
            Debug.LogError("[ScrollView] Init GridView的ScrollRect必须设置vertical或horizontal 有且只有一个为true");
        }

        alignmentCenter=gridLayoutGroup.childAlignment==TextAnchor.UpperCenter;

        firstItemGo.SetActive(false);

        RefreshSize();

        scrollRect.onValueChanged.AddListener((vec)=>{
            OnValueChanged();
            onValueChange?.Invoke(vec);
            });
    }

    public void SetOnValueChanged(Action<Vector2> action)
    {
        onValueChange=action;
    }

    public void RefreshSize()
    {
        if(initPosition!=null)
        {
            RemoveAll();
            Vector2 vec=content.anchoredPosition;
            if(isVertical)
            {
                content.anchoredPosition=new Vector2(vec.x,0);
            }
            else
            {
                content.anchoredPosition=new Vector2(0,vec.y);
            }
        }

        initPosition=new Vector2(contentGo.transform.localPosition.x,contentGo.transform.localPosition.y);

        RectTransform viewport=scrollRect.viewport;
        GridLayoutGroup gridLayoutGroup=contentGo.GetComponent<GridLayoutGroup>();
        Rect viewportRect= viewport.rect;

        isVertical=scrollRect.vertical;

        showSize=new Vector2(viewportRect.width,viewportRect.height);

        if((showSize.x+showSize.y)==0)
        {
            showSize=new Vector2(viewport.parent.GetComponent<RectTransform>().rect.width,viewport.parent.GetComponent<RectTransform>().rect.height);
        }

        cellSize=new Vector2(gridLayoutGroup.cellSize.x,gridLayoutGroup.cellSize.y);
        spacing=new Vector2(gridLayoutGroup.spacing.x,gridLayoutGroup.spacing.y);
        padding.left=gridLayoutGroup.padding.left;
        padding.right=gridLayoutGroup.padding.right;
        padding.top=gridLayoutGroup.padding.top;
        padding.bottom=gridLayoutGroup.padding.bottom;

        gridLayoutGroup.enabled=false;

        RectTransform firstItemTransform =firstItemGo.GetComponent<RectTransform>();
        firstItemTransform.sizeDelta=new Vector2(cellSize.x,cellSize.y);

        Vector2 leftUpPos=new Vector2(0,1);

        firstItemTransform.anchorMin=leftUpPos;
        firstItemTransform.anchorMax=leftUpPos;
        firstItemTransform.pivot=leftUpPos;

        if(isVertical)
        {
            content.anchorMin=leftUpPos;
            content.anchorMax=Vector2.one;
        }
        else
        {
            content.anchorMin=Vector2.zero;
            content.anchorMax=leftUpPos;
        }

        content.pivot=leftUpPos;

        if(isVertical)
        {
            maxColumn = (int)Mathf.Max(1,Mathf.Floor( (showSize.x - padding.left - padding.right + spacing.x) / (cellSize.x + spacing.x) ));
            maxRow=-1;
            tailItemCount= (int)Mathf.Ceil( (showSize.y - padding.bottom + spacing.y) / (cellSize.y + spacing.y) ) * maxColumn;

        }
        else
        {
            maxRow=(int) Mathf.Max(1, Mathf.Floor( (showSize.y - padding.top - padding.bottom +spacing.y) / (cellSize.y + spacing.y) ) );
            maxColumn=-1;
            tailItemCount = (int) Mathf.Ceil( (showSize.x - padding.right + spacing.x) /(cellSize.x + spacing.x) ) * maxRow;
        }

        OnValueChanged();
    }

    private void OnValueChanged()
    {
        if(itemDataAry.Count != lastItemDataAryNum)
        {
            lastItemDataAryNum = itemDataAry.Count;

            if(isVertical)
            {
                int rowNum = (int)Mathf.Ceil(((float)lastItemDataAryNum)/maxColumn);
                float contentHeight = rowNum * cellSize.y + Mathf.Max(rowNum-1,0)* spacing.y + padding.top + padding.bottom;
                content.sizeDelta = new Vector2(0, contentHeight);
            }
            else
            {
                int columnNum = (int) Mathf.Ceil( ((float)lastItemDataAryNum)/maxRow );
                float contentWidth = columnNum * cellSize.x + Mathf.Max(columnNum-1,0) * spacing.x + padding. left + padding.right;
                content.sizeDelta = new Vector2(contentWidth , 0);
            }
        }

        if(itemDataAry.Count==0)
        {
            beginIndex=0;
            endIndex=0;
            while(itemAry.Count>0)
            {
                RecycleItem(itemAry[0]);
            }
        }
        else
        {
            Vector3 contentPosition = contentGo.transform.localPosition;
            float contentX = contentPosition.x;
            float contentY = contentPosition.y;

            float contentWidth = content.rect.width;
            float contentHeight = content.rect.height;

            if(isVertical)
            {
                if( (contentY - contentHeight)> (initPosition.y - showSize.y) )
                {
                    endIndex = itemDataAry.Count;
                    beginIndex = (int) Mathf.Max(1, Mathf.Ceil((float)itemDataAry.Count/maxColumn) * maxColumn - tailItemCount +1);
                }
                else
                {
                    float overTopHeight = Mathf.Max(0, contentY - initPosition.y - padding.top);

                    float hideRow = Mathf.Floor( (overTopHeight+spacing.y)/(cellSize.y+spacing.y) );

                    float showRow = Mathf.Ceil( (overTopHeight + showSize.y) / (cellSize.y+spacing.y) );
                    beginIndex=(int) Mathf.Max(hideRow * maxColumn +1 ,1);
                    endIndex =(int) Mathf.Min(showRow *maxColumn, itemDataAry.Count);
                }
            }
            else
            {
                if( (contentX + contentWidth) < (initPosition.x + showSize.x) )
                {
                    endIndex = itemDataAry.Count;
                    beginIndex = (int) Mathf.Max(1, Mathf.Ceil((float)itemDataAry.Count/maxRow) *maxRow -tailItemCount +1 );

                }
                else
                {
                    float overLeftWidth = Mathf.Max(0, initPosition.x - contentX - padding.left);
                    float hideColumn = Mathf.Floor( (overLeftWidth + spacing.x)/ (cellSize.x + spacing.x) );

                    float showColumn = Mathf.Floor( (overLeftWidth + showSize.x)/ (cellSize.x +spacing.x));
                    beginIndex = (int)Mathf.Max(hideColumn* maxRow +1 , 1);
                    endIndex =(int)Mathf.Min(showColumn*maxRow,itemDataAry.Count);
                }
            }

            for(int i=itemAry.Count - 1;i>=0;i--)
            {
                int itemIndex = itemAry[i].GetIndex();
                if(itemIndex < beginIndex || itemIndex> endIndex)
                {
                    RecycleItem(itemAry[i]);
                }
            }


            int itemIndx=0;
            for(int i=beginIndex;i<=endIndex;i++)
            {
                UnityEngine.Object itemData = itemDataAry[i-1];
                ScrollViewItem item=null;
                if(itemAry.Count > itemIndx)
                {
                    item = itemAry[itemIndx];
                }
                if(item!=null && item.GetIndex()==i)
                { 
                }
                else
                {
                    item = GetFreeItem();
                    itemAry.Insert(itemIndx, item);
                    item.SetIndexAndData(i, itemData);
                }
                itemIndx++;
            }

            int size=itemAry.Count;
            for(int i=0;i<itemAry.Count;i++)
            {
                int index = itemAry[i].GetIndex();
                int row,column;
                if(isVertical)
                {
                    row=(int) Mathf.Ceil((float)index/maxColumn);
                    column = index%maxColumn;
                    if(column==0)
                    {
                        column = maxColumn;
                    }
                }
                else
                {
                    column = (int)Mathf.Ceil((float)index/maxRow);
                    row=index % maxRow;
                    if(row==0)
                    {
                        row=maxRow;
                    }
                }

                if(alignmentCenter)
                {
                    float itemX= contentWidth * 0.5f + (-size* cellSize.x*0.5f -spacing.x*Mathf.Floor(size/2)) + (column - 1)* (cellSize.x+spacing.x);
                    float itemY= -padding.top - (row -1) * (cellSize.y+spacing.y);
                    itemAry[i].SetPosition(itemX,itemY);
                }
                else
                {
                    float itemX=padding.left + (column - 1)* (cellSize.x+spacing.x);
                    float itemY=-padding.top - (row -1) * (cellSize.y+spacing.y);
                    itemAry[i].SetPosition(itemX,itemY);
                }

            }
        }

    }


    private void RecycleItem(ScrollViewItem item)
    {
        item.SetActive(false);
        for(int i=0;i<itemAry.Count;i++)
        {
            if(item==itemAry[i])
            {
                item.CallRecycle();
                itemAry.Remove(item);
                break;
            }
        }
        recycleItemAry.Add(item);
    }

    public int GetDataCount()
    {
        if(itemDataAry!=null)
        {
            return itemDataAry.Count;
        }
        return 0;
    }

    public ScrollViewItem GetFreeItem()
    {
        ScrollViewItem item;
        if(recycleItemAry.Count>0)
        {
            item=recycleItemAry[0];
            recycleItemAry.RemoveAt(0);
        }
        else
        {
            item = NewItem( GameObject.Instantiate(firstItemGo,contentGo.transform) );
            item.CallInit();
        }

        item.SetIndexAndData(-1,null);
        item.SetActive(true);
        return item;
    }

    public ScrollViewItem NewItem(GameObject obj)
    {
        ScrollViewItem item=new ScrollViewItem();
        item.Init(view,obj,scrollRect);
        return item;
    }

    public void AddArray(UnityEngine.Object[] arry)
    {
        for(int i=0;i<arry.Length;i++)
        {
            itemDataAry.Add(arry[i]);
        }
        OnValueChanged();
    }

    public void RemoveAll()
    {
        itemDataAry.Clear();
        if(isVertical)
        {
            SetPosition(initPosition.y);
        }
        else
        {
            SetPosition(initPosition.x);
        }
    }

    public void SetPosition(float value)
    {
        scrollRect.StopMovement();
        if(contentGo==null)
            return;
        Vector3 oldPos =contentGo.transform.localPosition;
        Vector3 newPos;
        if(isVertical)
        {
            value= Mathf.Max(initPosition.y, value);
            value= Mathf.Min(initPosition.y + Mathf.Max(content.rect.height - showSize.y,0),value);
            newPos = new Vector3(oldPos.x,value,oldPos.z);
        }
        else
        {
            value=Mathf.Min(initPosition.x,value);
            value=Mathf.Max(initPosition.x - Mathf.Max(content.rect.width- showSize.x,0),value);
            newPos = new Vector3(value,oldPos.y,oldPos.z);
        }
        contentGo.transform.localPosition = newPos;
        OnValueChanged();
    }
}

ScrollViewItem.cs

using UnityEngine;
using UnityEngine.UI;

public class ScrollViewItem
{
    public int index;
    public Object data;
    protected bool isSelected;
    public GameObject gameObject;

    public virtual void Init(GameObject view,GameObject self,ScrollRect scrollRect,params UnityEngine.Object[] optionPar)
    {
        gameObject=self;
    }

    public int GetIndex()
    {
        return index;
    }

    public void SetIndexAndData(int pIndex,Object pData)
    {
        index=pIndex;
        data=pData;
        if(pIndex>0)
        {
            CallShow();
        }
    }

    public void SetActive(bool value)
    {
        if(gameObject!=null)
        {
            gameObject.SetActive(value);
        }
    }

    public void SetPosition(float x,float y)
    {
        if(gameObject!=null)
        {
            Vector3 nowPos = gameObject.transform.localPosition;
            gameObject.transform.localPosition = new (x,y,nowPos.z);
        }
    }

    public void CallRecycle()
    {
        Recycle(index,data);
    }

    public void CallDestroy()
    {
        Destroy();
    }

    public void CallSetSelected()
    {
        SetSelected(isSelected);
    }

    public void CallShow()
    {
        Show(data,index);
    }

    public void CallInit()
    {
        ChildInit();
    }


    public virtual void SetSelected(bool isSelected)
    {
        this.isSelected=isSelected;
    }


    //---------------子类重写----------------------
    
    public virtual void Show(UnityEngine.Object data,int index)
    {
        Text text=gameObject.transform.Find("Text").GetComponent<Text>();
        text.text=index.ToString();
    }

    public virtual void Recycle(int index,Object data)
    {

    }

    public virtual void Destroy()
    {

    }

    public virtual void ChildInit()
    {

    }
    //---------------子类重写----------------------

}
  • 本文作者: Calmer
  • 本文链接: https://mytechplayer.com/archives/循环列表的实现ui优化
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
# 工具链
Unity3D编辑器扩展(UnityEditor)
游戏开发<知识体系>
  • 文章目录
  • 站点概览
Calmer

Calmer

88 日志
7 分类
10 标签
RSS
Creative Commons
0%
© 2020 — 2025 Calmer
由 Halo 强力驱动
蜀ICP备20010026号-1川公网安备51019002006543
Copyright © 2020-2025 Calmer的文章