测试环境
操作系统:Windows8.1
开发工具:Unity5.5.2
1、问题描述,在实际开发过程中经常会使用ScrollRect实现滚动列表,当初次加载数据比较多的情形时,Unity3D会出现比较严重的卡顿,降低帧率,其原因主要为 a、集中式的申请ItemRenderer对象大量堆内存,b、帧Draw Call增加。
2、解决方案,主要逻辑根据Viewport即Mask Visual区域计算当前上下文显示ItemRenderer个数,同时滚动的时候会动态计算scrollLineIndex行数,来重新计算每一个ItemRenderer渲染的位置,从而复用ItemRenderer。
如图所示:当前数据已经有31个,但是ItemRenderer的实例只有21个,即当前满屏情况下最大的显示个数。

3、完成代码
UIWrapItem 用来作为数据、ItemRenderer prefab的 具体关联类。
using UnityEngine;
/// <summary>
/// Wrapper Item for user data index.
/// </summary>
public class UIWrapItem : MonoBehaviour {
/// <summary>
/// User data index
/// </summary>
private int dataIndex = 0;
/// <summary>
/// Item container
/// </summary>
private UIWrapContainer container = null;
private void OnDestroy()
{
container = null;
}
/// <summary>
/// Container setter
/// </summary>
public UIWrapContainer Container
{
set
{
container = value;
}
}
/// <summary>
/// DataIndex getter & setter
/// </summary>
public int DataIndex
{
set
{
dataIndex = value;
gameObject.name = dataIndex.ToString();
if (dataIndex >= 0)
{
transform.localPosition = container.GetLocalPositionByDataIndex(dataIndex);
if (container.onInitializeItem != null)
{
container.onInitializeItem(gameObject, dataIndex);
}
}
}
get { return dataIndex; }
}
}
UIWrapContainer作用为UIWrapItem的容器,提供基本的数据判定逻辑。
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
/// <summary>
/// This script makes it possible for a scroll view to wrap its item, creating scroll views.
/// Usage: simply attach this script underneath your scroll view where you would normally place a container:
///
/// + Scroll View
/// |- UIWrapContainer
/// |-- Item 1
/// |-- Item 2
/// |-- Item 3
/// </summary>
[DisallowMultipleComponent]
public class UIWrapContainer : MonoBehaviour
{
public delegate void OnInitializeItem(GameObject go, int dataIndex);
public OnInitializeItem onInitializeItem = null;
public enum Arraygement
{
Horizontal,
Vertical
}
#region public variables
/// <summary>
/// Type of arragement
/// </summary>
public Arraygement arrangement = Arraygement.Horizontal;
/// <summary>
/// Maximum item per line.
/// If the arrangement is horizontal, this denotes the number of columns.
/// If the arrangement is vertical, this stands for the number of rows.
/// </summary>
public int maxPerLine = 1;
/// <summary>
/// The width of each of the items.
/// </summary>
public float itemWidth = 100f;
/// <summary>
/// The height of each of the items.
/// </summary>
public float itemHeight = 100f;
/// <summary>
/// The Horizontal space of each of the items.
/// </summary>
public float itemHorizontalSpace = 0f;
/// <summary>
/// The vertical space of each of the items.
/// </summary>
public float itemVerticalSpace = 0f;
public ScrollRect scrollRect = null;
public RectTransform viewport = null;
public RectTransform container = null;
public GameObject itemPrefab = null;
#endregion
#region private variables
private int dataCount = 0;
private int maximumVisualVerticalItemCount = 0;
private int maximumVisualHorizontalItemCount = 0;
private int currentScrollLineIndex = -1;
private IList<UIWrapItem> activeItems = null;
private Queue<UIWrapItem> deactiveItems = null;
#endregion
void Awake()
{
activeItems = new List<UIWrapItem>();
deactiveItems = new Queue<UIWrapItem>();
maximumVisualVerticalItemCount = Mathf.CeilToInt(viewport.rect.height / (itemHeight + itemVerticalSpace));
maximumVisualHorizontalItemCount = Mathf.CeilToInt(viewport.rect.width / (itemWidth + itemHorizontalSpace));
}
void Start()
{
}
public void Initialize(int dataCount)
{
if (scrollRect == null || container == null || itemPrefab == null)
{
Debug.LogError("Not attach scrollRect or container or itemPrefab instance here, please check.");
return;
}
if (dataCount <= 0)
{
return;
}
setDataCount(dataCount);
scrollRect.onValueChanged.RemoveAllListeners();
scrollRect.onValueChanged.AddListener(OnValueChanged);
deactiveItems.Clear();
activeItems.Clear();
UpdateItems(0);
}
public void RemoveItem(int dataIndex)
{
if (dataIndex < 0 || dataIndex >= dataCount)
{
return;
}
bool isDestroy = activeItems.Count >= dataCount;
setDataCount(dataCount - 1);
for (int index = activeItems.Count - 1; index >= 0; index--)
{
UIWrapItem item = activeItems[index];
int itemDataIndex = item.DataIndex;
if (itemDataIndex == dataIndex)
{
activeItems.Remove(item);
if (isDestroy)
{
GameObject.Destroy(item.gameObject);
}
else
{
item.DataIndex = -1;
item.gameObject.SetActive(false);
deactiveItems.Enqueue(item);
}
}
if (itemDataIndex > dataIndex)
{
item.DataIndex = itemDataIndex - 1;
}
}
UpdateItems(GetCurrentScrollLineIndex());
}
public void AddItem(int dataIndex)
{
if (dataIndex < 0 || dataIndex > dataCount)
{
return;
}
bool isNew = false;
for (int index = activeItems.Count - 1; index >= 0; index--)
{
UIWrapItem item = activeItems[index];
if (item.DataIndex >= (dataCount - 1))
{
isNew = true;
break;
}
}
setDataCount(dataCount + 1);
if (isNew)
{
for (int index = 0, length = activeItems.Count; index < length; index++)
{
UIWrapItem item = activeItems[index];
int itemDataIndex = item.DataIndex;
if (itemDataIndex >= dataIndex)
{
item.DataIndex = itemDataIndex + 1;
}
}
UpdateItems(GetCurrentScrollLineIndex());
}
else
{
for (int index = 0, length = activeItems.Count; index < length; index++)
{
UIWrapItem item = activeItems[index];
int itemDataIndex = item.DataIndex;
if (itemDataIndex >= dataIndex)
{
item.DataIndex = itemDataIndex;
}
}
}
}
public Vector3 GetLocalPositionByDataIndex(int dataIndex)
{
float x = 0f;
float y = 0f;
float z = 0f;
switch (arrangement)
{
case Arraygement.Horizontal:
{
x = (dataIndex / maxPerLine) * (itemWidth + itemHorizontalSpace);
y = -(dataIndex % maxPerLine) * (itemHeight + itemVerticalSpace);
break;
}
case Arraygement.Vertical:
{
x = (dataIndex % maxPerLine) * (itemWidth + itemHorizontalSpace);
y = -(dataIndex / maxPerLine) * (itemHeight + itemVerticalSpace);
break;
}
}
return new Vector3(x, y, z);
}
private void setDataCount(int count)
{
if (count != dataCount)
{
dataCount = count;
UpdateContainerSize();
}
}
private void OnValueChanged(Vector2 value)
{
switch (arrangement)
{
case Arraygement.Vertical:
{
float y = value.y;
if (y >= 1.0f || y <= 0.0f)
{
return;
}
break;
}
case Arraygement.Horizontal:
{
float x = value.x;
if (x <= 0.0f || x >= 1.0f)
{
return;
}
break;
}
}
int scrollPerLineIndex = GetCurrentScrollLineIndex();
if (scrollPerLineIndex == currentScrollLineIndex) { return; }
UpdateItems(scrollPerLineIndex);
}
private void UpdateItems(int scrollLineIndex)
{
if (scrollLineIndex < 0)
{
return;
}
currentScrollLineIndex = scrollLineIndex;
int startDataIndex = currentScrollLineIndex * maxPerLine;
int endDataIndex = (currentScrollLineIndex + (arrangement == Arraygement.Vertical ? maximumVisualVerticalItemCount : maximumVisualHorizontalItemCount)) * maxPerLine;
for (int index = activeItems.Count - 1; index >= 0; index--)
{
UIWrapItem item = activeItems[index];
int itemDataIndex = item.DataIndex;
if (itemDataIndex < startDataIndex || itemDataIndex >= endDataIndex)
{
item.DataIndex = -1;
activeItems.Remove(item);
item.gameObject.SetActive(false);
deactiveItems.Enqueue(item);
}
}
for (int dataIndex = startDataIndex; dataIndex < endDataIndex; dataIndex++)
{
if (dataIndex >= dataCount)
{
continue;
}
if (Exists(dataIndex))
{
continue;
}
CreateItem(dataIndex);
}
}
private void CreateItem(int dataIndex)
{
UIWrapItem item = null;
if (deactiveItems.Count > 0)
{
item = deactiveItems.Dequeue();
item.gameObject.SetActive(true);
}
else
{
item = AddChild(itemPrefab, container).AddComponent<UIWrapItem>();
}
item.Container = this;
item.DataIndex = dataIndex;
activeItems.Add(item);
}
private bool Exists(int dataIndex)
{
if (activeItems == null || activeItems.Count <= 0)
{
return false;
}
for (int index = 0, length = activeItems.Count; index < length; index++)
{
if (activeItems[index].DataIndex == dataIndex)
{
return true;
}
}
return false;
}
private GameObject AddChild(GameObject prefab, Transform parent)
{
if (prefab == null || parent == null)
{
Debug.LogError("Could not add child with null of prefab or parent.");
return null;
}
GameObject go = GameObject.Instantiate(prefab) as GameObject;
go.layer = parent.gameObject.layer;
go.transform.SetParent(parent, false);
return go;
}
private void OnDestroy()
{
scrollRect = null;
container = null;
itemPrefab = null;
onInitializeItem = null;
activeItems.Clear();
deactiveItems.Clear();
activeItems = null;
deactiveItems = null;
}
}
4、未完待续
实际在开中主要使用数组集合作为参数及动态的Add or Rmove操作,所以接下来将会迭代掉采用数组下标的方式提供数据的初始化,增加删除等操作。
最后实现仓促难免存在bug,请与指正。
来源:https://www.cnblogs.com/heitao/p/6629146.html