之前先初步实现了功能,后来抽象出了一个父类,继承父类实现接口。
使用者只需要场景里拼好UI,然后自定义一个负责显示的类继承指定的基类,(数据类肯定是早就在其他模块就已经实现好了的),然后实例化LoopList对象调用Init方法即可满足需求。
不足:
1.格子都是大小一样的。
2.目前只完成了垂直方向的功能。
3.代码应该可以更简化(之前参考的一些代码实现该需求不到200行代码)
实现功能:
使用方式:
如下,设置好rect挂载Scroll Rect组件,指定content,content为rect的子物体,目前只实现了垂直方向的功能,如需水平方向,可自行实现。
item设置:
注意锚点和中心点,该go为最终布局在content中的格子,锚点和中心点的设置也可用代码设置
content设置:
同item设置
自定义数据类,作为最终每个itemGo显示的内容
自定义view类:
继承一个泛型父类,并将泛型参数设置为自定义的数据类
测试代码,挂载于任意Go上即可。
最后的核心类登场!
using System.Collections.Generic;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class ICustomGridItem<T> where T : class
{
protected T info;
protected GameObject go;
public virtual void Init(GameObject go)
{
this.go = go;
}
public virtual void UpdateView()
{
}
public void SetInfo(T info)
{
this.info = info;
UpdateView();
}
}
public class MyLoopList<DataType, ItemType> where ItemType : ICustomGridItem<DataType>, new() where DataType:class
{
RectTransform scrollRect;
RectTransform contentTR;
public GameObject ItemGo;
public int GridHeight; //
public int GridWidth;
public int wholeItemCount; //格子总数
public int lineCount; //每一行的格子数
public Vector2 space;
int line; //面板里的行数
float contentHeight; //content根据grid数量初始化后的高度
float lastContentRTy;
int itemCountInView; //面板里可容纳的最大Item数量
float scrollViewHeight; //面板的初始高度
int firstLineIndex; //当前面板中的第一个Item的index
LinkedList<RectTransform> itemList = new LinkedList<RectTransform>();
int itemDataIndex;
Dictionary<GameObject, ICustomGridItem<DataType>> gridList = new Dictionary<GameObject, ICustomGridItem<DataType>>();
DataType[] datas;
public void Init(DataType[] datas, Vector2 space, int gridHeight,
int gridWidth, int lineCount, int wholeItemCount, GameObject itemGo, RectTransform scrollRect)
{
this.scrollRect = scrollRect;
this.datas = datas;
GridHeight = gridHeight;
GridWidth = gridWidth;
this.lineCount = lineCount;
this.wholeItemCount = wholeItemCount;
this.space = space;
ItemGo = itemGo;
contentTR = scrollRect.Find("content").GetComponent<RectTransform>();
if (contentTR == null)
{
Debug.LogError("应该有叫'content'的子物体");
return;
}
var rect = scrollRect.GetComponent<RectTransform>().rect;
scrollViewHeight = rect.height;
Vector2 size = Vector2.zero;
size.x = rect.width;
contentHeight = (space.y + GridHeight) * wholeItemCount / lineCount; //设置content的高度
size.y = contentHeight;
contentTR.sizeDelta = size; //content的总大小被设置
//上一帧的 content y轴位置
lastContentRTy = GetContentPositionY();
//将预设的宽高设置
(ItemGo.transform as RectTransform).sizeDelta = new Vector2(GridWidth, GridHeight);
//计算面板里能显示多少行 +2 防止穿帮
line = (int)(scrollViewHeight /(GridHeight+space.x)) + 2;
itemCountInView = line * lineCount; // view窗口里显示的item数量
for (int i = 1; i < itemCountInView + 1; i++)
{
GameObject go = GameObject.Instantiate(ItemGo, contentTR);
var item = new ItemType();
item.Init(go);
gridList.Add(go,item );
Vector3 pos = Vector3.zero;
int count = (i % lineCount);
if (count == 0) count = lineCount;
pos.x = (i - 1) % lineCount * GridWidth + count * space.x;
int hang = (int)(Mathf.Ceil(i * 1.0f / lineCount));
pos.y = -((hang - 1) * GridHeight + hang * space.y);
pos.z = 0;
var rectt = (go.transform as RectTransform);
rectt.anchoredPosition = pos;
itemList.AddLast(rectt);
}
//第一个go的索引 最后一个go的索引(对应的数据索引)
firstLineIndex = 0;
scrollRect.GetComponent<ScrollRect>().onValueChanged.AddListener(OnValueChanged);
ItemGo.SetActive(false);
Refresh();
}
public void OnValueChanged(Vector2 pos)
{
float nowContentRTy = GetContentPositionY();
//往上移
bool isUp = false;
if (nowContentRTy - lastContentRTy > 0)
{
if (scrollViewHeight + GetItemPositionY(false) + nowContentRTy > GridHeight
&& contentHeight - nowContentRTy > scrollViewHeight)
{
isUp = true;
MoveItemGo(isUp);
firstLineIndex += lineCount;
}
}
else
{
if (-GetItemPositionY(true) - nowContentRTy > space.y
&& nowContentRTy > 0)
{
isUp = false;
MoveItemGo(isUp);
firstLineIndex -= lineCount;
}
}
lastContentRTy = nowContentRTy;
}
//true 表示是处理第一个
float GetItemPositionY(bool first)
{
float y;
if (first)
y = itemList.First.Value.anchoredPosition.y;
else
y = itemList.Last.Value.anchoredPosition.y;
return y;
}
float GetContentPositionY()
{
return contentTR.anchoredPosition.y;
}
void SetInfo(ICustomGridItem<DataType> item, int index)
{
if(index >=0 && index < datas.Length)
{
item.SetInfo(datas[index]);
}
else
{
item.SetInfo(null);
}
}
void MoveItemGo(bool up)
{
if (up)
{//tou
for (int i = 0; i < lineCount; i++)
{
var item = itemList.First.Value;
Vector3 pos = item.anchoredPosition;
pos.y -= line * (GridHeight + space.y);
item.anchoredPosition = pos;
itemList.RemoveFirst();
itemList.AddLast(item);
//注入数据
int index = firstLineIndex +(line-1)*lineCount + lineCount + i;
SetInfo(gridList[item.gameObject], index);
}
}
else
{
for (int i = 0; i < lineCount; i++)
{
var item = itemList.Last.Value;
Vector3 pos = item.anchoredPosition;
pos.y += line * (GridHeight + space.y);
item.anchoredPosition = pos;
itemList.RemoveLast();
itemList.AddFirst(item);
int index = firstLineIndex - i - 1;
SetInfo(gridList[item.gameObject], index);
}
}
}
void Refresh()
{
var value = itemList.First;
for (int i = firstLineIndex; i < firstLineIndex + itemCountInView; i++)
{
if (i >= datas.Length) return;
if (value == null) return;
gridList[value.Value.gameObject].SetInfo(datas[i]);
gridList[value.Value.gameObject].UpdateView();
value = value.Next;
}
}
}