ScrollRect算是项目中需要经常用到的一个组件。但是因为unity本身并不支持scrollrect的虚拟化显示。在数据量很大时scrollrect中就会创建了过多的子项,导致项目卡顿。所以抽空简单实现了下项的重复使用,以达到性能优化的目的。
首先,先在界面上放一个ScrollRect,建立一个用于复制的子项:
因为当前并不处理横向滚动,故先将横向的Scrollbar干点了。
然后在控件上绑定自己写的类,在Start中动态生成一定数量的项用于动态显示:
wawawa
void Start()
{
//这个类需要和ScrollRect组件绑在一起,如果没有,请重新对ScrollRect赋值
ScrollRect = GetComponent<ScrollRect>();
//将Content固定为顶对齐
ScrollRect.content.anchorMin = new Vector2(0,1);
ScrollRect.content.anchorMax = new Vector2(1, 1);
//y轴固定为0,方便计算
ScrollRect.content.anchoredPosition = new Vector2(ScrollRect.content.anchoredPosition.x,0);
//先将单个项的中心点移到顶部
Vector2 pivot = ItemBase.pivot;
pivot.y = 1;
ItemBase.pivot = pivot;
//将项修改为顶部对齐方式
ItemBase.anchorMin = new Vector2(0.5f, 1);
ItemBase.anchorMax = new Vector2(.5f, 1);
itemHeight = ItemBase.sizeDelta.y;
//计算需要创建多少个项用于重复显示
//使用ScrollRect的高度,才能正确反应可视区域的高度
float height = ScrollRect.GetComponent<RectTransform>().sizeDelta.y;
height -= TopMagin + BottomMagin;
int itemCount = (int)Mathf.Ceil(height / (itemHeight + ItemMagin));
//多创建两个,避免上下滑动时出现穿帮
itemCount += 2;
ItemBase.gameObject.SetActive(false);
for (int i = 0; i < itemCount; i++)
{
GameObject item = GameObject.Instantiate(ItemBase.gameObject);
item.transform.SetParent(ScrollRect.content);
Items.Add(item.GetComponent<RectTransform>());
Items[i].anchoredPosition = Vector2.zero;
}
//监听 onValueChanged事件,以便对Items进行数据和坐标的更新
ScrollRect.onValueChanged.AddListener((Vector2 vec2)=> {
ResetItemInfo();
});
}
里面用到的一些局部属性定义:
private ScrollRect ScrollRect;
/// <summary>
/// 用于循环的列表项
/// </summary>
public RectTransform ItemBase;
/// <summary>
/// 每个项的间距
/// </summary>
public float ItemMagin;
/// <summary>
/// 第一个项和顶部的距离
/// </summary>
public float TopMagin = 0;
/// <summary>
/// 最后一个项同底部的距离
/// </summary>
public float BottomMagin = 0;
/// <summary>
/// 回调,用于刷新单个项的数据,第一个参数为项实例,第二个参数为数据下标
/// </summary>
public Action<RectTransform, int> OnItemChanged;
/// <summary>
/// 单个项的高度
/// </summary>
private float itemHeight = 0;
private int DataCount;
private List<RectTransform> Items = new List<RectTransform>();
然后就是公布一个外部可调用的接口方法。这个类本身并不需要关注数据的具体信息,所以只需要传入数据长度用于计算scrollrect的content高度就好了:
public void Refresh(int dataCount)
{
DataCount = dataCount;
//先计算出总的高度
float height = itemHeight * DataCount + (DataCount - 1) * ItemMagin + TopMagin + BottomMagin;
//设置出content新的高度
ScrollRect.content.sizeDelta = new Vector2(ScrollRect.content.sizeDelta.x,height);
ResetItemInfo();
}
最重要的部分当然是对循环项的重新赋值和坐标定位了啊:
private void ResetItemInfo()
{
float contentY = ScrollRect.content.anchoredPosition.y;
contentY -= TopMagin;
//当前可显示的数据下标
int index = (int)Mathf.Floor(contentY / (itemHeight + ItemMagin));
index = index < 0 ? 0 : index;
float startY = TopMagin + index * itemHeight + ItemMagin * (index - 1);
startY = -startY;
int itemIndex = 0;
for (int i = index; i < DataCount; i++)
{
if (Items.Count <= itemIndex)
{
break;
}
if (!Items[itemIndex].gameObject.activeSelf)
{
Items[itemIndex].gameObject.SetActive(true);
}
Items[itemIndex].anchoredPosition = new Vector2(Items[itemIndex].anchoredPosition.x,startY);
//回调,外部绑定OnItemChange以便对项的显示进行刷新
OnItemChanged?.Invoke(Items[itemIndex],i);
startY -= itemHeight + ItemMagin;
itemIndex++;
}
//多余的项隐藏
for (int i = itemIndex; i < Items.Count; i++)
{
if (Items[i].gameObject.activeSelf)
{
Items[i].gameObject.SetActive(false);
}
}
}
具体使用的时候就只需要刷新数据长度和绑定OnItemChanged事件回调进行数据刷新就好啦。
这是测试代码:先根据输入的数字创建指定长度的数据。然后在回调中处理更新单个项的显示信息:
int num = 0;
if(int.TryParse(InputField.text,out num))
{
if (num > 0)
{
int index = datas.Count;
for (int i = 0; i < num; i++)
{
datas.Add(new Data() {
name="Item"+(index+i)
});
}
VirtualScroll.Refresh(datas.Count);
}
}
VirtualScroll.OnItemChanged = (RectTransform item, int index) => {
item.GetComponentInChildren<Text>().text = datas[index].name;
};
详细的例子可以看我上传的资源:https://download.csdn.net/download/lbfht/87300730