NGUI长列表优化利器

优化原理

NGUI3.7.x以上版本 有个新组件 UIWrapContent ,当我们的列表内容很多时,可以进行优化。它不是一次生成全部的child,而是只有固定数量的child,在滑动时更新child的内容。

当前NGUI3.6.X也有此组件,不过不完善,比如更新每一条渲染未实现,protected virtual void UpdateItem (Transform item, int index) ,还有未提供便捷的接口供外部调用。

UIWrapContent详解

无需循环滚动

如果你需要无限滚动,那么请设置Range Limit,这个范围是在:-最大数量+1 ~ 0。至于前面的负号,你可以去看看它的实现原理。比如你共显示20条数据,那么范围就是-20+1~0(-19~0)。

重叠?

如果你的内容之间会出现如下所示的重叠现象,那是Item Height的值过小

这个Item Height表示每两个Item之间间隔,这儿不是每个item的高度,所以请设置成和UIGrid的Height一样的值,当然如果你是水平滑动,就请和Cell Width一样。如果没有UIGrid,那么就设置比item的高度大一些。

名字被改了?

在运行的时候,如果是老版本的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);
}
}

UIWrapContent封装

UI结构

使用此Help,你的UI结构可以是以下任意一种(注:如果是NGUI3.9.x建使用结构二)

下图左UI结构: ListPanel上绑定了UIPanel、UIScrollView,UIWrapContent、UIGrid,即只有一层结构

下图右结构:Scrollview上绑定了UIPanel,UIScrollview,WrapContent上绑定了UIGrid和UIWrapContent,分两层结构

组件源码

为了减少代码量,我对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 = ;
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 + ;
_wrapContent.maxIndex = ;
}
//fix: 按字母排序有bug:显示错乱
//_wrapContent.SortAlphabetically(); if (_scrollView != null)
{
var canDrag = _count >= GetActiveChilds(_wrapContent.transform).Count;
if (count == ) 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 - );//小于总数才显示
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 = ; 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;
}
//.......
}
}
												

最新文章

  1. 通过Jexus 部署 dotnetcore版本MusicStore 示例程序
  2. node.js基础 1之 HTTP流程实例
  3. javase基础复习攻略《五》
  4. js函数、变量提升(hoisting)
  5. iPhone中修改iMessage关联手机号码的终极方法
  6. JavaScript中判断为整数的多种方式
  7. 关于easyUI在子页面增加显示tabs的一个问题
  8. Oracle数据库多语言文字存储解决方案
  9. 用PHP判断客户端是否是手机
  10. Deep Learning and the Triumph of Empiricism
  11. UVALive 6910 Cutting Tree(并查集应用)
  12. sed-加速你在Linux的文件编辑
  13. 【BZOJ 3626】 [LNOI2014]LCA【在线+主席树+树剖】
  14. vue模式
  15. WebViewJavascriptBridge的使用说明
  16. 关于flask自带web应用服务器Werkzeug 使用requests请求时出现的错误。
  17. ftp不能登录报错
  18. 运动目标检测中基于HSV空间的阴影去除算法
  19. 树莓派进阶之路 (021) - 3.2inch RPi LCD (B)
  20. Nexus3 仓库搭建(基于Docker)

热门文章

  1. [实现]Javascript代码的另一种压缩与加密方法——代码图片转换
  2. JavaScript 数据类型判断
  3. CalloutManager
  4. Sharepoint学习笔记—习题系列--70-576习题解析 -(Q16-Q18)
  5. 解决windows防火墙无法启动的问题
  6. Java中的经典算法之选择排序(SelectionSort)
  7. 【代码笔记】iOS-3DES+Base64加密解密
  8. Android实用代码七段(五)
  9. iOS-点击推送消息跳转处理
  10. __block和__weak的区别