基于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;

        }
    }
}
时间: 2024-10-24 21:39:44

基于Unity-NGUI的可重用列表的相关文章

基于 unity ngui 上的滚动加载__UiVirtual

在游戏里面经常会有背包,好友,对话,这样的列表.当列表的内容多了,如果一打开界面就对所有内容进行实例化,会消耗大量的性能,且会造成UI上的卡顿. 于是便需要,在列表里面只实例化屏幕上可见的item.屏幕外的不需要进行实例化. 这里 https://github.com/textcube/infinitescroll 便是我们要找的内容 如图: 然而在在看源码的时候我们不难发现,这个开源的git仓库,是对移动到屏幕外面的item进行了destroy(见WordItem.cs),对移动到屏幕上的it

基于Qt的类似QQ好友列表抽屉效果的实现

前段时间在忙毕业设计,所以一直没有更新博客.今天答辩完以后,将对我的毕业设计进行模块展示,供Qt初学者进行参考. 毕业设计题目:Linux系统下基于Qt的局域网即时通信系统设计与实现 其中我有一个类似于QQ的好友列表,然后对好友可以进行分组管理,毕设中具体效果图如下: 网上查寻到的设计思路: 1.采用QToolBox的方式,虽然看起来有点样子,但是并不是我们所熟悉的好友列表,比如:http://blog.csdn.net/qianguozheng/article/details/6719074

蛋哥的学习笔记之-基于Unity的Shader编程:X-1 音乐水波特效

蛋哥的学习笔记之-基于Unity的Shader编程:X-1 音乐水波特效 热度 13728 2015-7-11 23:34 |个人分类:蛋哥的学习笔记之-基于Unity的Shader编程| 音乐, Unity, Shader, 水波, Shader, Shader, Shader, Shader 一.要干些啥: 很久很久没有写文档了,前段时间做了个个人很喜欢的,自认为比较原创的小特效,所以写个文档纪念下(本人特别喜欢音乐) 思路其实很简单,首先用顶点着色器实现一般的水波特效,然后解析音频数据(我

【Unity NGUI游戏开发之五】多分辨率下完美分布式协同开发

NGUI多分辨率下完美分布式协同开发:不同分辨率下相对于屏幕坐标的Perfab数据不再丢失 NGUI多分辨率下完美分布式协同开发不同分辨率下相对于屏幕坐标的Perfab数据不再丢失 开发问题 原因分析 案例 完美过程 案例分析 实现过程 开发问题: NGUI分布式开发中,用git管理资源,团队成员每人负责一个perfab,所有现对于屏幕大小的相对位置的perfab因为引用了perfab外的数据,导致perfab的Anchor锚点数据丢失,最后的perfab集成后,必须重新设置,导致开发成本大幅度

Unity NGUI监听按钮点击事件的三种方法

NGUI版本:3.6.5 1.直接实现OnClick方法: 创建一个脚本,在脚本中实现OnClick()方法,绑定该脚本到按钮上,点击时就会实现OnClick函数内容: 2.使用SendMessage: 选择按钮后,打开Component——NGUI——Interaction,选择Button Message,为按钮添加一个UIButton Message组件: 然后设置UIButton Message中的参数即可: Target:接收按钮消息的游戏对象: Function Name:接收按钮消

【Unity NGUI游戏开发之三】TweenPosition位移动画(二):相对于UIAnchor不同分辨率下的完美适配位移动画

Unity中的UI我们采用的是NGUI,NGUI的界面位移动画,我们一般使用的是TweenPosition. 一种是简单的相对位移,不考虑分辨率适配问题,只需要简单的从位置A到位置B,已经在文中介绍了: [Unity NGUI游戏开发之二]TweenPosition位移动画(一):不相对于Anchor的位移动画 另外一种是考虑到屏幕分辨率适配的位移动画,我们游戏中大多遇到的是这种情况. eg.我们想让一个UI从屏幕外沿着屏幕的左边移动到屏幕的中央,TweenPositon播放动画,在960*64

Unity NGUI 使用经验

在.NET下使用了约2个月的Unity NGUI后,感觉.NET cs用起来还比较容易上手,Unity 2D项目中使用NGUI也非常好用.其丰富的库支持,让愿意花时间的人一学就会用,比如我们用得比较多的,iTween/EasyTouch/FxMaker/UnityVS等.尤其UnityVS让原来使用VS习惯的人,用起来那叫一个爽! 费话不多说了,下面说说我们以开发过程中解决的一些问题和关注的一些参数问题. 作为一个程序,保障游戏的流畅性是游戏可玩性高的重要基础. 1. 尽量把Draw Calls

Unity NGUI UIPanel下对粒子或自定义Mesh的剪裁

写在开篇: 越来越烦那些无脑转发自己不做验证的博主论坛楼主,网上好不容易找到一些资料,结果代码搞下来却是错的,有些确实是因为版本问题太老不兼容,但是有些明显是有问题的,转发前自己试试就知道肯定是不能用的.结果...哎...真是不想说啥了. 这次是在小地图中画线画圈,用到了动态绘制Mesh,小地图需要对这些线进行裁切,所以去网上搜了一篇叫做<Unity NGUI UIPanel下对粒子的剪裁>的文章.当然还是感谢一下原作者提供的思路.我这里对这篇文章中涉及到的代码进行了优化改动,使之可以使用.没

啥?虚拟现实技术已经应用到自动化仓库? | 基于unity实现的自动化仓库模拟监控系统

3D自动化仓库  演示视频  https://www.bilibili.com/video/av57621560 基于unity与Spring MVC实现 项目介绍 视频中的项目是我的本科毕业设计.从电气设备的计算机仿真模拟进行入手,实现自动化仓库场景电气设备功能模拟. 主要解决了以下三个关键问题: 采用unity3D实现自动化仓库硬件设备模拟 采用Spring MVC实现自动化仓库服务端. 采用WebSocket技术实现3D自动化仓库监控模拟端与硬件模拟端进行数据交互. 1 登陆验证 本系统分