基于Unity-NGUI的可重用列表

简介:

对于数据量较大的列表, 载入时需要创建等同于数据个数的GameObject, 非常消耗性能.

本列表组件只生成Math.min(列表容器高度/条目高度 +1, 数据个数) 个GameObject, 并在列表滚动时, 重排列条目位置, 并重新对每个条目SetData(待优化).

适用范围:

包含大批量数据的单一条目列表, 所有条目高度一致

缺点:

方法WrapContent中未对SetData的调用做剪枝, 导致过多对子项SendMessage方法的调用, 当有打印语句时会有明显卡顿

代码如下:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SongeWrap : MonoBehaviour
{
    public GameObject ItemRenderer;

    void Awake ()
    {
    }

    void Start()
    {
        pScroll.restrictWithinPanel = true;

        firstFlag = addFlag("FirstFlag");
        lastFlag = addFlag("LastFlag");

        pPanel.onClipMove = panel => WrapContent(); ;
        GetComponentInParent<UIWidget>().onChange += onContainerItemChange;
        WrapContent();
    }

    void Update () 
    {
        testFunc();
    }

    #region 数据
    List<object> dataProvider = new List<object>();
    public void AddData(object data)
    {
        dataProvider.Add(data);
        onDataChange();
    }
    public void RemoveData(object data)
    {
        if (dataProvider.Contains(data))
            dataProvider.Remove(data);
        onDataChange();
    }
    public void RemoveDataAt(int index)
    {
        if(index >= 0 && index < dataProvider.Count)
        {
            dataProvider.RemoveAt(index);
            onDataChange();
        }
    }
    public void ClearData()
    {
        dataProvider.Clear();
        onDataChange();
    }

    void onDataChange()
    {
        refreshItemNum();
    }

    public object GetDataAt(int index)
    {
        return dataProvider[index];
    }

    #endregion

    #region Item个数管理

    int maxItemNum = 0;//按照列表大小, 最大所需条目数

    void onContainerItemChange()
    {
        float containerSize = scrollDir == UIScrollView.Movement.Horizontal ?
            pPanel.width :
            pPanel.height;
        maxItemNum = (int)(containerSize / ItemSize) + 1;
        //Debug.Log("容器/条目大小变化, containerSize: " + containerSize + ", ItemSize:" + ItemSize);
        //Debug.Log("最大所需条目个数: " + maxItemNum);
        refreshItemNum();
    }

    List<Transform> GetChildList()
    {
        List<Transform> childList = new List<Transform>();
        for(int i = 0; i< transform.childCount; i++)
        {
            Transform childT = transform.GetChild(i);
            if (!(childT == firstFlag.transform || childT == lastFlag.transform))
                childList.Add(childT);
        }
        return childList;
    }

    void refreshItemNum()
    {
        int itemNum = Mathf.Min(dataProvider.Count, maxItemNum);
        //Debug.Log("刷新条目数: " + itemNum + " " + dataProvider.Count + " " + maxItemNum);
        List<Transform> childList = GetChildList();
        while(childList.Count > itemNum)
        {
            Transform removeItem = childList[0];
            removeItem.parent = null;
            removeItem.gameObject.SetActive(false);
            childList.Remove(removeItem);
            Destroy(removeItem.gameObject);
        }

        while(childList.Count < itemNum)
        {
            GameObject addItem = Instantiate(ItemRenderer);
            addItem.transform.parent = transform;
            addItem.transform.localScale = Vector3.one;
            addItem.transform.localPosition = Vector3.zero;
            childList.Add(addItem.transform);
        }
        WrapContent();
    }

    #endregion

    #region 布局

    UIScrollView.Movement scrollDir { get { return pScroll.movement; } }
    UIScrollView pScroll { get { return NGUITools.FindInParents<UIScrollView>(gameObject); } }
    UIPanel pPanel { get { return pScroll.GetComponent<UIPanel>(); } }

    private float itemSize = 100;
    public float ItemSize
    {
        get { return itemSize; }
        set
        {
            float oldItemSize = itemSize;
            itemSize = value;
            if (itemSize != oldItemSize)
            {
                firstFlag.GetComponent<UISprite>().width = (int)(itemSize);
                lastFlag.GetComponent<UISprite>().height = (int)(itemSize);
                onContainerItemChange();
            }
        }
    }

    GameObject firstFlag;
    GameObject lastFlag;

    GameObject addFlag(string goName)
    {
        GameObject newGO = new GameObject();
        newGO.name = goName;
        UISprite sp = newGO.AddComponent<UISprite>();
        sp.width = (int)(itemSize);
        sp.height = (int)(itemSize);
        newGO.transform.parent = transform;
        newGO.transform.localScale = Vector3.one;
        newGO.transform.localPosition = Vector3.zero;
        return newGO;
    }

    void sortChild(int startIndex)
    {
        List<Transform> childList = GetChildList();
        firstFlag.transform.localPosition = Vector3.zero;
        lastFlag.transform.localPosition = Vector3.zero;
        SongeUtil.EnumVector dir = scrollDir == UIScrollView.Movement.Vertical ?
            SongeUtil.EnumVector.y : 
            SongeUtil.EnumVector.x;
        firstFlag.SetLocalPos(0f, dir);
        lastFlag.SetLocalPos(-ItemSize * (dataProvider.Count  == 0 ? 0 : dataProvider.Count - 1), dir);

        if (scrollDir == UIScrollView.Movement.Vertical)
            childList.Sort(UIGrid.SortVertical);
        else
            childList.Sort(UIGrid.SortHorizontal);

        for (int i = 0; i < childList.Count; i++)
        {
            childList[i].SetLocalPos((-startIndex - i) * itemSize, dir);

            Debug.Log("SetData, startIndex/i: " + startIndex + "/" + i + "\r\n"
                + "RealIndex/dataProvider.count: " + (startIndex + i) + "/" + dataProvider.Count);

            childList[i].gameObject.SendMessage("SetIndex", startIndex + i);

            //由于scrollerPanel返回的位置信息可能出界, 所以获取的index必须做范围判定
            if ((startIndex + i) < dataProvider.Count && (startIndex + i) >= 0)
                childList[i].gameObject.SendMessage("SetData", dataProvider[startIndex + i]);
        }
    }

    int curIndex = -1;
    public void WrapContent()
    {
        int oldIndex = curIndex;
        List<Transform> childList = GetChildList();

        Vector3[] corners = pPanel.worldCorners;
        for (int i = 0; i < 4; ++i)
        {
            Vector3 v = corners[i];
            v = transform.InverseTransformPoint(v);
            corners[i] = v;
        }
        //当前位置
        float curPosition = scrollDir == UIScrollView.Movement.Horizontal ?
            corners[2].x - ItemSize / 2f - pPanel.clipSoftness.x :
            corners[2].y - ItemSize / 2f - pPanel.clipSoftness.y;
        float rawCurPos = curPosition;
        //总长度
        float totalLength = scrollDir == UIScrollView.Movement.Horizontal ?
            corners[2].x - corners[0].x :
            corners[2].y - corners[0].y;
        float softness = scrollDir == UIScrollView.Movement.Horizontal ?
            pPanel.clipSoftness.x :
            pPanel.clipSoftness.y;

        if (childList.Count * ItemSize < totalLength)
        {
            curPosition = Mathf.Clamp(curPosition, 0, totalLength - ItemSize - softness * 2f);
            curIndex = Mathf.FloorToInt(curPosition / ItemSize);
            sortChild(0);
        }
        else
        {
            curPosition = -curPosition;
            curPosition = Mathf.Max(curPosition, 0f);
            curIndex = Mathf.FloorToInt(curPosition / ItemSize);
            sortChild(curIndex);
        }

        //Debug.Log("cur(raw)/total: " + (int)curPosition + "(" + (int)rawCurPos + ")/" + totalLength + "\r\n"
        //    + oldIndex + " -> " + curIndex + (curIndex != oldIndex ? " Warp!" : ""));
    }
    #endregion

    #region 测试
    public bool TestModel = false;
    void testFunc()
    {
        if (!TestModel)
            return;
        if (Input.GetKeyDown(KeyCode.Alpha3))
            TestAddData();
        else if (Input.GetKeyDown(KeyCode.Alpha4))
            TestRemoveData();
    }

    [ContextMenu("TestAddData 测试添加数据")]
    void TestAddData()
    {
        AddData(Random.Range(0, 99).ToString());
    }
    [ContextMenu("TestRemoveData 测试删除数据")]
    void TestRemoveData()
    {
        RemoveDataAt(0);
    }
    
    #endregion
}

其中 ItemRenderer上需要挂载一个脚本接受数据, 该脚本需要有SetIndex(int index)和SetData(object data)两个方法

using UnityEngine;
using System.Collections;

public class TestItemRenderer : MonoBehaviour {

    int index = -1;
    void SetIndex(int _index)
    {
        index = _index;
    }

    public object data;
    void SetData(object _data)
    {
        if(data == _data)
        {
            //Debug.Log("重复数据: "+_data);
        }
        else
        {
            data = _data;
            string str = data is string ? data.ToString() : "非字符串类型: "+data;
            GetComponentInChildren<UILabel>().text = index+"."+str;

        }
    }
}

 

你可能感兴趣的:(基于Unity-NGUI的可重用列表)