NGUI长列表优化利器
优化原理
NGUI3.7.x以上版本 有个新组件 UIWrapContent ,当我们的列表内容很多时,可以进行优化。它不是一次生成全部的child,而是只有固定数量的child,在滑动时更新child的内容。
当前NGUI3.6.X也有此组件,不过不完善,比如更新每一条渲染未实现,protected virtual void UpdateItem (Transform item, int index) ,还有未提供便捷的接口供外部调用。
NGUI3.7.x以上版本 有个新组件 UIWrapContent ,当我们的列表内容很多时,可以进行优化。它不是一次生成全部的child,而是只有固定数量的child,在滑动时更新child的内容。
当前NGUI3.6.X也有此组件,不过不完善,比如更新每一条渲染未实现,protected virtual void UpdateItem (Transform item, int index) ,还有未提供便捷的接口供外部调用。
UIWrapContent详解
无需循环滚动
重叠?
名字被改了?
在运行的时候,如果是老版本的NGUI,那么很不幸的是,Item的名字会被修改,这个某些情况下还是有影响的。
如果你不想名字被修改,打开 NGUI\Scripts\Interaction\UIWrapContent.cs,在 WrapContent 方法,查找 t.name = realIndex.ToString(); 并删除。(在ngui3.7.3中一共用四行,全删除)
在运行的时候,如果是老版本的NGUI,那么很不幸的是,Item的名字会被修改,这个某些情况下还是有影响的。
如果你不想名字被修改,打开 NGUI\Scripts\Interaction\UIWrapContent.cs,在 WrapContent 方法,查找 t.name = realIndex.ToString(); 并删除。(在ngui3.7.3中一共用四行,全删除)
重要方法
public delegate void OnInitializeItem (GameObject go, int wrapIndex, int realIndex);
执行渲染的委托,DoRender(要渲染的对象,索引[0开始]) 真正开始渲染
private void OnInitItem(GameObject go, int wrapindex, int realindex)
{
var index = Mathf.Abs(realindex);// 取绝对值
CacheObject2Index[go] = index;
if (CheckActive(go, index) && _hasRefresh)
{
DoRender(go, index);
}
}
在滚动时调用,更新当前滚动未尾的Item
protected virtual void UpdateItem(Transform item, int index)
{
if (onInitializeItem != null)
{
int realIndex = (mScroll.movement == UIScrollView.Movement.Vertical) ?
Mathf.RoundToInt(item.localPosition.y / itemSize) :
Mathf.RoundToInt(item.localPosition.x / itemSize);
onInitializeItem(item.gameObject, index, realIndex);
}
}
public delegate void OnInitializeItem (GameObject go, int wrapIndex, int realIndex);
执行渲染的委托,DoRender(要渲染的对象,索引[0开始]) 真正开始渲染
private void OnInitItem(GameObject go, int wrapindex, int realindex) { var index = Mathf.Abs(realindex);// 取绝对值 CacheObject2Index[go] = index; if (CheckActive(go, index) && _hasRefresh) { DoRender(go, index); } }
在滚动时调用,更新当前滚动未尾的Item
protected virtual void UpdateItem(Transform item, int index) { if (onInitializeItem != null) { int realIndex = (mScroll.movement == UIScrollView.Movement.Vertical) ? Mathf.RoundToInt(item.localPosition.y / itemSize) : Mathf.RoundToInt(item.localPosition.x / itemSize); onInitializeItem(item.gameObject, index, realIndex); } }
UIWrapContent封装
UI结构
组件源码
为了减少代码量,我对UIWrapContent进行了一层封装,代码如下:
需要NGUI3.7.x之后的版本
update log
2015-10-25 增加可以设置顺序(从左到右,从上到下)
2016-05-28
改掉foreach,减少GC,修改error
已知bug:invertOrder=true时,有莫名的表现,日后修复。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 对NGUI的 UIWrapContent的封装,如果低于NGUI3.7.x,请使用高版本的UIWrapContent替换
/// 目录结构:(NGUI3.9.7)
/// -GameObject绑定 UIPanel,UIScrollView
/// -GameObject绑定 UIWrapContent,[UIGrid]
/// -Item (具体的滑动内容)
/// 使用方法:var WrapContentHelper = UIWrapContentHelper.Create(WrapContent);
/// by 赵青青
/// </summary>
public class UIWrapContentHelper
{
public delegate void UIWrapContentRenderDelegate(GameObject obj, int index);
/// <summary>
///obj:要渲染的对象; index:索引,从0开始
/// </summary>
public UIWrapContentRenderDelegate OnRenderEvent;
private int _count;//总数
private bool _hasRefresh = false;//是否已刷新
private UIWrapContent _wrapContent;
private UIPanel _panel;
private UIScrollView _scrollView;
private Vector2 _initPanelClipOffset;
private Vector3 _initPanelLocalPos;
/// <summary>
/// 缓存起来上次渲染的对象对应索引
/// </summary>
private Dictionary<GameObject, int> CacheObject2Index = new Dictionary<GameObject, int>();
private UIWrapContentHelper(){}
private UIWrapContentHelper(UIWrapContent uiWrapContent)
{
if (uiWrapContent == null)
{
Debug.LogError("UIWrapContentHelper 传入了NULL");
return;
}
_wrapContent = uiWrapContent;
//_wrapContent.hideInactive = false;
_wrapContent.onInitializeItem = OnInitItem; //NOTE NGUI 3.7.x以上版本才有此功能
//NOTE UIPanel 建议挂在UIWrapContent的父级,NGUI3.9.7非父级我这儿出现异怪现象
_panel = _wrapContent.gameObject.GetComponent<UIPanel>();
var panelParent = _wrapContent.transform.parent;
if (_panel == null && panelParent != null)
{
_panel = panelParent.GetComponent<UIPanel>();
}
if (_panel == null)
{
Debug.LogError(uiWrapContent.name + "的父节点没有UIPanel");
return;
}
_scrollView = _panel.GetComponent<UIScrollView>();
_initPanelClipOffset = _panel.clipOffset;
_initPanelLocalPos = _panel.cachedTransform.localPosition;
}
//初始化数据,Init或Open时调用
public void ResetScroll()
{
if (_panel == null || _wrapContent == null || _scrollView == null)
{
Debug.LogWarning("panel or wrapContent , scrollView is null ");
return;
}
_panel.clipOffset = _initPanelClipOffset;
_panel.cachedTransform.localPosition = _initPanelLocalPos;
// 重设组件~索引和位置
var index = 0;
foreach (var oChildTransform in _wrapContent.transform)
{
var childTransform = (Transform)oChildTransform;
// NOTE: 横方向未测试
if (_scrollView.movement == UIScrollView.Movement.Vertical)
{
childTransform.SetLocalPositionY(-_wrapContent.itemSize * index);
}
else if (_scrollView.movement == UIScrollView.Movement.Horizontal)
{
childTransform.SetLocalPositionX(-_wrapContent.itemSize * index);
}
CacheObject2Index[childTransform.gameObject] = index;
index++;
}
//fix soft clip panel
if (_panel.clipping == UIDrawCall.Clipping.SoftClip) _panel.SetDirty();
}
/// <summary>
/// 设置多少项
/// </summary>
/// <param name="count"></param>
/// <param name="invertOrder">是否反转</param>
private void SetCount(int count, bool invertOrder = false)
{
if (_panel == null || _wrapContent == null)
{
Debug.LogWarning("panel or wrapContent is null ");
return;
}
_count = count;
//TODO: invertOrder有bug ,NGUI 3.7.x有此功能
//if (invertOrder)
//{
// _wrapContent.minIndex = 0;
// _wrapContent.maxIndex = count - 1;
//}
//else
{
_wrapContent.minIndex = -count + 1;
_wrapContent.maxIndex = 0;
}
//fix: 按字母排序有bug:显示错乱
//_wrapContent.SortAlphabetically();
if (_scrollView != null)
{
var canDrag = _count >= GetActiveChilds(_wrapContent.transform).Count;
if (count == 1) canDrag = false;
_scrollView.restrictWithinPanel = canDrag;
_scrollView.disableDragIfFits = !canDrag; // 只有一个的时候,不能滑动
}
}
private void OnInitItem(GameObject go, int wrapindex, int realindex)
{
var index = Mathf.Abs(realindex);// 取绝对值
CacheObject2Index[go] = index;
if (CheckActive(go, index) && _hasRefresh)
{
DoRender(go, index);
}
}
/// <summary>
/// 检查是否应该隐藏起来
/// </summary>
private bool CheckActive(GameObject go, int index)
{
bool needActive = index <= (_count - 1);//小于总数才显示
go.SetActive(needActive);
return needActive;
}
//触发渲染事件
private void DoRender(GameObject go, int index)
{
if (OnRenderEvent == null)
{
Debug.LogError("UIWrapContent必须设置RenderFunc!");
return;
}
OnRenderEvent(go, index);
}
/// <summary>
/// 执行刷新,单个单个地渲染
/// </summary>
/// <param name="count"></param>
/// <param name="invertOrder">反转:当有Scrollbar时才设置此值。指scrollbar的拖动方向,反转有bug,需完善</param>
public void Refresh(int count, bool invertOrder = false)
{
SetCount(count, invertOrder);
//fix:使用GetEnumerator 替代foreach,减少GC
var enumerator = CacheObject2Index.GetEnumerator();
while (enumerator.MoveNext())
{
if (CheckActive(enumerator.Current.Key, enumerator.Current.Value))
{
DoRender(enumerator.Current.Key, enumerator.Current.Value);
}
}
_hasRefresh = true;
}
//强制设置scrollview是否可以滑动,
//fix 前面在SetCount中有设此值,但判断依据不一定
public void CanDragScrollview(bool canDrag)
{
if (_scrollView != null)
{
_scrollView.restrictWithinPanel = canDrag;
_scrollView.disableDragIfFits = !canDrag; // 只有一个的时候,不能滑动
}
}
public static UIWrapContentHelper Create(UIWrapContent uiWrapContent)
{
return new UIWrapContentHelper(uiWrapContent);
}
// 获取一个Transfrom下所有active=true的child
public static List<GameObject> GetActiveChilds(Transform parent)
{
var list = new List<GameObject>();
if (parent == null) return list;
var max = parent.childCount;
for (int idx = 0; idx < max; idx++)
{
var childObj = parent.GetChild(idx).gameObject;
if (childObj.activeInHierarchy) list.Add(childObj);
}
return list;
}
}
为了减少代码量,我对UIWrapContent进行了一层封装,代码如下:
需要NGUI3.7.x之后的版本
update log
2015-10-25 增加可以设置顺序(从左到右,从上到下)
2016-05-28
改掉foreach,减少GC,修改error
已知bug:invertOrder=true时,有莫名的表现,日后修复。
using UnityEngine; using System.Collections; using System.Collections.Generic; /// <summary> /// 对NGUI的 UIWrapContent的封装,如果低于NGUI3.7.x,请使用高版本的UIWrapContent替换 /// 目录结构:(NGUI3.9.7) /// -GameObject绑定 UIPanel,UIScrollView /// -GameObject绑定 UIWrapContent,[UIGrid] /// -Item (具体的滑动内容) /// 使用方法:var WrapContentHelper = UIWrapContentHelper.Create(WrapContent); /// by 赵青青 /// </summary> public class UIWrapContentHelper { public delegate void UIWrapContentRenderDelegate(GameObject obj, int index); /// <summary> ///obj:要渲染的对象; index:索引,从0开始 /// </summary> public UIWrapContentRenderDelegate OnRenderEvent; private int _count;//总数 private bool _hasRefresh = false;//是否已刷新 private UIWrapContent _wrapContent; private UIPanel _panel; private UIScrollView _scrollView; private Vector2 _initPanelClipOffset; private Vector3 _initPanelLocalPos; /// <summary> /// 缓存起来上次渲染的对象对应索引 /// </summary> private Dictionary<GameObject, int> CacheObject2Index = new Dictionary<GameObject, int>(); private UIWrapContentHelper(){} private UIWrapContentHelper(UIWrapContent uiWrapContent) { if (uiWrapContent == null) { Debug.LogError("UIWrapContentHelper 传入了NULL"); return; } _wrapContent = uiWrapContent; //_wrapContent.hideInactive = false; _wrapContent.onInitializeItem = OnInitItem; //NOTE NGUI 3.7.x以上版本才有此功能 //NOTE UIPanel 建议挂在UIWrapContent的父级,NGUI3.9.7非父级我这儿出现异怪现象 _panel = _wrapContent.gameObject.GetComponent<UIPanel>(); var panelParent = _wrapContent.transform.parent; if (_panel == null && panelParent != null) { _panel = panelParent.GetComponent<UIPanel>(); } if (_panel == null) { Debug.LogError(uiWrapContent.name + "的父节点没有UIPanel"); return; } _scrollView = _panel.GetComponent<UIScrollView>(); _initPanelClipOffset = _panel.clipOffset; _initPanelLocalPos = _panel.cachedTransform.localPosition; } //初始化数据,Init或Open时调用 public void ResetScroll() { if (_panel == null || _wrapContent == null || _scrollView == null) { Debug.LogWarning("panel or wrapContent , scrollView is null "); return; } _panel.clipOffset = _initPanelClipOffset; _panel.cachedTransform.localPosition = _initPanelLocalPos; // 重设组件~索引和位置 var index = 0; foreach (var oChildTransform in _wrapContent.transform) { var childTransform = (Transform)oChildTransform; // NOTE: 横方向未测试 if (_scrollView.movement == UIScrollView.Movement.Vertical) { childTransform.SetLocalPositionY(-_wrapContent.itemSize * index); } else if (_scrollView.movement == UIScrollView.Movement.Horizontal) { childTransform.SetLocalPositionX(-_wrapContent.itemSize * index); } CacheObject2Index[childTransform.gameObject] = index; index++; } //fix soft clip panel if (_panel.clipping == UIDrawCall.Clipping.SoftClip) _panel.SetDirty(); } /// <summary> /// 设置多少项 /// </summary> /// <param name="count"></param> /// <param name="invertOrder">是否反转</param> private void SetCount(int count, bool invertOrder = false) { if (_panel == null || _wrapContent == null) { Debug.LogWarning("panel or wrapContent is null "); return; } _count = count; //TODO: invertOrder有bug ,NGUI 3.7.x有此功能 //if (invertOrder) //{ // _wrapContent.minIndex = 0; // _wrapContent.maxIndex = count - 1; //} //else { _wrapContent.minIndex = -count + 1; _wrapContent.maxIndex = 0; } //fix: 按字母排序有bug:显示错乱 //_wrapContent.SortAlphabetically(); if (_scrollView != null) { var canDrag = _count >= GetActiveChilds(_wrapContent.transform).Count; if (count == 1) canDrag = false; _scrollView.restrictWithinPanel = canDrag; _scrollView.disableDragIfFits = !canDrag; // 只有一个的时候,不能滑动 } } private void OnInitItem(GameObject go, int wrapindex, int realindex) { var index = Mathf.Abs(realindex);// 取绝对值 CacheObject2Index[go] = index; if (CheckActive(go, index) && _hasRefresh) { DoRender(go, index); } } /// <summary> /// 检查是否应该隐藏起来 /// </summary> private bool CheckActive(GameObject go, int index) { bool needActive = index <= (_count - 1);//小于总数才显示 go.SetActive(needActive); return needActive; } //触发渲染事件 private void DoRender(GameObject go, int index) { if (OnRenderEvent == null) { Debug.LogError("UIWrapContent必须设置RenderFunc!"); return; } OnRenderEvent(go, index); } /// <summary> /// 执行刷新,单个单个地渲染 /// </summary> /// <param name="count"></param> /// <param name="invertOrder">反转:当有Scrollbar时才设置此值。指scrollbar的拖动方向,反转有bug,需完善</param> public void Refresh(int count, bool invertOrder = false) { SetCount(count, invertOrder); //fix:使用GetEnumerator 替代foreach,减少GC var enumerator = CacheObject2Index.GetEnumerator(); while (enumerator.MoveNext()) { if (CheckActive(enumerator.Current.Key, enumerator.Current.Value)) { DoRender(enumerator.Current.Key, enumerator.Current.Value); } } _hasRefresh = true; } //强制设置scrollview是否可以滑动, //fix 前面在SetCount中有设此值,但判断依据不一定 public void CanDragScrollview(bool canDrag) { if (_scrollView != null) { _scrollView.restrictWithinPanel = canDrag; _scrollView.disableDragIfFits = !canDrag; // 只有一个的时候,不能滑动 } } public static UIWrapContentHelper Create(UIWrapContent uiWrapContent) { return new UIWrapContentHelper(uiWrapContent); } // 获取一个Transfrom下所有active=true的child public static List<GameObject> GetActiveChilds(Transform parent) { var list = new List<GameObject>(); if (parent == null) return list; var max = parent.childCount; for (int idx = 0; idx < max; idx++) { var childObj = parent.GetChild(idx).gameObject; if (childObj.activeInHierarchy) list.Add(childObj); } return list; } }
组件使用
如果需要每次打开UI时,复位UIScrollView到初始状态,请调用 WrapContentHelper.ResetScroll();
OnRenderWrapContent 是具体的渲染逻辑
using System;
using System.Collections.Generic;
using Umeng;
using UnityEngine;
using System.Collections;
public class CUIFriendList : CUINavController
{
private UIWrapContent WrapContent;
private CUIWrapContentHelper WrapContentHelper;
private List<CPartnerVo> CachePartnerVoList;//显示的数据
//初始化
public override void OnInit()
{
base.OnInit();
WrapContent = GetControl<UIWrapContent>("ListPanel");
WrapContentHelper = CUIWrapContentHelper.Create(WrapContent);
WrapContentHelper.RenderFunc = OnRenderWrapContent;
}
//界面打开前播放动画
public override void BeforeShowTween(object[] onOpenArgs, System.Action doNext)
{
//NOTE 重设Scrollview的位置
WrapContentHelper.ResetScroll();
RefreshUI();
//其它的业务逻辑
base.BeforeShowTween(onOpenArgs, doNext);
}
//刷新列表
private void RefreshUI()
{
var max = CachePartnerVoList.Count;//要渲染数据
WrapContentHelper.Refresh(max);
}
//具体的渲染逻辑
private void OnRenderWrapContent(GameObject gameObj, int idx)
{
if (idx >= CachePartnerVoList.Count)
{
gameObj.SetActive(false);
Debug.LogWarning("超出索引");
return;
}
var partnerVo = CachePartnerVoList[idx];
var trans = gameObj.transform;
//TODO 执行具体的渲染逻辑
//eg
if(trans == null) return;
var NameLabel_=trans.FindChild("NameLabel");
if(NameLabel_)
{
NameLabel_.GetComponent<UILabel>().text=partnerVo.Name;
}
//.......
}
}
本文转自赵青青博客园博客,原文链接:http://www.cnblogs.com/zhaoqingqing/p/4901393.html,如需转载请自行联系原作者
如果需要每次打开UI时,复位UIScrollView到初始状态,请调用 WrapContentHelper.ResetScroll();
OnRenderWrapContent 是具体的渲染逻辑
using System; using System.Collections.Generic; using Umeng; using UnityEngine; using System.Collections; public class CUIFriendList : CUINavController { private UIWrapContent WrapContent; private CUIWrapContentHelper WrapContentHelper; private List<CPartnerVo> CachePartnerVoList;//显示的数据 //初始化 public override void OnInit() { base.OnInit(); WrapContent = GetControl<UIWrapContent>("ListPanel"); WrapContentHelper = CUIWrapContentHelper.Create(WrapContent); WrapContentHelper.RenderFunc = OnRenderWrapContent; } //界面打开前播放动画 public override void BeforeShowTween(object[] onOpenArgs, System.Action doNext) { //NOTE 重设Scrollview的位置 WrapContentHelper.ResetScroll(); RefreshUI(); //其它的业务逻辑 base.BeforeShowTween(onOpenArgs, doNext); } //刷新列表 private void RefreshUI() { var max = CachePartnerVoList.Count;//要渲染数据 WrapContentHelper.Refresh(max); } //具体的渲染逻辑 private void OnRenderWrapContent(GameObject gameObj, int idx) { if (idx >= CachePartnerVoList.Count) { gameObj.SetActive(false); Debug.LogWarning("超出索引"); return; } var partnerVo = CachePartnerVoList[idx]; var trans = gameObj.transform; //TODO 执行具体的渲染逻辑 //eg if(trans == null) return; var NameLabel_=trans.FindChild("NameLabel"); if(NameLabel_) { NameLabel_.GetComponent<UILabel>().text=partnerVo.Name; } //....... } }
本文转自赵青青博客园博客,原文链接:http://www.cnblogs.com/zhaoqingqing/p/4901393.html,如需转载请自行联系原作者