UIWrapContent(NGUI长列表优化利器)

简介:

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)。

image

重叠?

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

image

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

image

名字被改了?

在运行的时候,如果是老版本的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,分两层结构

imageimage

 

组件源码

为了减少代码量,我对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,如需转载请自行联系原作者

相关文章
|
XML 编解码 开发工具
《移动互联网技术》第六章 资源管理: 掌握定制控件样式、界面主题、可绘制资源程序的编写方法
《移动互联网技术》第六章 资源管理: 掌握定制控件样式、界面主题、可绘制资源程序的编写方法
56 0
|
1月前
|
前端开发 数据处理 开发者
Flutter应用开发中滚动性能优化与无限列表实现的重要性
本文深入探讨了Flutter应用开发中滚动性能优化与无限列表实现的重要性。首先分析了影响滚动性能的因素,如布局复杂度、重绘频率和数据处理等。接着介绍了优化方法,包括懒加载、简化布局、控制重绘和高效数据处理。最后详细讲解了无限列表的实现原理及步骤,并通过案例分析展示了具体应用,旨在为开发者提供实用的技术指导。
41 5
|
4月前
|
C# UED 开发者
WPF与性能优化:掌握这些核心技巧,让你的应用从卡顿到丝滑,彻底告别延迟,实现响应速度质的飞跃——从布局到动画全面剖析与实例演示
【8月更文挑战第31天】本文通过对比优化前后的方法,详细探讨了提升WPF应用响应速度的策略。文章首先分析了常见的性能瓶颈,如复杂的XAML布局、耗时的事件处理、不当的数据绑定及繁重的动画效果。接着,通过具体示例展示了如何简化XAML结构、使用后台线程处理事件、调整数据绑定设置以及利用DirectX优化动画,从而有效提升应用性能。通过这些优化措施,WPF应用将更加流畅,用户体验也将得到显著改善。
313 1
|
4月前
|
JavaScript 前端开发 算法
【Vue秘籍揭秘】:掌握这一个技巧,让你的列表渲染速度飙升!——深度解析`key`属性如何成为性能优化的秘密武器
【8月更文挑战第20天】Vue.js是一款流行前端框架,通过简洁API和高效虚拟DOM更新机制简化响应式Web界面开发。其中,`key`属性在列表渲染中至关重要。本文从`key`基本概念出发,解析其实现原理及最佳实践。使用`key`帮助Vue更准确地识别列表变动,优化DOM更新过程,确保组件状态正确维护,提升应用性能。通过示例展示有无`key`的区别,强调合理使用`key`的重要性。
71 3
|
4月前
|
C# 开发者 数据处理
WPF开发者必备秘籍:深度解析数据网格最佳实践,轻松玩转数据展示与编辑大揭秘!
【8月更文挑战第31天】数据网格控件是WPF应用程序中展示和编辑数据的关键组件,提供排序、筛选等功能,显著提升用户体验。本文探讨WPF中数据网格的最佳实践,通过DevExpress DataGrid示例介绍其集成方法,包括添加引用、定义数据模型及XAML配置。通过遵循数据绑定、性能优化、自定义列等最佳实践,可大幅提升数据处理效率和用户体验。
70 0
|
4月前
|
开发框架 API 开发者
Flutter表单控件深度解析:从基本构建到高级自定义,全方位打造既美观又实用的移动端数据输入体验,让应用交互更上一层楼
【8月更文挑战第31天】在构建美观且功能强大的移动应用时,表单是不可或缺的部分。Flutter 作为热门的跨平台开发框架,提供了丰富的表单控件和 API,使开发者能轻松创建高质量表单。本文通过问题解答形式,深入解读 Flutter 表单控件,并通过具体示例代码展示如何构建优秀的移动应用表单。涵盖创建基本表单、处理表单提交、自定义控件样式、焦点管理和异步验证等内容,适合各水平开发者学习和参考。
111 0
|
4月前
|
C# 开发者 Windows
震撼发布:全面解析WPF中的打印功能——从基础设置到高级定制,带你一步步实现直接打印文档的完整流程,让你的WPF应用程序瞬间升级,掌握这一技能,轻松应对各种打印需求,彻底告别打印难题!
【8月更文挑战第31天】打印功能在许多WPF应用中不可或缺,尤其在需要生成纸质文档时。WPF提供了强大的打印支持,通过`PrintDialog`等类简化了打印集成。本文将详细介绍如何在WPF应用中实现直接打印文档的功能,并通过具体示例代码展示其实现过程。
408 0
|
6月前
|
编解码 前端开发 JavaScript
带您一步步构建一个基本的动态新闻网站,包括页面布局、样式设计以及交互效果的实现
【6月更文挑战第14天】构建动态新闻网站实战项目,涉及页面布局、样式设计和交互实现。首页采用顶部导航栏、轮播图和新闻列表布局;新闻列表页按分类显示新闻,详情页展示完整内容并可添加相关推荐和评论。设计注重色彩搭配、字体选择和布局间距,实现轮播图效果、导航栏交互和响应式设计,提升用户体验。该项目有助于锻炼HTML和CSS技能,理解网页设计实际应用。
206 1
|
Web App开发 存储 缓存
我是如何优化弹窗拖拽卡顿的?内附排查和优化过程
我是如何优化弹窗拖拽卡顿的?内附排查和优化过程
248 0
|
API Android开发
Material Design 实战 之 第六弹 —— 可折叠式标题栏(CollapsingToolbarLayout) & 系统差异型的功能实现(充分利用系统状态栏空间)
Material Design 实战 之 第六弹 —— 可折叠式标题栏(CollapsingToolbarLayout) & 系统差异型的功能实现(充分利用系统状态栏空间)