UGUI自定义ListView列表组件(虚拟列表)

本文介绍了一位程序员如何针对Unity的UGUI系统进行优化,解决ScrollRect在处理大量元素时的性能问题。作者通过封装一个UIList组件,实现了虚拟列表功能,支持数据刷新和列表宽高变化的回调。该组件可以用于排行榜、列表显示等场景,并提供了设置项皮肤、数据结构体以及滑动更新逻辑。此外,文章还提供了完整的代码示例和调用方法。
摘要由CSDN通过智能技术生成

最近在尝试使用ugui,发现这个还真的不方便,很多都是基础组件。自带的ScrollRect没有虚拟列表,如果有10000个,那岂不是需要创建10000个子对象?
不过,身为程序员怎么能不偷懒呢?就打算自己封装一个组件:
效果:
在这里插入图片描述
用途一般是用来做排行榜啊,显示列表等:像这种
在这里插入图片描述
首先需要一个数据结构体,作为后续存数据:

public struct ItemDate
{
    public int index;
    public object data;
    public UIList m_parent;
}

需要列表相对应的item:
item也有皮肤ui

public class Item:MonoBehaviour
{
    public int index;
    public object data;
    public UIList m_parent;
    public Dictionary<string, Transform> m_childName;
    public void refData()
    {
        index = 0;
        data = null;
        m_parent = null;
        m_childName = new Dictionary<string, Transform>();
    }
}

功能我就只做了:距离顶部,距离底部,距离左部,列表间距,获取文本测量高度,listview列表宽高改变时回调,数据刷新时回调。
当然可以根据自己的需求进行扩展~
为了方便起见,我将item的成员全部存了起来,调用直接字典键值对拿取就可以了,不过没有区分类型,需要自己GetComponent<组件>就可以了。

 /// <summary>
 /// 储存ui成员变量
 /// </summary>
 /// <param name="skin"></param>
 /// <returns></returns>
 private Dictionary<string, Transform> setUIChildName(Transform skin)
 {
     Dictionary<string, Transform> map = new Dictionary<string, Transform>();
     for (int i = 0; i < skin.childCount; i++)
     {
         Transform childobj = skin.GetChild(i);
         map[childobj.gameObject.name] = childobj;
         if (childobj.childCount > 0)
         {
             setUIChildName(childobj);
         }
     }
     return map;
 }

滑动改变当前item坐标和更新数据:我用的计数器:startindex和endindex,来判断区间所在
然后就是区分到底是向上滑动还是向下滑动的:
刚开始我的思路是用的记录当前帧和上一帧之间差值的正负来判断方向,更新startindex和endindex,最后出了bugQAQ。。
然后就根据矩形边框的超出界限高度进行判断,得到cur,在与startindex进行对比,大于它,就是上方向,反之则是下方向,然后更新计数器
在这里插入图片描述
完整代码:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class Item:MonoBehaviour
{
    public int index;
    public object data;
    public UIList m_parent;
    public Dictionary<string, Transform> m_childName;
    public void refData()
    {
        index = 0;
        data = null;
        m_parent = null;
        m_childName = new Dictionary<string, Transform>();
    }
}
public struct ItemDate
{
    public int index;
    public object data;
    public UIList m_parent;
}
public class UIList : ScrollRect
{
    RectTransform rectTr;

    private float m_width;
    private float m_height;
    private List<ItemDate> m_datas;
    private RectTransform m_Skin;
    private List<RectTransform> comList;
    private int startIndex;
    private int endIndex;
    /// <summary>
    /// 宽高改变时触发
    /// </summary>
    public event Action OnChangeViewWH;
    /// <summary>
    /// 数据跟新时调用
    /// </summary>
    public event Action<Item> OnUpdateItem;
    /// <summary>
    /// 距离顶部
    /// </summary>
    private float _m_top;
    public float m_top {
        set { _m_top = value; }
    }
    /// <summary>
    /// 距离底部
    /// </summary>
    private float _m_down;
    public float m_down
    {
        set { _m_down = value; }
    }
    /// <summary>
    /// 距离左部
    /// </summary>
    private float _m_left;
    public float m_left
    {
        set { _m_left = value; }
    }
    /// <summary>
    /// 间距
    /// </summary>
    private float _Spacing;
    public float Spacing
    {
        get { return _Spacing; }
        set { _Spacing = value; }
    }
    /// <summary>
    /// 文本测量高度和文本测量宽度
    /// </summary>
    private Vector2 _sizeData;
    public Vector2 sizeData
    {
        get { return _sizeData; }
    }
    override protected void Awake()
    {
        base.Awake();
        rectTr = GetComponent<RectTransform>();
        comList = new List<RectTransform>();
        m_datas = new List<ItemDate>();
        startIndex = 0;
        _Spacing = 10;
        m_width = rectTr.sizeDelta.x;
        m_height = rectTr.sizeDelta.y;
        OnChangeViewWH += ChangeViewWH;
        OnUpdateItem += UpdataChild;
        onValueChanged.AddListener(OnChange);
    }
    override protected void Start()
    {
        base.Start();

    }
    // Update is called once per frame
    void Update()
    {
        if (m_width != rectTr.sizeDelta.x || m_height != rectTr.sizeDelta.y)
        {
            m_width = rectTr.sizeDelta.x;
            m_height = rectTr.sizeDelta.y;
            //调用宽高改变时触发
            OnChangeViewWH.DynamicInvoke();
        }
    }
    /// <summary>
    /// 设置list子控件皮肤
    /// </summary>
    public RectTransform setSkin
    {
        set
        {
            m_Skin = value;
        }
    }
    /// <summary>
    /// 设置数据源
    /// </summary>
    /// <param name="datas"></param>
    public void setData<T>(List<T> datas)
    {
        if (m_Skin == null)
        {
            Debug.LogError("Item皮肤没有设置");
        }
        //初始化数据
        this.m_datas.Clear();
        this.content.localPosition = new Vector3(this.content.localPosition.x, 0, 0);
        startIndex = 0;
        int len = datas.Count;
        //计算容器宽度+距离底部的距离
        _sizeData = new Vector2(m_Skin.sizeDelta.x, (m_Skin.sizeDelta.y * len + _Spacing * len - _Spacing) + _m_down + _m_top);
        //更新数据
        for (int i = 0; i < datas.Count; i++)
        {
            ItemDate data = new ItemDate();
            data.index = i;
            data.data = datas[i];
            data.m_parent = this;
            this.m_datas.Add(data);
        }
        //宽高发生改变
        ChangeViewWH();
    }
    /// <summary>
    /// 储存ui成员变量
    /// </summary>
    /// <param name="skin"></param>
    /// <returns></returns>
    private Dictionary<string, Transform> setUIChildName(Transform skin)
    {
        Dictionary<string, Transform> map = new Dictionary<string, Transform>();
        for (int i = 0; i < skin.childCount; i++)
        {
            Transform childobj = skin.GetChild(i);
            map[childobj.gameObject.name] = childobj;
            if (childobj.childCount > 0)
            {
                setUIChildName(childobj);
            }
        }
        return map;
    }
    /// <summary>
    /// 宽高发生改变时
    /// </summary>
    public void ChangeViewWH() {
        //向上取整得到数量
        int colunm = Mathf.CeilToInt((m_height + _Spacing) / (m_Skin.sizeDelta.y + _Spacing));
        endIndex = startIndex + colunm;
        //超出的删除
        if (comList.Count > colunm + 1 && colunm > 0) 
        {
            for (int s = colunm + 1; s < comList.Count; s++) 
            {
                Destroy(comList[s].gameObject);
                comList.RemoveAt(s);
            }
            return;
        }
        //生成
        if (colunm <= m_datas.Count)
        {
            for (int i = comList.Count; i <= colunm; i++)
            {
                //加个保护
                if (i >= this.m_datas.Count) break;

                RectTransform skin = Instantiate(m_Skin);
                //添加item
                skin.gameObject.AddComponent<Item>();
                comList.Add(skin);
                //设置父节点
                skin.SetParent(this.content, false);
            }
        }
        //更新所有item
        updateItem(); 
        //赋值文本测量高度
        this.content.sizeDelta = new Vector2(this.content.sizeDelta.x, _sizeData.y);
    }
    /// <summary>
    /// 数据初始化
    /// </summary>
    private void updateItem()
    {
        for (int i = 0; i < comList.Count; i++)
        {
            //计算item的坐标,以距离左边和距离上边为基准
            comList[i].localPosition = new Vector3(_m_left, -(_m_top + i * (m_Skin.sizeDelta.y + _Spacing)), 0);
            updataView(comList[i], i);
        }
    }
    /// <summary>
    /// 刷新数据--根据item刷新
    /// </summary>
    /// <param name="_comList"></param>
    /// <param name="i"></param>
    private void updataView(RectTransform _comList, int i)
    {
        Item data = _comList.gameObject.GetComponent<Item>();
        data.index = this.m_datas[i].index;
        data.data = this.m_datas[i].data;
        data.m_parent = this.m_datas[i].m_parent;
        data.m_childName = setUIChildName(_comList);
        OnUpdateItem.DynamicInvoke(data);
    }
    /// <summary>
    /// 滑动改变时
    /// </summary>
    /// <param name="pos"></param>
    private void OnChange(Vector2 pos)
    {
        int colunm = endIndex - startIndex;
        int curindex = Mathf.FloorToInt(this.content.localPosition.y / (m_Skin.sizeDelta.y + _Spacing));
        if (curindex > startIndex && endIndex + 1 < this.m_datas.Count)  
        {
            //符合当前的item
            for (int i = 0; i < comList.Count; i++)
            {
                Item data = comList[i].gameObject.GetComponent<Item>();
                if (data.index == startIndex)
                {
                    comList[i].localPosition = new Vector3(_m_left, -_m_top - (endIndex + 1) * (m_Skin.sizeDelta.y + _Spacing), 0);
                    updataView(comList[i], endIndex + 1);
                    break;
                }
            }
            startIndex++;
            endIndex = startIndex + colunm;
        }
        else if (curindex < startIndex && startIndex - 1 >= 0 && pos.y < 1)    
        {
            for (int i = 0; i < comList.Count; i++)
            {
                Item data = comList[i].gameObject.GetComponent<Item>();
                if (data.index == endIndex)
                {
                    comList[i].localPosition = new Vector3(_m_left, -_m_top - (startIndex - 1) * (m_Skin.sizeDelta.y + _Spacing), 0);
                    //刷新单个数据
                    updataView(comList[i], startIndex - 1);
                    break;
                }
            }
            startIndex--;
            endIndex = startIndex + colunm;
        }
    }
    public virtual void UpdataChild(Item data) { }
}

调用:

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

public class dataa {
    public string name;
    public int index;
}
public class addlist : MonoBehaviour
{
    public UIList list;
    public GameObject skin;
    public Button btn;
    // Start is called before the first frame update
    void Start()
    {
        list.setSkin = skin.GetComponent<RectTransform>();
        List<dataa> dic = new List<dataa>();
        for (int i = 0; i < 10; i++)
        {
            dataa date = new dataa();
            date.name = "名字" + i;
            date.index = i;
            dic.Add(date);
        }
        list.OnUpdateItem += change;
        list.m_left = 10;
        list.m_top = 20;
        list.m_down = 30;
        list.setData(dic);
        List<dataa> dica = new List<dataa>();
        btn.onClick.AddListener(delegate ()
        {
            dica.Clear();
            for (int i = 0; i < 10; i++)
            {
                dataa date = new dataa();
                date.name = "已经改变";
                date.index = i;
                dica.Add(date);
            }
            list.setData(dica);
        });
    }
    void change(Item item)
    {
        Text text = item.m_childName["Text"].GetComponent<Text>();
        Button on_btn = item.m_childName["Button"].GetComponent<Button>();
        text.text = "xx" + (item.data as dataa).index + "--" + (item.data as dataa).name;
        //按钮点击消息事件
        on_btn.onClick.AddListener(() =>
        {
            Debug.Log((item.data as dataa).index);
        });
    }
    void Update()
    {
        
    }
}

结构我用的还是Scroll View的结构,删掉了滑动条,同步了边框
在这里插入图片描述
调用我随便添加到组件上面的,只要参数没问题就可以了
在这里插入图片描述
item的锚点一定要是左上角:
在这里插入图片描述
就酱~,每天学一点,那么前进的方向就一定是正确的。

### 回答1: Unity UGUI 列表是一种常见的 UI 控件,用于展示类别、选项或者数据。它通常由一个滚动视图组成,包含一个或多个列表项,每个列表项包含显示内容、图标和按钮。 在 Unity 中,实现列表通常需要以下步骤: 1. 创建一个滚动区域并设置相关参数,如大小、位置、滚动方向等等。 2. 在滚动区域内创建列表项,通常使用一个 prefab 来实现。 3. 让列表项拥有响应鼠标事件的组件,通常是按钮或 Toggle 组件。 4. 定义列表项的数据结构,如包含名称、描述、图标等属性。 5. 将数据与列表项绑定,使得列表项能够根据数据源的变化自动更新。 6. 定义列表项的样式,如字体、颜色、对齐方式等等。 7. 编写列表项的逻辑代码,处理用户点击触发的事件。 总之,Unity UGUI 列表是一种非常常见和实用的 UI 控件,可以用于许多不同的场景,如游戏菜单、设置界面、排行榜等等。有了列表的支持,用户可以更加便捷地浏览和操作大量的信息。 ### 回答2: Unity UGUI列表是一个UI组件,它允许开发者在游戏设计中创建各种不同类型的列表。在列表中,开发者可以显示一系列项目,用于展示各种信息和元素,例如角色列表、任务列表、排行榜等。 这个组件提供了一些控制选项,例如滚动条、滑动速度、自动布局等。开发者可以使用这些选项来控制列表的行为,使其更接近预期的效果。列表还可以支持不同的布局方式,例如网格布局、表格布局、平铺布局等,以便显示不同格式的项目。 Unity UGUI列表还允许开发者自定义项目的外观和交互。每个项目都可以选择不同的背景图片、字体、颜色和图标等。开发者还可以在每个项目上定义按钮事件或其他交互事件,例如打开一个菜单、播放动态内容等。 总的来说,Unity UGUI列表是一个很有用的UI组件,它可以简化游戏设计中的列表创建过程,并提供了灵活的控制和交互选项。开发者可以利用它来创建丰富的游戏UI,为玩家提供更好的交互和用户体验。 ### 回答3: Unity UGUI 列表是 Unity 引擎中的一个 UI 元素,用于显示和控制列表中的项目。列表通常用于显示大量的数据,如图像、文本或按钮。Unity UGUI 中的列表组件支持选择、滚动和无限滚动功能。列表组件可以通过在游戏对象中添加 Scroll View、Viewport 和 Content 三个组件来实现。Scroll View 是列表的外壳,Viewport 是显示列表的区域,Content 是列表中的项目。在 Content 中,通过使用 Layout Group 和 Content Size Fitter 来控制列表中的项目排列和大小。 Unity UGUI 列表可以和数据绑定一起使用,通过数据绑定可以动态地更新列表中的项目。例如,在游戏中选择一种角色后,可以通过数据绑定将角色属性动态地绑定到列表中的项目,从而更新列表中的显示内容。列表组件还支持使用导航组件来快速地在列表中导航。在导航组件中,可以使用键盘或鼠标来快速地选择列表中的项目。 Unity UGUI 列表是游戏开发中非常重要的一个元素,它可以用来创建各种类型的列表,如任务列表、道具列表等。在使用列表组件时,需要仔细地考虑列表的设计和布局,以便用户可以轻松地使用列表。使用 Unity UGUI 列表可以使游戏界面变得更加直观和易于操作,增强游戏的可玩性和用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值