• NGUI UIScrollView


    一、当UIScrollView的以下的包括的子项太多(二三十个之上)时。它的滚动就会变的有些卡不流畅,尤其是在手机上。

    对些网上也有非常多的优化它的相关,以下是我的一个优化:

    1、将在超出裁剪框的一个item的距离的item,从scrollview中销毁掉 。

    当它将要出如今裁剪框中时,再将它构造出来。-- 大家好你都是这么做的。

    2、为避免频繁的构造、销毁,导致频繁的分配内存和产生大量的内存垃圾内,导致的性能问题。我添加了一个对象池来管理item的构造与移除工作。

    3、scrollvew中的元素在普通情况下,当中的item是要求等距的。假设它的大小不一样,它们的距离就会參差不齐。而这个问题在我的优化中是不存在的。

    4、最主要是代码量少、使用简单、扩展方便。


    二、话不多说,上代码:

    1、主类:Lzh_LoopScrollView.cs

    /*
     * 描术:
     * 
     * 作者:AnYuanLzh
     * 时间:2014-xx-xx
     */
    using UnityEngine;
    using System.Collections.Generic;
    
    /// <summary>
    /// 这个类主要做了一件事,就是优化了,NGUI UIScrollView 在数据量非常多都时候,
    /// 创建过多都GameObject对象,造成资源浪费.
    /// </summary>
    public class Lzh_LoopScrollView : MonoBehaviour
    {
    	public enum ArrangeDirection
    	{
    		Left_to_Right,
    		Right_to_Left,
    		Up_to_Down,
    		Down_to_Up,
    	}
    	/// <summary>
    	/// items的排列方式
    	/// </summary>
    	public ArrangeDirection arrangeDirection = ArrangeDirection.Up_to_Down;
    
    	/// <summary>
    	/// 列表单项模板
    	/// </summary>
    	public GameObject itemPrefab;
    
    	/// <summary>
    	/// The items list.
    	/// </summary>
    	public List<Lzh_LoopItemObject> itemsList;
    	/// <summary>
    	/// The datas list.
    	/// </summary>
    	public List<Lzh_LoopItemData> datasList;
    
    	/// <summary>
    	/// 列表脚本
    	/// </summary>
    	public UIScrollView scrollView;
    
    	public GameObject itemParent;
    
    	/// <summary>
    	/// itemsList的第一个元素
    	/// </summary>
    	Lzh_LoopItemObject firstItem;
    	/// <summary>
    	/// itemsList的最后一个元素
    	/// </summary>
    	Lzh_LoopItemObject lastItem;
    
    
    	public delegate void DelegateHandler(Lzh_LoopItemObject item, Lzh_LoopItemData data);
    	/// <summary>
    	/// 响应
    	/// </summary>
    	public DelegateHandler OnItemInit;
    
    	/// <summary>
    	/// 第一item的起始位置
    	/// </summary>
    	public Vector3 itemStartPos = Vector3.zero;
    	/// <summary>
    	/// 菜单项间隙
    	/// </summary>
    	public float gapDis = 0f;
    
    	// 对象池
    	// 再次优化,频繁的创建与销毁
    	Queue<Lzh_LoopItemObject> itemLoop = new Queue<Lzh_LoopItemObject>();
    
    	void Awake()
    	{
    		if(itemPrefab==null || scrollView==null || itemParent==null)
    		{
    			Debug.LogError("Lzh_LoopScrollView.Awake() 有属性没有在inspector中赋值");
    		}
    
    		// 设置scrollview的movement
    		if(arrangeDirection == ArrangeDirection.Up_to_Down || 
    		   arrangeDirection == ArrangeDirection.Down_to_Up)
    		{
    			scrollView.movement = UIScrollView.Movement.Vertical;
    		}
    		else
    		{
    			scrollView.movement = UIScrollView.Movement.Horizontal;
    		}
    	}
    	
    	// Update is called once per frame
    	void Update ()
    	{
    		//if(scrollView.isDragging)
            {
                Validate();
            }
    	}
    
    	/// <summary>
    	/// 检验items的两端是否要补上或删除
    	/// </summary>
    	void Validate()
    	{
    		if( datasList==null || datasList.Count==0)
    		{
    			return;
    		}
    
            // 假设itemsList还不存在
            if(itemsList==null || itemsList.Count==0)
            {
                itemsList = new List<Lzh_LoopItemObject>();
    
    			Lzh_LoopItemObject item = GetItemFromLoop();
                InitItem(item, 0, datasList[0]);
                firstItem = lastItem = item;
                itemsList.Add(item);
    
    			//Validate();
            }
    
    		// 
    		bool all_invisible = true;
    		foreach(Lzh_LoopItemObject item in itemsList)
    		{
    			if(item.widget.isVisible==true)
    			{
    				all_invisible=false;
    			}
    		}
    		if (all_invisible == true)
    				return;
    
    		// 先推断前端是否要增减
    		if(firstItem.widget.isVisible)
    		{
    			// 推断要不要在它的前面补充一个item
    			if(firstItem.dataIndex>0)
    			{
    				Lzh_LoopItemObject item = GetItemFromLoop();
    
    				// 初化:数据索引、大小、位置、显示
    				int index = firstItem.dataIndex-1;
    				//InitItem(item, index, datasList[index]);
    				AddToFront(firstItem, item, index, datasList[index]);
    				firstItem = item;
    				itemsList.Insert(0,item);
    
                    //Validate();
    			}
    		}
    		else
    		{
    			// 推断要不要将它移除
    			// 条件:自身是不可见的;且它后一个item也是不可见的(或被被裁剪过半的).
    			// 		这有个隐含条件是itemsList.Count>=2.
    			if(itemsList.Count>=2
    			   && itemsList[0].widget.isVisible==false
    			   && itemsList[1].widget.isVisible==false)
    			{
                    itemsList.Remove(firstItem);
    				PutItemToLoop(firstItem);
                    firstItem = itemsList[0];
    
                    //Validate();
    			}
    		}
    
    		// 再推断后端是否要增减
    		if(lastItem.widget.isVisible)
    		{
    			// 推断要不要在它的后面补充一个item
    			if(lastItem.dataIndex < datasList.Count-1)
    			{
    				Lzh_LoopItemObject item = GetItemFromLoop();
    
    				// 初化:数据索引、大小、位置、显示
    				int index = lastItem.dataIndex+1;
    				AddToBack(lastItem, item, index, datasList[index]);
    				lastItem = item;
                    itemsList.Add(item);
    
                    //Validate();
    			}
    		}
    		else
    		{
    			// 推断要不要将它移除
    			// 条件:自身是不可见的;且它前一个item也是不可见的(或被被裁剪过半的).
    			// 		这有个隐含条件是itemsList.Count>=2.
    			if(itemsList.Count>=2
    				&& itemsList[itemsList.Count-1].widget.isVisible==false
    			   	&& itemsList[itemsList.Count-2].widget.isVisible==false)
    			{
    				itemsList.Remove(lastItem);
    				PutItemToLoop(lastItem);
    				lastItem = itemsList[itemsList.Count-1];
    
                    //Validate();
    			}
    		}
    
    
    
    	}
    
    	/// <summary>
    	/// Init the specified datas.
    	/// </summary>
    	/// <param name="datas">Datas.</param>
    	public void Init(List<Lzh_LoopItemData> datas, DelegateHandler onItemInitCallback)
    	{
    		datasList = datas;
    		this.OnItemInit = onItemInitCallback;
    
    		Validate();
    	}
    
    	/// <summary>
    	/// 构造一个 item 对象
    	/// </summary>
    	/// <returns>The item.</returns>
    	Lzh_LoopItemObject CreateItem()
    	{
    		GameObject go = NGUITools.AddChild(itemParent,itemPrefab);
            UIWidget widget = go.GetComponent<UIWidget>();
            Lzh_LoopItemObject item = new Lzh_LoopItemObject();
            item.widget = widget;
    		go.SetActive(true);
            return item;
    	}
    
    	/// <summary>
    	/// 用数据列表来初始化scrollview
    	/// </summary>
    	/// <param name="item">Item.</param>
    	/// <param name="indexData">Index data.</param>
    	/// <param name="data">Data.</param>
    	void InitItem(Lzh_LoopItemObject item, int dataIndex, Lzh_LoopItemData data)
    	{
    		item.dataIndex = dataIndex;
            if(OnItemInit!=null)
            {
                OnItemInit(item, data);
            }
    		item.widget.transform.localPosition = itemStartPos;
    	}
    
    	/// <summary>
    	/// 在itemsList前面补上一个item
    	/// </summary>
    	void AddToFront(Lzh_LoopItemObject priorItem, Lzh_LoopItemObject newItem, int newIndex, Lzh_LoopItemData newData)
    	{
    		InitItem (newItem, newIndex, newData);
    		// 计算新item的位置
    		if(scrollView.movement == UIScrollView.Movement.Vertical)
    		{
    			float offsetY = priorItem.widget.height*0.5f + gapDis + newItem.widget.height*0.5f;
    			if(arrangeDirection == ArrangeDirection.Down_to_Up) offsetY *=-1f;
    			newItem.widget.transform.localPosition = priorItem.widget.cachedTransform.localPosition + new Vector3(0f, offsetY, 0f);
    		}
    		else
    		{
    			float offsetX = priorItem.widget.width*0.5f + gapDis + newItem.widget.width*0.5f;
    			if(arrangeDirection == ArrangeDirection.Right_to_Left) offsetX *=-1f;
    			newItem.widget.transform.localPosition = priorItem.widget.cachedTransform.localPosition - new Vector3(offsetX, 0f, 0f);
    		}
    	}
    
    	/// <summary>
    	/// 在itemsList后面补上一个item
    	/// </summary>
    	void AddToBack(Lzh_LoopItemObject backItem, Lzh_LoopItemObject newItem, int newIndex, Lzh_LoopItemData newData)
    	{
    		InitItem (newItem, newIndex, newData);
    		// 计算新item的位置
    		if(scrollView.movement == UIScrollView.Movement.Vertical)
    		{
    			float offsetY = backItem.widget.height*0.5f + gapDis + newItem.widget.height*0.5f;
    			if(arrangeDirection == ArrangeDirection.Down_to_Up) offsetY *=-1f;
    			newItem.widget.transform.localPosition = backItem.widget.cachedTransform.localPosition - new Vector3(0f, offsetY, 0f);
    		}
    		else
    		{
    			float offsetX = backItem.widget.width*0.5f + gapDis + newItem.widget.width*0.5f;
    			if(arrangeDirection == ArrangeDirection.Right_to_Left) offsetX *=-1f;
    			newItem.widget.transform.localPosition = backItem.widget.cachedTransform.localPosition + new Vector3(offsetX, 0f, 0f);
    		}
    	}
    
    
    	#region 对象池性能相关
    	/// <summary>
    	/// 从对象池中取行一个item
    	/// </summary>
    	/// <returns>The item from loop.</returns>
    	Lzh_LoopItemObject GetItemFromLoop()
    	{
    		Lzh_LoopItemObject item;
    		if(itemLoop.Count<=0)
    		{
    			item = CreateItem();
    		}
    		else
    		{
    			item = itemLoop.Dequeue();
    		}
    		item.widget.gameObject.SetActive(true);
    		return item;
    	}
    	/// <summary>
    	/// 将要移除的item放入对象池中
    	/// --这个里我保证这个对象池中存在的对象不超过3个
    	/// </summary>
    	/// <param name="item">Item.</param>
    	void PutItemToLoop(Lzh_LoopItemObject item)
    	{
    		if(itemLoop.Count>=3)
    		{
    			Destroy(item.widget.gameObject);
    			return;
    		}
    		item.dataIndex = -1;
    		item.widget.gameObject.SetActive(false);
    		itemLoop.Enqueue(item);
    	}
    	#endregion
    
    }



    2、item对像的封装类:Lzh_LoopItemObject。不要求详细的item类来继承它,但我们要示详细的item对像一定要包括UIWidget组件。

    /*
     * 描术:
     * 
     * 作者:AnYuanLzh
     * 时间:2014-xx-xx
     */
    using UnityEngine;
    using System.Collections;
    
    /// <summary>
    /// item对像的封装类Lzh_LoopItemObject。不要求详细的item类来继承它。
    /// 但我们要示详细的item对像一定要包括UIWidget组件。

    /// </summary> [System.Serializable] public class Lzh_LoopItemObject { /// <summary> /// The widget. /// </summary> public UIWidget widget; /// <summary> /// 本item。在实际整个scrollview中的索引位置, /// 即对就数据。在数据列表中的索引 /// </summary> public int dataIndex= -1; }


    3、与item对关联的数据类:Lzh_LoopItemData。详细的item的数据类一定继承它

    /*
     * 描术:
     * 
     * 作者:AnYuanLzh
     * 时间:2014-xx-xx
     */
    using UnityEngine;
    using System.Collections;
    
    /// <summary>
    /// 与item对关联的数据类。详细的item的数据类一定继承它
    /// </summary>
    public class Lzh_LoopItemData
    {
    	// ***
    
    }



    4、上面三个是基本的类,凝视也比較具体。

    四、demoproject

    详细的使用请下载我的demoproject

    1、demo的执行效果图:

                  

    2、demo下载:这个包中一份project码和一个build好的可执行的exe。

       (注:其代码project中缺少NGUI插件。而要你们自行加上,这个demo我用的是ngui3.7.4。我认为相近的其他版ngui也是行的)


    2014-11-04 补:Lzh_LoopItemData 这个基类事实上是不能够不要的。还可降低复杂度。

    2015-01-27补:这仅仅一个非常easy的demo,仅仅为了表达一个主要的思路,并且简单了也方便交流与学习,可优化与扩展的空间是非常大的。


  • 相关阅读:
    【Eclipse】怎样把代码复制到word中并保持颜色
    windows下配置gvim
    这是给开发者的弥天大谎还是至理名言?
    Linux中常用软件安装(基于Ubuntu)
    MyEclipse 9.1优化技巧
    【数据库复习】函数依赖
    Windows下使用Flex入门
    【数据库复习】SQL
    浏览器中的“Linux”
    Unity开发原则
  • 原文地址:https://www.cnblogs.com/clnchanpin/p/7206506.html
Copyright © 2020-2023  润新知