UGUI ScrollRect 性能優化


測試環境


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM