【整合】 框架使用规则

This commit is contained in:
Shiliang
2026-07-10 17:50:20 +08:00
parent db0143be83
commit 001d9e5ef5
45 changed files with 1716 additions and 4062 deletions

View File

@@ -1,231 +1,135 @@
# StrayFog 框架 UI 开发指南
# UI 开发指南
## 一、窗口开发
### 1.1 创建模板
```csharp
[HFAssetBundlePath(enHFLanguageRootFolder.ASSETS_GAME_GAMEHFRESOURCE, "UIWindows/MyWindow/MyWindow.prefab")]
[HFUIWindowLayer(enHFUICanvasLayer.UIWindow, enHFUIWindowLayer.Dynamic)]
public class MyWindow : AbsHFUIWindow
{
private SFUI_Button BtnClose;
窗口类继承 `AbsHFUIWindow`,通过 attribute 声明资源路径和 Canvas 层级:
```csharp
[HFAssetBundlePath("UIWindows/ExampleWindow/ExampleWindow.prefab")]
[HFUIWindowLayer(enHFUICanvasLayer.Dynamic)]
public partial class ExampleWindow : AbsHFUIWindow
{
protected override void OnRunAwake()
{
base.OnRunAwake();
BtnClose = FindUICtrlByName<SFUI_Button>("BtnClose");
BtnClose.OnAddClickListener += OnBtnClose;
// 查找控件、绑定按钮、初始化列表。
}
protected override void OnOpen(Action _onOpenComplete)
protected override void OnOpen(object[] _args)
{
base.OnOpen(_onOpenComplete);
RefreshUI();
base.OnOpen(_args);
// 注册事件并刷新本次显示数据。
}
private void OnBtnClose(GameObject go) => CloseWindow();
private void RefreshUI() { }
}
```
### 1.2 生命周期
| 方法 | 调用时机 | 推荐操作 |
|-----|---------|---------|
| `OnRunAwake` | 创建时一次 | 绑定 UI 事件、初始化滚动列表 `BindEvent` |
| `OnOpen` | 每次打开 | 注册 TC 事件、调用 `ShowView()` 刷新数据 |
| `OnClose` | 每次关闭 | 取消 TC 事件 |
**关键原则**
- **绑定类操作**(如 `BindEvent`)放在 `OnRunAwake`,只需执行一次
- **刷新类操作**(如 `ShowView`)放在 `OnOpen`,每次打开都需执行
### 1.3 窗口操作
```csharp
// 打开
HFFunCatalogue.UIManager.OpenWindow<MyWindow>();
// 关闭
HFFunCatalogue.UIManager.CloseWindow<MyWindow>();
```
---
## 二、事件绑定
| 事件类型 | 绑定位置 | 是否需要 `-=` |
|---------|---------|--------------|
| UI 按钮/拖拽 | `OnRunAwake` | 不需要 |
| 全局 TC 事件 | `OnOpen`/`OnClose` | 需要 |
---
## 三、常用组件
### 3.1 按钮
```csharp
SFUI_Button btn = GetComponent<SFUI_Button>();
btn.OnAddClickListener += OnBtnClick;
btn.SetSelfGray(true);
```
### 3.2 文本
```csharp
SFUI_TextMeshProUGUI txt = GetComponent<SFUI_TextMeshProUGUI>();
txt.text = "Hello";
```
### 3.3 图片
```csharp
SFUI_Image img = GetComponent<SFUI_Image>();
img.HFSetSprite(/* atlasName */, "Icon_Player", /* languageRoot */);
```
### 3.4 滚动列表(核心)
**生命周期正确划分示例**
```csharp
private SFUI_ScrollRect m_ScrollList;
private List<ItemData> m_ItemList;
protected override void OnRunAwake()
{
base.OnRunAwake();
m_ScrollList = FindUICtrlByName<SFUI_ScrollRect>("ScrollList");
// ✅ 绑定事件放在 Awake一次绑定永久生效
m_ScrollList.BindEvent(
() => m_ItemList.Count, // 数据源闭包
(idx) => itemPrefab.gameObject, // 生成预制体
OnUpdateItem, // 更新回调
(item) => item.gameObject.HFGetOrAddSimulateBehaviour<ItemSlot>() // 获取组件
);
}
protected override void OnOpen(Action _onOpenComplete)
{
base.OnOpen(_onOpenComplete);
m_ItemList = GetDataFromServer(); // 获取最新数据
m_ScrollList.ShowView(); // ✅ 刷新显示放在 Open
}
private void OnUpdateItem(int index, AbsHFUIWindow window)
{
ItemSlot slot = window as ItemSlot;
slot.SetData(m_ItemList[index]);
}
```
**设计要点**
- `BindEvent``OnRunAwake` 中执行,建立绑定机制
- `ShowView``OnOpen` 中执行,触发数据刷新
- 数据源通过闭包引用,`ShowView` 时自动获取最新数据
---
## 四、对象池
| 场景 | 推荐方式 |
|-----|---------|
| 滚动列表 | `SFUI_ScrollRect.BindEvent`(内置池) |
| 固定栏(装备/技能) | `HFSimulateBehaviourPool<T>` |
---
## 五、子组件
### 5.1 多态基类
```csharp
public abstract class AbsListItem<T> : AbsHFUIWindow
{
protected SFUI_Button m_BtnItem;
protected Action<T> m_OnSelect;
protected override void OnRunAwake()
protected override void OnClose()
{
base.OnRunAwake();
m_BtnItem = gameObject.GetComponent<SFUI_Button>();
m_BtnItem.OnAddClickListener += OnBtnClick;
// 移除事件和计时器。
base.OnClose();
}
public virtual void SetData(T data, Action<T> onSelect = null)
{
m_OnSelect = onSelect;
OnDataChanged(data);
}
protected abstract void OnDataChanged(T data);
private void OnBtnClick(GameObject go) => m_OnSelect?.Invoke(m_CurrentData);
}
// 子类
public class CardItem : AbsListItem<CardData>
{
protected override void OnDataChanged(CardData data) { }
}
```
---
业务打开窗口使用:
## 六、开发规范
```csharp
HFFunCatalogue.UIManager.OpenWindow<ExampleWindow>();
```
### 6.1 文件路径
不要直接实例化窗口 Prefab也不要直接调用内部 `HFUIWindowMgr` 单例。
#### 6.1.1 核心资源路径
## 二、生命周期规则
| 类型 | 路径 | 说明 |
|-----|------|------|
| **窗口脚本** | `Assets/Game/GameHFScripte/GameFunction/UIWindows/{WindowName}/` | 窗口逻辑脚本存放目录 |
| **窗口预制体** | `Assets/Game/GameHFResource/CN/UIWindows/{WindowName}/` | UI 窗口预制体存放目录 |
| **图标图集** | `Assets/Game/GameHFResource/CN/SpriteAtlas/{AtlasName}/` | SpriteAtlas 图集资源 |
| **字体资源** | `Assets/Game/GameHFResource/CN/Fonts/` | 字体文件存放目录 |
- `OnRunAwake`:实例首次绑定时执行一次,适合控件查找、按钮绑定和虚拟列表初始化。
- `OnOpen`:每次打开执行,适合注册事件、应用参数和刷新数据数量。
- `OnClose`:每次关闭执行,与 `OnOpen` 中的监听、计时器和临时状态对称清理。
- 列表初始化不能依赖数据已经到达;先以数量 `0` 初始化,数据准备后再刷新数量。
#### 6.1.2 工具脚本路径
## 三、单列虚拟列表
| 工具名称 | 路径 | 职责 |
|---------|------|------|
| **UIPrefabGenerator** | `Assets/Editor/Command/UIPrefabGenerator.cs` | JSON 转 UI 预制体 |
| **UIPrefabToJson** | `Assets/Editor/Command/UIPrefabToJson.cs` | 预制体转 JSON |
| **RemoteCommand** | `Assets/Editor/Command/RemoteCommand.cs` | 外部命令接口 |
| **ExternalCommandListener** | `Assets/Editor/Command/ExternalCommandListener.cs` | 外部命令监听器 |
单列或单行滚动使用 LoopListView2 包装接口:
#### 6.1.3 文档路径
```csharp
m_List.SetLoopListViewItemPrefabs(
new GameObject[] { m_ItemTemplate.gameObject }, 0, 1);
| 文档名称 | 路径 |
|---------|------|
| UI 开发指南 | [UI窗口系统/开发指南.md](开发指南.md) |
| UI 界面拼接设计指南 | [UI窗口系统/界面拼接.md](界面拼接.md) |
m_List.InitListView(
0,
OnGetItem,
OnCreateItem,
OnUpdateItem,
OnGetPrefabIndex,
false);
```
#### 6.1.4 命令触发路径
数据准备或变化后:
- **触发文件**: `{ProjectRoot}/Trigger_Command.txt`
- **JSON 配置**: `Assets/Editor/UI/{WindowName}.json`
```csharp
m_List.RefreshDataTotalCount(m_DataList == null ? 0 : m_DataList.Count);
```
### 6.2 性能优化
- 使用对象池复用 UI 元素
- 将图标打包到 SpriteAtlas
- 滚动列表用 `SFUI_ScrollRect.BindEvent`
回调职责:
---
| 回调 | 职责 |
|---|---|
| `OnGetItem` | 从已创建节点取得 `SFUI_ScrollRectItem` |
| `OnCreateItem` | 首次创建绑定类和 SimulateBehaviour |
| `OnUpdateItem` | 根据 dataIndex 更新显示,不能假设旧数据仍有效 |
| `OnGetPrefabIndex` | 多模板时返回模板索引,单模板返回 `0` |
**版本**: v1.1
**适用**: StrayFog 框架 UI 开发人员
垂直列表模板通常使用左上锚点和 pivot `(0, 1)`,宽度与 viewport 匹配;水平列表按滚动方向设置。模板尺寸或 pivot 错误会造成重叠、偏移或不可见。
---
## 四、多列虚拟列表
存在固定列数、`GridLayoutGroup` 语义或二维排布时使用 LoopGridView
```csharp
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);
```
刷新数量:
```csharp
m_Grid.RefreshGridDataTotalCount(m_DataList == null ? 0 : m_DataList.Count);
```
不要通过单列 ListView 模拟多列布局。Grid 的 itemSize、spacing、padding 和列数必须与 Prefab 视觉布局一致。
## 五、Prefab 设置
- 模板节点保持 `SetActive(false)`,但必须位于窗口层级中以便引用收集。
- `SFUI_ScrollRect`、viewport、content 和模板引用必须完整。
- 虚拟列表初始化会关闭旧 `LayoutGroup``ContentSizeFitter`,运行时布局由 SuperScrollView 管理。
- 不要因为这些旧组件在运行时被关闭就重新勾选或依赖它们计算虚拟列表尺寸。
- 检查模板上的 `LoopListViewItem2``LoopGridViewItem`;包装层会补组件,但 Prefab 结构仍需正确。
- UIMono 收集需要包含 inactive 子节点,不能只扫描激活节点。
## 六、验证清单
1. 构建 `SFMono.csproj` 和涉及的 `GameHF.csproj`
2. 刷新 Unity Assets 并读取 Console。
3. 检查 Prefab 的 viewport、content、模板、RectTransform 和组件启用状态。
4. 验证空数据、1 个元素、超出视口、多次刷新和重复打开。
5. 滚动后验证元素回收与复用,没有 `KeyNotFoundException`、旧数据显示或重复创建。
6. 多列列表验证最后一行不足列数时的尺寸和定位。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [UI 窗口系统入口](README.md) | 本文件是 UI 窗口系统模块的子文档 |
| [UI 界面拼接设计指南](界面拼接.md) | 界面拼接是 UI 开发的补充设计指南 |
| [UI 拼接工具](../编辑器工具/UI拼接工具.md) | JSON 驱动 UI 生成工具说明 |
| [事件系统](../事件系统/README.md) | UI 事件订阅和释放规范 |
| [对象池与运行时对象管理](../内存对象管理/README.md) | UI 元素复用相关 |
| [资源管理](../资源管理/README.md) | UI 资源加载依赖资源管理 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | UI 命名与编码规范互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| [UI 窗口系统](README.md) | 窗口框架入口与生命周期 |
| [界面拼接](界面拼接.md) | Prefab 结构和视觉实现 |
| [UI 拼接工具](../编辑器工具/UI拼接工具.md) | JSON/Prefab 双向转换 |
| [开发规则](../../../rules/20-开发规则.md) | 通用 UI 和 C# 规则 |