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;
}
//.......
}
}
												

UIWrapContent(NGUI长列表优化利器)的更多相关文章

  1. 记一次vue长列表的内存性能分析和优化

    好久没写东西,博客又长草了,这段时间身心放松了好久,都没什么主题可以写了 上周接到一个需求,优化vue的一个长列表页面,忙活了很久也到尾声了,内存使用和卡顿都做了一点点优化,还算有点收获 写的有点啰嗦 ...

  2. 在NGUI中高效优化UIScrollView之UIWrapContent的简介以及使用

    前言: 1.我使用的NGUI版本为 v3.7.5,不知道老版的NGUI是否有UIWrapContent 这个脚本. 2.本文讲解主要以图片显示的例子为主,本文例子UIScrollView是水平方向,一 ...

  3. react-native中使用长列表

    React Native 提供了几个适用于展示长列表数据的组件,一般而言我们会选用FlatList或是SectionList. FlatList组件用于显示一个垂直的滚动列表,其中的元素之间结构近似而 ...

  4. 【转】PHP中被忽略的性能优化利器:生成器.md

      PHP  如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生.但很多PHP开发者或许都不知道生成器这个功能,可能是因为生成器是PHP 5.5.0才引入的功能,也可以是生成器作用不是很明 ...

  5. React解决长列表方案(react-virtualized)

    github地址 高效渲染大型列表的响应式组件 使用窗口特性,即在一个滚动的范围内,呈现你给定数据的一小部分,大量缩减了呈现组件所需的时间,以及创建DOM节点的数量. 缺点:滑动过快,可能会出现空白的 ...

  6. 网络编程Netty IoT百万长连接优化

    目录 IoT推送系统 IoT是什么 IoT推送系统的设计 心跳检测机制 简述心跳检测 心跳检测机制代码示例 百万长连接优化 连接优化代码示例 TCP连接四元组 配置优化 IoT推送系统 IoT是什么 ...

  7. 开源|性能优化利器:数据库审核平台Themis的选型与实践

    作者:韩锋 出处:DBAplus社群分享:来源:宜信技术学院 Themis开源地址:https://github.com/CreditEaseDBA 一.面临的挑战 1.运维规模及种类 我相信,这也是 ...

  8. Unity NGUI C#性能优化

    建议读者先看这篇博文:http://blog.csdn.net/zzxiang1985/article/details/43339273,有些技术已经变了,比如第1招,unity5的打包机制已经变许多 ...

  9. NGUI Draw Calls优化(思路)

    用NGUI做界面的时候发现不注意GameObject(或者说Widget)的depth的话,单独运行界面时,Draw Calls挺高的: 网上搜了一下,大把的博客说的都是类似以下的原则: (PS:以下 ...

随机推荐

  1. SharePoint 2013 在母版页中插入WebPart

    最近QQ群里有朋友问,如何在母版页里插入自己开发的WebPart.其实很简单,母版页中虽然不允许插入WebPartZone,但是Designer就可以插入WebPart:或者手动注册,然后插入WebP ...

  2. 客户端调用服务端webservice的端口问题

    今天有一个同事过来问:他有一个程序在A服务器上调第三方B服务器短信发送服务接口(webservice),无论是否发送成功,服务接口都会返回状态.现在客户要做每一个服务器 做入站端口管控,一切不必要的端 ...

  3. tomcat下运行多个项目

    1. tomact下修改端口解决端口冲突 http://jingyan.baidu.com/article/9f63fb91d0f1b8c8400f0e1d.html 打开 servers下面的ser ...

  4. VMware Data Recovery备份恢复vmware虚拟机

    VMware Data Recovery 是VMware虚拟机备份工具,可创建虚拟机备份,同时不会中断虚拟机的使用或虚拟机提供的数据和服务.Data Recovery 管理现有备份,并在这些备份过时后 ...

  5. Java继承中的转型及其内存分配

    看书的时候被一段代码能凌乱啦,代码是这样的: package 继承; abstract class People { public String tag = "疯狂Java讲义"; ...

  6. JavaScript上传图片及时预览

    /*******************************正面图片上传预览开始****************************/        function previewImage ...

  7. 【C语言】C语言标识符

    目录: [定义]  [作用]  [命名规则]  [命名规范] 1.定义  标识符就是我们给函数或变量定义的名称.方便查阅增强可读性.减少沟通成本. 2.作用  · 增强可读性.  · 减少沟通成本. ...

  8. TCP连接状态与2MSL等待时间

    1 连接状态图 2 建立连接:三次握手,不使用DNS和使用DNS 3 关闭连接-四次握手 连接双方任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了.如果一方调用shutdown ...

  9. Swift开发第十篇——可变参数函数&初始化方法顺序

    本篇分为两部分: 一.Swift中的可变参数函数 二.初始化方法的顺序 一.Swift中的可变参数函数 可变参数函数指的是可以接受任意多个参数的函数,在 OC 中,拼接字符串的函数就属于可变参数函数 ...

  10. php设计模式 观察者模式

    观察者模式的核心是把客户元素(观察者)从一个中心类(主体)中分离开来.当主体知道事件发生时,观察者需要被通知到.同时,我们并不希望将主体与观察者之间的关系进行硬编码.为了达到这个目的,我们可以允许观察 ...