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,请与指正。

原文地址:https://www.cnblogs.com/heitao/p/6629146.html