6.4 KiB
UI 开发指南
一、窗口开发
窗口类继承 AbsHFUIWindow,通过 attribute 声明资源路径和 Canvas 层级:
[HFAssetBundlePath("UIWindows/ExampleWindow/ExampleWindow.prefab")]
[HFUIWindowLayer(enHFUICanvasLayer.UIWindow, enHFUIWindowLayer.Dynamic, isUnloadOnClose = true)]
public partial class ExampleWindow : AbsHFUIWindow
{
protected override void OnRunAwake()
{
base.OnRunAwake();
// 查找控件、绑定按钮、初始化列表。
}
protected override void OnOpen(Action _onOpenComplete)
{
// 注册事件并刷新本次显示数据。
base.OnOpen(_onOpenComplete);
}
protected override void OnClose(Action _onCloseComplete)
{
// 移除事件和计时器。
base.OnClose(_onCloseComplete);
}
}
业务打开窗口使用:
HFFunCatalogue.UIManager.OpenWindow<ExampleWindow>();
不要直接实例化窗口 Prefab,也不要直接调用内部 HFUIWindowMgr 单例。
isUnloadOnClose 默认是 false。设为 true 后,通过 UI 管理器或窗口自身调用 CloseWindow 时,会在 OnClose 完成后自动销毁缓存实例并卸载窗口资源,适用于低频且内存占用较大的窗口。频繁开关的窗口应保留默认缓存行为,避免反复加载 Prefab 和 AssetBundle。
二、生命周期规则
OnRunAwake:实例首次绑定时执行一次,适合控件查找、按钮绑定和虚拟列表初始化。OnOpen:每次打开执行,适合注册事件、应用参数和刷新数据数量。OnClose:每次关闭执行,与OnOpen中的监听、计时器和临时状态对称清理。- 重写
OnOpen(Action)或OnClose(Action)时必须保证完成回调最终且仅调用一次;自动卸载窗口会在OnClose完成回调后执行。 - 列表初始化不能依赖数据已经到达;先以数量
0初始化,数据准备后再刷新数量。
三、单列虚拟列表
选择当前项目支持的接口
动态列表优先通过项目的 SFUI_ScrollRect 使用。先检查当前源码、Prefab 和相似窗口,确认使用 BindEvent 还是 SuperScrollView 包装;两套入口不要混用,也不要仅因本文示例而迁移已有列表。视觉、共享页签和 Item 复用规则见 界面拼接。
BindEvent 接口
支持该接口的项目,通过以下四个回调绑定列表;名称与类型以目标项目实际签名为准:
| 参数 | 职责 |
|---|---|
Func<int> _getItemCount |
返回当前数据数量,数据未就绪时返回 0 |
Func<int, GameObject> _getItemTemplete |
按数据索引返回模板 GameObject;相同结构返回同一模板 |
Action<SFUI_ScrollRectItem> _updateItem |
依据当前 Item 的数据索引完整刷新显示 |
Func<SFUI_ScrollRectItem, ISimulateBehaviour> _funcBindSimulateBehaviour |
为 Item 绑定项目模拟行为脚本,避免重复绑定 |
模板选择回调返回的是模板对象,不是 LoopListView2 的模板索引。绑定后按当前实现的刷新入口更新列表;不要照搬另一套包装的 RefreshDataTotalCount。同时检查空列表、尺寸变化、布局缓存与初始化要求。模板组件遵循项目序列化方式,不预先重复挂载列表动态添加的组件。
LoopListView2 包装接口
已接入该包装的项目,单列或单行滚动使用以下接口:
m_List.SetLoopListViewItemPrefabs(
new GameObject[] { m_ItemTemplate.gameObject }, 0, 1);
m_List.InitListView(
0,
OnGetItem,
OnCreateItem,
OnUpdateItem,
OnGetPrefabIndex,
false);
数据准备或变化后:
m_List.RefreshDataTotalCount(m_DataList == null ? 0 : m_DataList.Count);
回调职责:
| 回调 | 职责 |
|---|---|
OnGetItem |
从已创建节点取得 SFUI_ScrollRectItem |
OnCreateItem |
首次创建绑定类和 SimulateBehaviour |
OnUpdateItem |
根据 dataIndex 更新显示,不能假设旧数据仍有效 |
OnGetPrefabIndex |
多模板时返回模板索引,单模板返回 0 |
垂直列表模板通常使用左上锚点和 pivot (0, 1),宽度与 viewport 匹配;水平列表按滚动方向设置。模板尺寸或 pivot 错误会造成重叠、偏移或不可见。
四、多列虚拟列表
已接入 SuperScrollView 包装的项目,存在固定列数、GridLayoutGroup 语义或二维排布时使用 LoopGridView:
m_Grid.SetLoopGridViewItemPrefabs(
new GameObject[] { m_ItemTemplate.gameObject },
new Vector2(216, 293),
new Vector2(50, 30),
new RectOffset(55, 55, 0, 0),
4,
1);
m_Grid.InitGridView(
0,
OnGetGridItem,
OnCreateGridItem,
OnUpdateGridItem,
OnGetGridPrefabIndex);
刷新数量:
m_Grid.RefreshGridDataTotalCount(m_DataList == null ? 0 : m_DataList.Count);
不要通过单列 ListView 模拟多列布局。Grid 的 itemSize、spacing、padding 和列数必须与 Prefab 视觉布局一致。
五、Prefab 设置
- 模板节点保持
SetActive(false),但必须位于窗口层级中以便引用收集。 SFUI_ScrollRect、viewport、content 和模板引用必须完整。- SuperScrollView 包装初始化会关闭旧
LayoutGroup和ContentSizeFitter,运行时布局由该包装管理;不要重新勾选或依赖旧组件计算尺寸。 - 使用
BindEvent时检查其实际布局与初始化机制,不套用 SuperScrollView 的组件关闭规则。 - 使用 SuperScrollView 包装时检查模板上的
LoopListViewItem2或LoopGridViewItem;包装层会补组件,但 Prefab 结构仍需正确。 - UIMono 收集需要包含 inactive 子节点,不能只扫描激活节点。
六、验证清单
- 构建
SFMono.csproj和涉及的GameHF.csproj。 - 刷新 Unity Assets 并读取 Console。
- 检查 Prefab 的 viewport、content、模板、RectTransform 和组件启用状态。
- 验证空数据、1 个元素、超出视口、多次刷新和重复打开。
- 滚动后验证元素回收与复用,没有
KeyNotFoundException、旧数据显示或重复创建。 - 多列列表验证最后一行不足列数时的尺寸和定位。
文档关联
| 文档 | 关联原因 |
|---|---|
| UI 窗口系统 | 窗口框架入口与生命周期 |
| 界面拼接 | Prefab 结构和视觉实现 |
| UI 拼接工具 | JSON/Prefab 双向转换 |
| 开发规则 | 通用 UI 和 C# 规则 |