NGUI UIScrollView - 大量item子项的性能优化

一、当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、上面三个是主要的类,注释也比较详细。

四、demo工程

具体的使用请下载我的demo工程

1、demo的运行效果图:

              

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

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


2014-11-04 补:Lzh_LoopItemData 这个基类其实是不可以不要的,还可减少复杂度。

2015-01-27补:这只一个很简单的demo,只为了表达一个基本的思路,而且简单了也方便交流与学习,可优化与扩展的空间是很大的。


  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿海-程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值