測試環境
操作系統: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,請與指正。