一、当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;
///
/// 这个类主要做了一件事,就是优化了,NGUI UIScrollView 在数据量很多都时候,
/// 创建过多都GameObject对象,造成资源浪费.
///
public class Lzh_LoopScrollView : MonoBehaviour
{
public enum ArrangeDirection
{
Left_to_Right,
Right_to_Left,
Up_to_Down,
Down_to_Up,
}
///
/// items的排列方式
///
public ArrangeDirection arrangeDirection = ArrangeDirection.Up_to_Down;
///
/// 列表单项模板
///
public GameObject itemPrefab;
///
/// The items list.
///
public List itemsList;
///
/// The datas list.
///
public List datasList;
///
/// 列表脚本
///
public UIScrollView scrollView;
public GameObject itemParent;
///
/// itemsList的第一个元素
///
Lzh_LoopItemObject firstItem;
///
/// itemsList的最后一个元素
///
Lzh_LoopItemObject lastItem;
public delegate void DelegateHandler(Lzh_LoopItemObject item, Lzh_LoopItemData data);
///
/// 响应
///
public DelegateHandler OnItemInit;
///
/// 第一item的起始位置
///
public Vector3 itemStartPos = Vector3.zero;
///
/// 菜单项间隙
///
public float gapDis = 0f;
// 对象池
// 再次优化,频繁的创建与销毁
Queue itemLoop = new Queue();
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();
}
}
///
/// 检验items的两端是否要补上或删除
///
void Validate()
{
if( datasList==null || datasList.Count==0)
{
return;
}
// 如果itemsList还不存在
if(itemsList==null || itemsList.Count==0)
{
itemsList = new List();
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();
}
}
}
///
/// Init the specified datas.
///
/// Datas.
public void Init(List datas, DelegateHandler onItemInitCallback)
{
datasList = datas;
this.OnItemInit = onItemInitCallback;
Validate();
}
///
/// 构造一个 item 对象
///
/// The item.
Lzh_LoopItemObject CreateItem()
{
GameObject go = NGUITools.AddChild(itemParent,itemPrefab);
UIWidget widget = go.GetComponent();
Lzh_LoopItemObject item = new Lzh_LoopItemObject();
item.widget = widget;
go.SetActive(true);
return item;
}
///
/// 用数据列表来初始化scrollview
///
/// Item.
/// Index data.
/// Data.
void InitItem(Lzh_LoopItemObject item, int dataIndex, Lzh_LoopItemData data)
{
item.dataIndex = dataIndex;
if(OnItemInit!=null)
{
OnItemInit(item, data);
}
item.widget.transform.localPosition = itemStartPos;
}
///
/// 在itemsList前面补上一个item
///
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);
}
}
///
/// 在itemsList后面补上一个item
///
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 对象池性能相关
///
/// 从对象池中取行一个item
///
/// The item from loop.
Lzh_LoopItemObject GetItemFromLoop()
{
Lzh_LoopItemObject item;
if(itemLoop.Count<=0)
{
item = CreateItem();
}
else
{
item = itemLoop.Dequeue();
}
item.widget.gameObject.SetActive(true);
return item;
}
///
/// 将要移除的item放入对象池中
/// --这个里我保证这个对象池中存在的对象不超过3个
///
/// Item.
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;
///
/// item对像的封装类Lzh_LoopItemObject,不要求具体的item类来继承它。
/// 但我们要示具体的item对像一定要包含UIWidget组件。
///
[System.Serializable]
public class Lzh_LoopItemObject
{
///
/// The widget.
///
public UIWidget widget;
///
/// 本item,在实际整个scrollview中的索引位置,
/// 即对就数据,在数据列表中的索引
///
public int dataIndex= -1;
}
3、与item对关联的数据类:Lzh_LoopItemData,具体的item的数据类一定继承它
/*
* 描术:
*
* 作者:AnYuanLzh
* 时间:2014-xx-xx
*/
using UnityEngine;
using System.Collections;
///
/// 与item对关联的数据类,具体的item的数据类一定继承它
///
public class Lzh_LoopItemData
{
// ***
}
四、demo工程
具体的使用请下载我的demo工程
1、demo的运行效果图:
2、demo下载:这个包中一份工程码和一个build好的可运行的exe。
(注:其代码工程中缺少NGUI插件,而要你们自行加上,这个demo我用的是ngui3.7.4。我觉得相近的其它版ngui也是行的)
2014-11-04 补:Lzh_LoopItemData 这个基类其实是不可以不要的,还可减少复杂度。
2015-01-27补:这只一个很简单的demo,只为了表达一个基本的思路,而且简单了也方便交流与学习,可优化与扩展的空间是很大的。