• 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的名字会被修改,这个某些情况下还是有影响的。

    如果你不想名字被修改,打开 NGUIScriptsInteractionUIWrapContent.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;
            }
            //.......
        }
    }
    
    
  • 相关阅读:
    如何将 Web 框架迁移到 Serverless
    Serverless + CVM 实战
    Serverless Registry 设计解读与实战
    使用 ServerLess 实现云原生
    腾讯云 Serverless 技术在「老司机汽车 app」的落地实践
    LeetCode 树 103. 二叉树的锯齿形层次遍历(双端队列 DFS的空间复杂度)
    LeetCode 树 116. 填充每个节点的下一个右侧节点指针(层序遍历 分层)
    LeetCode 树 230. 二叉搜索树中第K小的元素(二叉搜索树 中序遍历 剪枝)
    LeetCode 树 236. 二叉树的最近公共祖先(递归 深度优先搜索)
    LeetCode 树 102. 二叉树的层序遍历(广度优先搜索 深度优先搜索 队列)
  • 原文地址:https://www.cnblogs.com/zhaoqingqing/p/4901393.html
Copyright © 2020-2023  润新知