using Cysharp.Threading.Tasks;
using DG.Tweening;
using Framework;
using PhxhSDK;
using UnityEngine.UI;
public abstract partial class UIWindow
{
/*
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ UIWindow 生命周期 (Lifecycle) ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ 1. 【Load 阶段】- 资源加载 ║
║ UIManager.CreateWindow() ║
║ ├─ 加载预制体资源 ║
║ ├─ 实例化GameObject ║
║ └─ 创建UIWindow实例 ║
║ ║
║ 2. 【Init 阶段】- 初始化 ║
║ UIWindow.Init(data) // 窗口初始化(公共接口,不可重写) ║
║ ├─ DoInit() // 基础初始化 ║
║ │ ├─ *AutoBindField() // 绑定UI组件引用 ║
║ │ ├─ InitScreenAdaption() // 屏幕适配 ║
║ │ └─ _TryBindBgClose() // 绑定背景关闭按钮 ║
║ └─ *OnInit() // 初始化UI状态,设置默认值 ║
║ ║
║ 3. 【Show 阶段】- 显示 ║
║ UIWindow.ShowWindow(data) // 显示窗口(公共接口,不可重写) ║
║ ├─ *OnBeforeShow(data) // 显示前检查 ║
║ ├─ DoShow() // 基础显示逻辑 ║
║ ├─ SetActive(true) // 激活GameObject ║
║ ├─ *OnAfterShow(data) // 显示后处理 ║
║ └─ 发送UI打开事件 // EventManager.Send ║
║ ║
║ 4. 【Update 阶段】- 运行时更新 ║
║ UIWindow.Update(dt) // 更新入口(公共接口,不可重写) ║
║ └─ *OnUpdate(dt) // 每帧更新,处理UI逻辑 ║
║ ║
║ 5. 【Refresh 阶段】- 数据刷新 ║
║ UIWindow.Refresh(data) // 数据刷新(公共接口,不可重写) ║
║ └─ *OnRefresh(data) // 刷新UI内容,更新显示数据 ║
║ ║
║ 6. 【Hide 阶段】- 隐藏 ║
║ UIWindow.HideWindow() // 隐藏窗口(公共接口,不可重写) ║
║ ├─ *OnHide() // 隐藏回调 ║
║ ├─ SetActive(false) // 隐藏GameObject ║
║ └─ 发送UI隐藏事件 // EventManager.Send ║
║ ║
║ 7. 【Close 阶段】- 关闭销毁 ║
║ UIWindow.CloseWindow() // 关闭窗口(公共接口,不可重写) ║
║ └─ DestroyWindow() // 内部销毁逻辑 ║
║ ├─ *OnHide() // 隐藏回调 ║
║ ├─ *OnRelease() // 释放资源,清理引用 ║
║ ├─ DoRelease() // 基础资源释放 ║
║ ├─ Destroy(gameObject) // 销毁GameObject ║
║ └─ 卸载预制体资源 // AssetManager.Unload ║
║ ║
║ 【架构设计原则】 ║
║ • 🔒 接口保护: 公共接口方法不可重写,确保框架逻辑完整性 ║
║ • 🎯 职责分离: Init和Show独立调用,Show负责显示配置,Refresh负责内容更新 ║
║ • ⚡ 性能优化: 支持预初始化窗口,避免显示时的初始化卡顿 ║
║ • 🛡️ 异常安全: 所有公共接口都有异常处理和状态检查 ║
║ • 📋 模板方法: 使用模板方法模式,框架控制流程,子类实现细节 ║
║ ║
║ 【使用方式】 ║
║ • 初始化: UIManager.CreateWindow() → Init(data) → ShowWindow(data) ║
║ • 数据更新: 调用 Refresh(data) 更新已显示窗口的内容 ║
║ • 隐藏显示: HideWindow() / ShowWindow() 控制窗口可见性 ║
║ • 销毁释放: CloseWindow() 完整销毁窗口和资源 ║
║ ║
║ 【子类实现指南 - 标记为 * 的虚方法】 ║
║ ║
║ 🔸 *OnBeforeInit(data) - 【不推荐】初始化预处理 ║
║ • 调用时机:DoInit和OnInit之前 ║
║ • 实现用途:解析传入数据,设置初始状态 ║
║ • ⚠️ 不建议使用:容易造成逻辑分散,建议统一在OnInit中处理 ║
║ • 替代方案:在OnInit()中处理所有初始化逻辑 ║
║ ║
║ 🔸 *OnInit() - 【推荐】UI组件初始化 ║
║ • 调用时机:DoInit之后,窗口显示之前 ║
║ • 实现用途:初始化UI组件,绑定事件,设置默认值 ║
║ • 典型示例:按钮事件绑定,列表组件初始化,默认文本设置 ║
║ • 注意事项:此时GameObject已创建,可安全访问UI组件 ║
║ ║
║ 🔸 *AutoBindField() - 【自动】组件绑定 ║
║ • 调用时机:DoInit过程中自动调用 ║
║ • 实现用途:自动绑定UI组件到字段 ║
║ • 注意事项:通常由代码生成工具处理,不建议手动重写 ║
║ ║
║ 🔸 *OnBeforeShow(data) - 【可选】显示前验证 ║
║ • 调用时机:DoShow和OnShow之前 ║
║ • 实现用途:显示前的条件检查和验证 ║
║ • 返回值:返回false可以取消本次显示操作 ║
║ • 使用场景:权限检查,前置条件验证 ║
║ ║
║ 🔸 *OnShow(data) - 【不推荐】显示时配置 ║
║ • 调用时机:窗口显示过程中,SetActive之前 ║
║ • 实现用途:设置显示配置,处理显示参数 ║
║ • 典型示例:设置窗口模式,配置显示状态,处理显示参数 ║
║ • 职责边界:负责显示配置,不负责数据内容更新(由Refresh处理) ║
║ ║
║ 🔸 *OnAfterShow(data) - 【可选】显示后处理 ║
║ • 调用时机:窗口完全显示后,SetActive之后 ║
║ • 实现用途:启动后续逻辑,播放动画效果 ║
║ • 典型示例:播放入场动画,开始计时器,发送显示事件 ║
║ ║
║ 🔸 *OnUpdate(dt) - 【可选】逐帧更新 ║
║ • 调用时机:每帧调用(仅在窗口激活时) ║
║ • 实现用途:处理需要逐帧更新的逻辑 ║
║ • 典型示例:倒计时更新,进度条刷新,动画状态更新 ║
║ • 性能注意:避免重度计算,优先使用事件驱动 ║
║ ║
║ 🔸 *OnRefresh(data) - 【推荐】内容数据刷新 ║
║ • 调用时机:外部调用Refresh(data)时触发 ║
║ • 实现用途:更新窗口显示内容,刷新数据绑定 ║
║ • 典型示例:刷新列表数据,更新文本内容,重新计算显示 ║
║ • 职责边界:负责内容更新,不负责显示配置(由Show处理) ║
║ • 调用规范:只能通过Refresh(data)公共接口间接调用 ║
║ ║
║ 🔸 *OnHide() - 【可选】隐藏时清理 ║
║ • 调用时机:窗口隐藏或销毁时 ║
║ • 实现用途:暂停逻辑,保存状态,停止动画 ║
║ • 典型示例:暂停计时器,保存用户输入,停止音效播放 ║
║ ║
║ 🔸 *OnRelease() - 【重要】资源释放 ║
║ • 调用时机:窗口销毁时,OnHide之后 ║
║ • 实现用途:释放资源,取消订阅,清理引用 ║
║ • 典型示例:取消事件监听,释放Tween动画,清理对象池,断开网络连接 ║
║ • 重要性:防止内存泄漏的关键环节 ║
║ ║
║ 【关键约束和最佳实践】 ║
║ ║
║ 🚫 【禁止行为】 ║
║ • 不要重写公共接口方法(Init, ShowWindow, Refresh, HideWindow等) ║
║ • 不要直接调用OnXxx虚方法,必须通过对应的公共接口 ║
║ • 不要在OnXxx方法中调用base.OnXxx()(基类实现为空) ║
║ ║
║ ✅ 【推荐做法】 ║
║ • 统一在OnInit中处理初始化,避免使用OnBeforeInit ║
║ • 明确区分Show(显示配置)和Refresh(内容更新)的职责 ║
║ • 在OnRelease中完整清理资源,防止内存泄漏 ║
║ • 优先使用事件驱动,减少OnUpdate中的重度计算 ║
║ • 通过公共接口调用:Refresh(data)、ShowWindow(data)等 ║
║ ║
║ 💡 【设计理念】 ║
║ 框架采用"模板方法模式",公共接口控制执行流程和异常处理, ║
║ 虚方法提供扩展点供子类实现具体业务逻辑,确保框架稳定性和扩展性的平衡。 ║
║ ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
*/
#region Lifecycle Management
///
/// 初始化窗口
///
/// 初始化数据
public void Init(object data = null)
{
try
{
OnBeforeInit(data);
if (isInited)
{
DebugUtil.LogWarning("window : {0} already inited", WindowName);
}
else
{
DoInit();
OnInit();
isInited = true;
}
}
catch (System.Exception e)
{
DebugUtil.LogError("window : {0} init window error: {1}", WindowName, e.Message);
}
}
///
/// 显示窗口
///
/// 显示数据
public void ShowWindow(object data = null)
{
try
{
if (_isShow)
{
DebugUtil.LogWarning("window : {0} is show, can't show again", WindowName);
}
if (!OnBeforeShow(data))
{
DebugUtil.LogError("window : {0} on before show return false", WindowName);
return;
}
DoShow();
OnShow(data);
if (gameObject is null)
{
DebugUtil.Log("UI已被销毁:{0}", WindowName);
}
else
{
gameObject.SetActive(true);
}
_isShow = true;
bool isShowOpenAnim = IsShowOpenAnim;
if (isShowOpenAnim)
{
AnimShow(() =>
{
OnAfterShow(data);
EventManager.Instance.Send(EventManager.EventName.UIOpen, WindowName);
}).Forget();
}
else
{
OnAfterShow(data);
EventManager.Instance.Send(EventManager.EventName.UIOpen, WindowName);
}
}
catch (System.Exception e)
{
DebugUtil.LogError("window : {0} show window error: {1}", WindowName, e.Message);
}
}
///
/// 更新入口
///
public void Update(float dt)
{
try
{
_notchScreenAdapter?.UpdateCheck();
OnUpdate(dt);
}
catch (System.Exception e)
{
DebugUtil.LogError("window : {0} update window error: {1}", WindowName, e.Message);
}
}
///
/// 刷新窗口数据
///
/// 新数据
public void Refresh(object data)
{
try
{
if (isInited)
{
OnRefresh(data);
}
else
{
DebugUtil.LogError("window : {0} not inited", WindowName);
}
}
catch (System.Exception e)
{
DebugUtil.LogError("window : {0} refresh with data error: {1}", WindowName, e.Message);
}
}
///
/// 隐藏窗口
///
public void HideWindow()
{
if (!_isShow)
{
DebugUtil.LogWarning("window : {0} is hide, can't hide again", WindowName);
}
try
{
if (UIManager.Instance.WindowInfos.TryGetValue(WindowName, out var windowInfo))
{
if (windowInfo.UIOpenType == UIOpenType.FullScreen)
{
if (!CameraManager.Instance.CheckMainCameraIsActive())
{
CameraManager.Instance.SetMainCameraActive(true);
}
}
}
OnHide();
gameObject?.SetActive(false);
_isShow = false;
EventManager.Instance.Send(EventManager.EventName.UIHide, WindowName);
}
catch (System.Exception e)
{
DebugUtil.LogError("window : {0} hide window error: {1}", WindowName, e.Message);
}
}
///
/// 关闭窗口
///
/// 关闭类型
public async void CloseWindow(UIWindowCloseType closeType)
{
try
{
IsClosing = true;
DebugUtil.LogG($"关闭UI:{WindowName}, closeType{closeType}");
if (UIManager.Instance.CheckWindowIsFullScreen(this))
{
if (_shutMainCamera)
{
CameraManager.Instance.SetMainCameraActive(true);
}
}
if (closeType == UIWindowCloseType.HideAndDestroy)
{
var last = UIManager.Instance.GetLast();
if (last == this)
{
DebugUtil.LogG($"PopWindow{this.WindowName}");
UIManager.Instance.PopWindow();
}
if (UIManager.Instance.UiWindowStack.Count > 0)
{
if (UIManager.Instance.CheckWindowIsFullScreen(this))
{
var window = UIManager.Instance.GetLastFullScreen();
if (window != null && window != this)
{
if (UIManager.Instance.CheckIsWindowInStack(this))
{
UIManager.Instance.ForceRemoveWindow(this);
DebugUtil.LogG($"UI出栈,{WindowName}");
}
}
for (int i = UIManager.Instance.UiWindowStack.Count - 1; i >= 0; i--)
{
var latestWindow = UIManager.Instance.UiWindowStack[i];
latestWindow.ShowWindow();
if (UIManager.Instance.CheckWindowIsFullScreen(latestWindow))
break;
}
}
else
{
UIManager.Instance.ForceRemoveWindow(this);
}
}
bool isShowCloseAnim = IsShowOpenAnim;
if (isShowCloseAnim)
{
UIManager.Instance.BlockUIForCloseOperation();
await AnimHide(() =>
{
DestroyWindow();
UIManager.Instance.RemoveWindow(mWindowName);
IsClosing = false;
});
}
else
{
UIManager.Instance.RemoveWindow(mWindowName);
DestroyWindow();
IsClosing = false;
}
}
else if (closeType == UIWindowCloseType.ForceDestroy)
{
// 强制清除所有界面时使用
DestroyWindow();
UIManager.Instance.RemoveWindow(mWindowName);
IsClosing = false;
}
}
catch (System.Exception e)
{
DebugUtil.LogError("window : {0} close window error: {1}", WindowName, e.Message);
}
}
#endregion
#region Internal Implementation
///
/// 执行基础初始化
///
private void DoInit()
{
_isShow = false;
_shutMainCamera = UIManager.Instance.IsShutMainCamera(WindowName);
IsShowOpenAnim = UIManager.Instance.GetIsAutoPlayAnim(WindowName);
AutoBindField();
InitScreenAdaption();
_TryBindBgClose();
}
///
/// 执行显示逻辑
///
private void DoShow()
{
if (UIManager.Instance.WindowInfos.TryGetValue(WindowName, out var windowInfo))
{
if (CameraManager.Instance.CheckMainCameraIsActive())
{
if (windowInfo.UIOpenType == UIOpenType.FullScreen)
{
// 特殊处理主界面
if (_shutMainCamera && WindowName != UINameConst.UI_MainPanel)
{
CameraManager.Instance.SetMainCameraActive(false);
}
}
}
}
}
///
/// 销毁窗口
///
private void DestroyWindow()
{
DebugUtil.LogG($"DestroyWindow:{WindowName}");
OnHide();
gameObject?.SetActive(false);
_isShow = false;
EventManager.Instance.Send(EventManager.EventName.UIHide, WindowName);
if (gameObject)
{
if (isInited)
{
OnRelease();
DoRelease();
isInited = false;
}
if (UIManager.Instance.UIWindowObjDic.ContainsKey(gameObject))
{
UIManager.Instance.UIWindowObjDic.Remove(gameObject);
}
UnityEngine.Object.Destroy(gameObject);
}
AssetManager.Instance.Unload(string.Format(UIManager.UI_PREFAB_PATH, mWindowName), true);
gameObject = null;
}
///
/// 基础资源释放
///
private void DoRelease()
{
EventManager.Instance.Send(EventManager.EventName.UIDestroy, WindowName);
UnLoad();
_openAnimSeq?.Kill();
_assetReferenceManager?.Dispose();
_assetReferenceManager = null;
_notchScreenAdapter = null;
_graphicRaycasterManager = null;
}
///
/// 绑定背景关闭按钮
///
private void _TryBindBgClose()
{
var trans = transform.Find("BgClose");
if (trans)
{
var btn = trans.GetComponent