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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM