git仓库:keiqkeqi321/LoopScrollView
1.实现目的
游戏功能中会遇到几百上千个条目放在一个页面上的需求,这无疑是不现实的.解决的方法是使用 UGUI 的ScrollView组件,将其 Content 加上 GridLayoutGroup网格排版组件和ContentSizeFilter,根据内容自动调整尺寸组件.再数据列表项全部生成出来加到Content里面.这就是基础的滚动视图的做法.
这样虽然能实现一些少量数据的需求,但是显示海量数据对内存也是极大的考验.
2.实现思路
1.只显示可视区域
既然可展示的数据只有ScrollView 的Viewport那么大,那么就可以只生成并显示 Viewport覆盖的所有的列表项就可以.列表数据注入的时候根据数据规模调整 content的 size,使其尺寸可以容纳所有的列表项,并计算可视范围可以容纳多少个列表项,生成可视的所有列表项多一行的列表项.
totalRow = Mathf.CeilToInt(count / (float)constraintCount);
rectTrans.sizeDelta = new UnityEngine.Vector2(rectTrans.sizeDelta.x, totalRow *
cellSize.y + (totalRow - 1) * spacing.y+padding.top);
viewRow = Mathf.CeilToInt(frame.y / (int)(cellSize.y + spacing.y) + 1);
int viewCount=viewRow * constraintCount;
int needSpawn=viewCount;
for (int i = 0; i < needSpawn; i++)
{
AddItem(Spawn().gameObject);
}
2.处理视图滚动
ScrollView的视图滚动的时候,会触发一个事件,订阅这个事件来处理向上向下滚动,计算这一帧的滚动跨度,如果大于每行个数就调用 JumpTo 来直接跳跃到需要显示的内容.上滑时检查最下一行列表项的下边界是否高于视图下边界,如果高于则将最上一行列表项回收,在最后一行生成.下滑检测也是如此.
Vector2 last = new Vector2();
void OnSlide(Vector2 vector)
{
float span = Mathf.Abs(vector.y - last.y);
if (loopItemDatas.Count * span > constraintCount)
{
loopContent.JumpTo(vector.y);
}
else
{
if (vector.y < last.y)
{
UpSlideCheck(vector);
}
else if (vector.y > last.y)
{
DownSlideCheck(vector);
}
}
last = vector;
}
void DownSlideCheck(Vector2 vector)
{
float top = loopContent.TopPosi;
float max = viewPort.position.y;
if (top < max)
{
//
loopContent.DownSlide();
}
}
void UpSlideCheck(Vector2 vector)
{
float bottom = loopContent.BottomPosi;
float min = viewPort.position.y - viewSize.y;
if (bottom > min)
{
//
loopContent.UpSlide();
}
}
3.数据驱动
显示层的东西都是从原列表项中读取出来的,并且没有对原数据列表隔壁那个改.所以增删改等操作只需要改动原列表数据,然后调用刷新,视图就会自动刷新.
3.如何使用
1.准备工作
创建用于承载显示数据的数据类,定义需要展示的所有数据,以及点击回调
public class LoopItemDataExample : LoopItemData
{
public Action clickAction;
public String showText;
}
创建用于显示列表项的 UI 组件类
public class LoopItemExample : LoopItem
{
public Text showText;
LoopItemDataExample data1;
public Button button;
void Start(){
button.onClick.AddListener(()=>{
data1?.clickAction?.Invoke();
});
}
protected override void UpdateInfor(LoopItemData data)
{
if(data.isEmpty){
button.image.color=Color.gray;
showText.text = null;
data1 =null;
return;
}
button.image.color=Color.white;
data1 = data as LoopItemDataExample;
showText.text = data1.showText;
}
}
将 LoopItemExample挂到场景需要复用显示的 itemTemplate 上.
将LoopContent挂到 Content 上
将LoopScrollView挂到ScrollView上并挂载依赖,其中Constraint用于限制每行个数
2.功能调用
初始化显示数据,并注入
public LoopScrollView loopScrollView;
public int count = 10000;
List<LoopItemData> datas ;
void Start()
{
//初始化数据
datas = new List<LoopItemData>(count);
for (int i = 0; i < count; i++)
{
var j = i;
datas.Add(new LoopItemDataExample()
{
id = j,
clickAction = () =>
{
Debug.Log("you click order " + j + " item");
},
showText = j.ToString(),
});
}
//注入驱动数据
loopScrollView.InitInfor(datas);
}
数据驱动功能使用,就是更改原数据列表,再调用刷新就好了
if (Input.GetKeyDown(KeyCode.A))
{
int id=datas.Last().id+1;
//加数据
datas.Add(new LoopItemDataExample()
{
id = datas.Last().id+1,
clickAction = () =>
{
Debug.Log("you click order " + id + " item");
},
showText = id.ToString(),
});
loopScrollView.Refresh();
}
if (Input.GetKeyDown(KeyCode.D))
{
//删除数据
datas.RemoveAt(datas.Count - 1);
loopScrollView.Refresh();
}
if (Input.GetKeyDown(KeyCode.S))
{
//改数据
(datas[0] as LoopItemDataExample).showText = "i am after";
loopScrollView.Refresh();
}
}