七种 Unity UGUI 列表刷新模式
下面详细讲解七种 Unity UGUI 列表刷新模式,并为每一种提供可直接参考的代码示例。所有示例基于 UnityEngine.UI 和 UnityEngine 命名空间,假设已引用相关库。
实际上一般就第一种和第三种用的最多
1. 全量刷新(Full Refresh)
原理
销毁 content 下的所有子物体,然后根据当前数据源的数量重新创建对应数量的 UI 元素,并通过回调绑定数据。
优点
- 实现简单,逻辑清晰。
- 确保 UI 与数据完全同步,无状态残留。
缺点
- 频繁的销毁/创建会产生大量 GC,性能较差。
- 不适用于长列表(>100)或频繁刷新场景。
适用场景
- 列表项很少(<30)。
- 内容完全替换且刷新频率低(如设置面板、角色选择界面)。
代码示例
csharp
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public static class ListRefreshFull
{
/// <summary>
/// 全量刷新列表
/// </summary>
public static List<T> RefreshFull<T>(this RectTransform content, List<string> dataList,
GameObject itemPrefab, System.Action<T, string> onRefresh)
where T : Component
{
// 1. 销毁所有子物体
foreach (Transform child in content)
Object.Destroy(child.gameObject);
List<T> items = new List<T>();
// 2. 根据数据数量重新创建
foreach (string data in dataList)
{
GameObject go = Object.Instantiate(itemPrefab, content);
T item = go.GetComponent<T>();
onRefresh?.Invoke(item, data);
items.Add(item);
}
// 3. 强制重建布局
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
return items;
}
}
// 使用示例
public class DemoFullRefresh : MonoBehaviour
{
public RectTransform content;
public GameObject itemPrefab;
void Start()
{
List<string> players = new List<string> { "Alice", "Bob", "Charlie" };
content.RefreshFull(players, itemPrefab, (item, name) =>
{
item.GetComponent<Text>().text = name;
});
}
}
2. 增量刷新(Incremental Update)
原理
保持现有子物体数量不变,遍历所有子物体,用新数据逐个更新其显示内容。如果数据数量与现有子物体数量不一致,则通过增加或删除子物体来匹配。
优点
- 没有创建/销毁开销(除数量调整外)。
- 适合数据频繁变化但列表结构稳定(增删少)的场景。
缺点
- 需要处理数量不一致的逻辑。
- 如果数据完全打乱(例如排序后),可能需要重新排列子物体顺序。
适用场景
- 列表长度基本固定,只是内容数值变化(如实时排行榜、计分板)。
代码示例
csharp
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public static class ListRefreshIncremental
{
public static void RefreshIncremental<T>(this RectTransform content, List<string> newData,
GameObject itemPrefab, System.Action<T, string> onRefresh)
where T : Component
{
int currentCount = content.childCount;
int newCount = newData.Count;
// 1. 数量调整:多了就删,少了就加
if (currentCount > newCount)
{
for (int i = newCount; i < currentCount; i++)
Object.Destroy(content.GetChild(i).gameObject);
}
else if (currentCount < newCount)
{
for (int i = currentCount; i < newCount; i++)
Object.Instantiate(itemPrefab, content);
}
// 2. 更新所有现有元素
for (int i = 0; i < newCount; i++)
{
Transform child = content.GetChild(i);
T item = child.GetComponent<T>();
onRefresh?.Invoke(item, newData[i]);
}
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
}
}
// 使用示例
public class DemoIncremental : MonoBehaviour
{
public RectTransform content;
public GameObject itemPrefab;
private List<string> data = new List<string> { "A", "B", "C" };
void Update()
{
// 模拟数据变化
for (int i = 0; i < data.Count; i++)
data[i] = $"Value {Random.Range(0, 100)}";
content.RefreshIncremental(data, itemPrefab, (Text txt, string val) => txt.text = val);
}
}
3. 对象池复用刷新(Pooling)
原理
预先创建一批 UI 元素放入池中(Stack 或 Queue)。刷新时从池中取出所需数量的元素激活并放入 content;多余元素回收到池中(停用并移出父级)。完全避免 Instantiate 和 Destroy 的 GC。
优点
- 极大减少 GC,性能优秀。
- 适用于中等长度(50~500)且频繁整体刷新的列表。
缺点
- 需要额外维护对象池逻辑。
- 池的初始预热需要时间。
适用场景
- 聊天记录、商品列表、任务列表等频繁完全替换内容的界面。
代码示例
csharp
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class UIObjectPool<T> where T : Component
{
private GameObject prefab;
private Transform parent;
private Stack<T> pool = new Stack<T>();
public UIObjectPool(GameObject prefab, Transform parent, int preloadCount = 5)
{
this.prefab = prefab;
this.parent = parent;
for (int i = 0; i < preloadCount; i++)
CreateNew();
}
private T CreateNew()
{
GameObject go = Object.Instantiate(prefab, parent);
go.SetActive(false);
T comp = go.GetComponent<T>();
pool.Push(comp);
return comp;
}
public T Get()
{
if (pool.Count == 0) CreateNew();
T item = pool.Pop();
item.gameObject.SetActive(true);
return item;
}
public void Return(T item)
{
item.gameObject.SetActive(false);
item.transform.SetParent(parent);
pool.Push(item);
}
}
public static class ListRefreshPooling
{
public static List<T> RefreshWithPool<T>(this RectTransform content, List<string> dataList,
UIObjectPool<T> pool, System.Action<T, string> onRefresh)
where T : Component
{
// 1. 将所有当前子物体回收到池中
foreach (Transform child in content)
{
T item = child.GetComponent<T>();
if (item != null) pool.Return(item);
}
// 2. 从池中取出新数据所需的元素,并重新设置父级为 content
List<T> activeItems = new List<T>();
foreach (string data in dataList)
{
T item = pool.Get();
item.transform.SetParent(content);
onRefresh?.Invoke(item, data);
activeItems.Add(item);
}
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
return activeItems;
}
}
// 使用示例
public class DemoPooling : MonoBehaviour
{
public RectTransform content;
public GameObject itemPrefab;
private UIObjectPool<Text> pool;
void Start()
{
pool = new UIObjectPool<Text>(itemPrefab, content, 10);
}
void RefreshList(List<string> newData)
{
content.RefreshWithPool(newData, pool, (txt, val) => txt.text = val);
}
}
4. 虚拟滚动 / 动态复用(Virtualization)
原理
只创建足以填满视口(Viewport)的 UI 元素(例如 10~20 个)。监听 ScrollRect 的滚动事件,根据滚动偏移量计算出当前应该显示的数据范围,然后移动现有元素的位置并重新绑定数据,实现无限滚动。
优点
- 内存占用固定,支持百万级数据。
- 滚动流畅。
缺点
- 实现复杂,需要精确计算元素位置、尺寸以及复用逻辑。
- 要求所有元素尺寸固定或能提前计算。
适用场景
- 好友列表、文件浏览器、无尽下拉加载。
简化代码示例(固定高度、单向滚动)
csharp
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class VirtualScrollList : MonoBehaviour
{
public ScrollRect scrollRect;
public RectTransform content;
public GameObject itemPrefab;
public float itemHeight = 50f;
private List<string> data = new List<string>(); // 假设有 10000 条数据
private List<RectTransform> items = new List<RectTransform>();
private int currentTopIndex = 0;
void Start()
{
// 预先创建足够填满视口的元素(视口高度 / itemHeight + 2)
int visibleCount = Mathf.CeilToInt(scrollRect.viewport.rect.height / itemHeight) + 2;
for (int i = 0; i < visibleCount; i++)
{
GameObject go = Instantiate(itemPrefab, content);
RectTransform rect = go.GetComponent<RectTransform>();
items.Add(rect);
}
scrollRect.onValueChanged.AddListener(OnScroll);
UpdateContentHeight();
RefreshVisibleItems(0);
}
void UpdateContentHeight()
{
content.sizeDelta = new Vector2(content.sizeDelta.x, data.Count * itemHeight);
}
void OnScroll(Vector2 _)
{
float scrollPos = content.anchoredPosition.y;
int newTopIndex = Mathf.FloorToInt(scrollPos / itemHeight);
newTopIndex = Mathf.Clamp(newTopIndex, 0, data.Count - items.Count);
if (newTopIndex != currentTopIndex)
{
currentTopIndex = newTopIndex;
RefreshVisibleItems(currentTopIndex);
}
}
void RefreshVisibleItems(int startIndex)
{
for (int i = 0; i < items.Count; i++)
{
int dataIndex = startIndex + i;
if (dataIndex < data.Count)
{
items[i].gameObject.SetActive(true);
items[i].anchoredPosition = new Vector2(0, dataIndex * itemHeight);
items[i].GetComponent<Text>().text = data[dataIndex];
}
else
{
items[i].gameObject.SetActive(false);
}
}
}
}
5. 数据绑定自动刷新(Data Binding / MVVM)
原理
为数据模型实现 INotifyPropertyChanged 或使用响应式属性(如 UniRx 的 ReactiveProperty)。UI 元素订阅数据变化事件,当属性值改变时自动更新自身显示。列表级的变化(增删元素)通常结合 ObservableCollection 触发整体刷新。
优点
- 完全解耦数据和视图,无需手动调用刷新方法。
- 细粒度更新,性能好。
缺点
- 需要引入响应式库(如 UniRx)或自己实现事件系统。
- 列表整体替换时仍需配合其他模式。
适用场景
- 复杂表单、实时监控面板、高度交互的 UI。
代码示例(使用 UniRx)
csharp
using UnityEngine;
using UnityEngine.UI;
using UniRx;
using System.Collections.Generic;
// 数据模型
public class PlayerModel
{
public ReactiveProperty<string> Name = new ReactiveProperty<string>();
public ReactiveProperty<int> Level = new ReactiveProperty<int>();
}
// UI 绑定组件
public class PlayerView : MonoBehaviour
{
public Text nameText;
public Text levelText;
public void Bind(PlayerModel model)
{
model.Name.SubscribeToText(nameText);
model.Level.Subscribe(v => levelText.text = $"Lv.{v}");
}
}
// 列表管理
public class DataBindingList : MonoBehaviour
{
public RectTransform content;
public GameObject itemPrefab;
private List<PlayerModel> players = new List<PlayerModel>();
private List<PlayerView> views = new List<PlayerView>();
void Start()
{
// 创建初始数据
for (int i = 0; i < 10; i++)
{
var model = new PlayerModel { Name = { Value = $"Player{i}" }, Level = { Value = i } };
players.Add(model);
var go = Instantiate(itemPrefab, content);
var view = go.GetComponent<PlayerView>();
view.Bind(model);
views.Add(view);
}
// 模拟数据变化(UI 自动刷新)
Observable.Interval(System.TimeSpan.FromSeconds(2)).Subscribe(_ =>
{
foreach (var p in players)
p.Level.Value++;
});
}
}
6. 局部刷新(Partial Refresh)
原理
只更新列表中特定索引或特定条件的元素。需要维护一个从数据索引到 UI 元素的映射(如 Dictionary<int, UIElement>),或者通过 content.GetChild(index) 获取。
优点
- 性能最佳,无 GC。
- 实现简单。
缺点
- 需要维护索引映射,且仅适合少量元素变化。
- 对大批量变化不友好。
适用场景
- 点赞、选中状态切换、某个玩家等级提升等单点变化。
代码示例
csharp
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class PartialRefreshDemo : MonoBehaviour
{
public RectTransform content;
public GameObject itemPrefab;
private List<Text> itemTexts = new List<Text>(); // 维护映射
private List<string> data = new List<string>();
void Start()
{
// 初始化全量刷新
data = new List<string> { "Apple", "Banana", "Cherry", "Date" };
foreach (var d in data)
{
var go = Instantiate(itemPrefab, content);
var txt = go.GetComponent<Text>();
txt.text = d;
itemTexts.Add(txt);
}
}
public void UpdateItem(int index, string newValue)
{
if (index >= 0 && index < itemTexts.Count)
{
data[index] = newValue;
itemTexts[index].text = newValue;
}
}
// 例如点击按钮修改第2项
public void OnButtonClick()
{
UpdateItem(1, "Blueberry");
}
}
7. 混合模式(Hybrid)
原理
根据场景灵活组合多种模式。例如:使用对象池管理元素创建/销毁,同时采用增量刷新更新内容;或者虚拟滚动内部使用对象池复用可视元素;甚至结合数据绑定实现局部自动更新。
优点
- 能够应对复杂多变的需求,平衡性能和开发成本。
缺点
- 实现较复杂,需要仔细设计。
适用场景
- 大型项目中的通用列表组件(如聊天 + 商品 + 排行榜共用一套高性能列表)。
代码示例(对象池 + 增量刷新)
csharp
// 扩展方法:先保证元素数量与数据匹配(对象池增删),再增量更新所有元素
public static void RefreshHybrid<T>(this RectTransform content, List<string> newData,
UIObjectPool<T> pool, System.Action<T, string> onRefresh)
where T : Component
{
int currentCount = content.childCount;
int targetCount = newData.Count;
// 1. 使用对象池调整数量
if (currentCount > targetCount)
{
for (int i = targetCount; i < currentCount; i++)
{
T item = content.GetChild(i).GetComponent<T>();
pool.Return(item);
}
}
else if (currentCount < targetCount)
{
for (int i = currentCount; i < targetCount; i++)
{
T item = pool.Get();
item.transform.SetParent(content);
}
}
// 2. 增量更新所有现有元素
for (int i = 0; i < targetCount; i++)
{
T item = content.GetChild(i).GetComponent<T>();
onRefresh?.Invoke(item, newData[i]);
}
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
}
总结
全量刷新 | Destroy + Instantiate | 差 | 低 | <30 |
增量刷新 | 调整数量 + 遍历更新 | 良好 | 中 | <200 |
对象池 | Get / Return | 优秀 | 中 | 50~500 |
虚拟滚动 | 动态复用可视元素 | 极佳 | 高 | 任意(>1000) |
数据绑定 | 订阅事件自动更新 | 优秀 | 高 | 任意(需配合) |
局部刷新 | 直接修改特定元素 | 最佳 | 低 | 单点变化 |
混合模式 | 按需组合 | 可调节 | 中~高 | 任意 |
根据项目具体需求(数据量、刷新频率、开发周期)选择合适的模式,才能写出高效又易于维护的 Unity UI 列表。
查看8道真题和解析