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

  • 相关阅读:
    PHP学习笔记
    $POST 、$HTTP_RAW_POST_DATA、php://input三者之间的区别
    APACHE支持.htaccess以及 No input file specified解决方案
    PHP常用函数总结
    PHP网页的工作原理
    Lamp源码安装参考教程
    php相关配置
    PHP技巧:提高PHP性能的53个技巧
    面向对象工具
    面向对象基础
  • 原文地址:https://www.cnblogs.com/heitao/p/6629146.html
Copyright © 2020-2023  润新知