前言
在我们使用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()
{
}
//---------------子类重写----------------------
}