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. 【Interaction 阶段】- 用户交互 ║
║ UIWindow.Back(data) // 处理返回键(公共接口,不可重写) ║
║ └─ *OnBack(data) // 返回键处理逻辑(虚方法,可重写) ║
║ └─ BackStrategy.HandleBack() // 默认使用策略模式 ║
║ ├─ None: 不响应,向下传递 ║
║ ├─ CloseSelf: 关闭当前窗口 ║
║ ├─ DoNothing: 拦截不操作 ║
║ └─ ShowExitConfirm: 显示退出确认 ║
║ ║
║ 7. 【Close 阶段】- 关闭 ║
║ UIWindow.CloseWindow(destroy) // 关闭窗口(公共接口,不可重写) ║
║ ├─ *OnHide() // 关闭回调 ║
║ ├─ SetActive(false) // 隐藏GameObject ║
║ └─ 发送UI隐藏事件 // EventManager.Send ║
║ ║
║ 8. 【Destroy 阶段】- 销毁 ║
║ UIWindow.CloseWindow(true) // 关闭并销毁窗口 ║
║ └─ DoDestroy() // 内部销毁逻辑 ║
║ ├─ *OnHide() // 关闭回调(如果还未调用) ║
║ ├─ *OnRelease() // 释放资源,清理引用 ║
║ ├─ DoRelease() // 基础资源释放 ║
║ ├─ Destroy(gameObject) // 销毁GameObject ║
║ └─ 卸载预制体资源 // AssetManager.Unload ║
║ ║
║ 【架构设计原则】 ║
║ • 🔒 接口保护: 公共接口方法不可重写,确保框架逻辑完整性 ║
║ • 🎯 职责分离: Init和Show独立调用,Show负责显示配置,Refresh负责内容更新 ║
║ • ⚡ 性能优化: 支持预初始化窗口,避免显示时的初始化卡顿 ║
║ • 🛡️ 异常安全: 所有公共接口都有异常处理和状态检查 ║
║ • 📋 模板方法: 使用模板方法模式,框架控制流程,子类实现细节 ║
║ ║
║ 【使用方式】 ║
║ • 初始化: UIManager.CreateWindow() → Init(data) → ShowWindow(data) ║
║ • 数据更新: 调用 Refresh(data) 更新已显示窗口的内容 ║
║ • 返回处理: 按下ESC/返回键 → Back() → OnBack() → BackStrategy处理 ║
║ • 关闭显示: CloseWindow(false) 关闭窗口(保留内存) / ShowWindow() 显示窗口 ║
║ • 销毁释放: CloseWindow(destroy: true) 完整销毁窗口和资源 ║
║ ║
║ 【子类实现指南 - 标记为 * 的虚方法】 ║
║ ║
║ 🔸 *OnBeforeInit(data) - 【不推荐】初始化预处理 ║
║ • 调用时机:DoInit和OnInit之前 ║
║ • 实现用途:解析传入数据,设置初始状态 ║
║ • ⚠️ 不建议使用:容易造成逻辑分散,建议统一在OnInit中处理 ║
║ • 替代方案:在OnInit()中处理所有初始化逻辑 ║
║ ║
║ 🔸 *OnInit() - 【推荐】UI组件初始化 ║
║ • 调用时机:DoInit之后,窗口显示之前 ║
║ • 实现用途:初始化UI组件,绑定事件,设置默认值 ║
║ • 典型示例:按钮事件绑定,列表组件初始化,默认文本设置 ║
║ • 注意事项:此时GameObject已创建,可安全访问UI组件 ║
║ ║
║ 🔸 *AutoBindField() - 【自动】组件绑定 ║
║ • 调用时机:DoInit过程中自动调用 ║
║ • 实现用途:自动绑定UI组件到字段 ║
║ • 注意事项:通常由代码生成工具处理,不建议手动重写 ║
║ ║
║ 🔸 *OnBeforeShow(data) - 【可选】显示前验证 ║
║ • 调用时机:DoShow和OnShow之前 ║
║ • 实现用途:显示前的条件检查和验证 ║
║ • 返回值:返回false可以取消本次显示操作 ║
║ • 使用场景:权限检查,前置条件验证 ║
║ ║
║ 🔸 *OnShow(data) - 【不推荐】显示时配置 ║
║ • 调用时机:窗口显示过程中,SetActive之前 ║
║ • 实现用途:设置显示配置,处理显示参数 ║
║ • 典型示例:设置窗口模式,配置显示状态,处理显示参数 ║
║ • ⚠️ 已废弃:建议使用OnAfterShow替代 ║
║ ║
║ 🔸 *OnAfterShow(data) - 【推荐】显示后处理 ║
║ • 调用时机:窗口完全显示后,SetActive之后 ║
║ • 实现用途:启动后续逻辑,播放动画效果 ║
║ • 典型示例:播放入场动画,开始计时器,发送显示事件 ║
║ ║
║ 🔸 *OnUpdate(dt) - 【可选】逐帧更新 ║
║ • 调用时机:每帧调用(仅在窗口激活时) ║
║ • 实现用途:处理需要逐帧更新的逻辑 ║
║ • 典型示例:倒计时更新,进度条刷新,动画状态更新 ║
║ • 性能注意:避免重度计算,优先使用事件驱动 ║
║ ║
║ 🔸 *OnRefresh(data) - 【推荐】内容数据刷新 ║
║ • 调用时机:外部调用Refresh(data)时触发 ║
║ • 实现用途:更新窗口显示内容,刷新数据绑定 ║
║ • 典型示例:刷新列表数据,更新文本内容,重新计算显示 ║
║ • 职责边界:负责内容更新,不负责显示配置(由Show处理) ║
║ • 调用规范:只能通过Refresh(data)公共接口间接调用 ║
║ ║
║ 🔸 *OnBack(data) - 【可选】返回键处理 ║
║ • 调用时机:Android返回键或ESC键按下时,由Back()公共接口调用 ║
║ • 实现用途:自定义返回键行为 ║
║ • 返回值:true表示已处理,false表示未处理(继续向下层窗口传递) ║
║ • 典型示例:显示确认对话框,保存状态后关闭,自定义返回逻辑 ║
║ • 默认行为:通过BackStrategy配置(None/CloseSelf/DoNothing/ShowExitConfirm) ║
║ • 注意事项:如果重写,可以选择调用base.OnBack()使用默认策略,或完全自定义 ║
║ • 异常处理:由Back()公共接口统一处理异常 ║
║ ║
║ 🔸 *OnHide() - 【可选】隐藏时清理 ║
║ • 调用时机:窗口隐藏或销毁时 ║
║ • 实现用途:暂停逻辑,保存状态,停止动画 ║
║ • 典型示例:暂停计时器,保存用户输入,停止音效播放 ║
║ ║
║ 🔸 *OnRelease() - 【重要】资源释放 ║
║ • 调用时机:窗口销毁时,OnHide之后 ║
║ • 实现用途:释放资源,取消订阅,清理引用 ║
║ • 典型示例:取消事件监听,释放Tween动画,清理对象池,断开网络连接 ║
║ • 重要性:防止内存泄漏的关键环节 ║
║ ║
║ 【关键约束和最佳实践】 ║
║ ║
║ 🚫 【禁止行为】 ║
║ • 不要重写公共接口方法(Init, ShowWindow, Update, Refresh, Back, CloseWindow等) ║
║ • 不要直接调用OnXxx虚方法,必须通过对应的公共接口 ║
║ • 不要在OnXxx方法中调用base.OnXxx()(基类实现通常为空或默认逻辑) ║
║ ║
║ ✅ 【推荐做法】 ║
║ • 统一在OnInit中处理初始化,避免使用OnBeforeInit ║
║ • 明确区分Show(显示配置)和Refresh(内容更新)的职责 ║
║ • 在OnRelease中完整清理资源,防止内存泄漏 ║
║ • 优先使用事件驱动,减少OnUpdate中的重度计算 ║
║ • 通过公共接口调用:Init(data), ShowWindow(data), Refresh(data), Back(data) 等 ║
║ • 返回键行为优先通过BackStrategy配置,只在特殊需求时重写OnBack ║
║ • 重写OnBack时,优先考虑调用base.OnBack()保留默认策略行为 ║
║ ║
║ 💡 【设计理念】 ║
║ 框架采用"模板方法模式",公共接口控制执行流程和异常处理, ║
║ 虚方法提供扩展点供子类实现具体业务逻辑,确保框架稳定性和扩展性的平衡。 ║
║ ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
*/
#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}\n===Stack===\n{2}\n======", WindowName, e.Message, e.StackTrace);
}
}
///
/// 显示窗口
///
/// 显示数据
public void ShowWindow(object data = null)
{
try
{
if (_isShow)
{
DebugUtil.LogWarning("window : {0} is show, can't show again", WindowName);
}
// 🎯 在显示前截图(如果需要)
CaptureScreenshotIfNeeded();
// 🎯 自动设置模糊背景(如果需要)
SetupBlurBackgroundIfNeeded();
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;
OnAfterShow(data);
bool isShowOpenAnim = IsShowOpenAnim;
if (isShowOpenAnim)
{
AnimShow(() =>
{
//TODO
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}\n===Stack===\n{2}\n======", WindowName, e.Message, e.StackTrace);
}
}
///
/// 在显示前截图(如果窗口配置需要截图)
///
private void CaptureScreenshotIfNeeded()
{
var config = UIManager.Instance.GetWindowInfo(WindowName);
if (config.NeedScreenshot)
{
ScreenshotManager.Instance.CaptureScreenshot();
}
}
///
/// 自动设置模糊背景(如果窗口配置需要截图)
///
private void SetupBlurBackgroundIfNeeded()
{
var config = UIManager.Instance.GetWindowInfo(WindowName);
if (!config.NeedScreenshot) return;
// 查找约定的 GaussianBlurMask 节点
var blurTransform = transform.Find("GaussianBlurMask");
if (blurTransform != null)
{
var blurMask = blurTransform.GetComponent();
if (blurMask != null)
{
blurMask.texture = ScreenshotManager.Instance.GetScreenTexture2D();
blurMask.gameObject.SetActive(true);
}
}
}
///
/// 更新入口
///
public void Update(float dt)
{
try
{
_notchScreenAdapter?.UpdateCheck();
OnUpdate(dt);
}
catch (System.Exception e)
{
DebugUtil.LogError("window : {0} update window error: {1}\n===Stack===\n{2}\n======", WindowName, e.Message, e.StackTrace);
}
}
///
/// 刷新窗口数据
///
/// 新数据
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}\n===Stack===\n{2}\n======", WindowName, e.Message, e.StackTrace);
}
}
///
/// 关闭窗口
///
/// 参数说明:
/// - destroy = false: 关闭但保留在内存中(快速重开,如背包、角色面板)
/// - destroy = true: 关闭并销毁,释放资源(不再需要的窗口)
///
/// 行为:
/// 1. 恢复主相机状态
/// 2. 处理导航栈移除(由栈管理器根据配置决定)
/// 3. 恢复下层窗口可见性(由可见性管理器根据配置决定)
/// 4. 调用 OnHide() 生命周期
/// 5. 隐藏窗口渲染(GameObject.SetActive(false))
/// 6. 修改逻辑状态(_isShow = false)
/// 7. 发送 UIHide 事件
/// 8. 播放关闭动画(如果配置了)
/// 9. 如果 destroy = true,销毁 GameObject 并释放资源
///
/// 注意:
/// - 如果窗口已关闭(_isShow = false)且 destroy = false:跳过
/// - 如果窗口已关闭(_isShow = false)且 destroy = true:跳过关闭逻辑,但仍执行销毁
///
/// 是否销毁窗口
public async void CloseWindow(bool destroy)
{
try
{
// 如果窗口已关闭
if (!_isShow)
{
// 如果不需要销毁,直接返回
if (!destroy)
{
DebugUtil.LogWarning("window : {0} is already closed", WindowName);
return;
}
// 如果需要销毁,跳过关闭逻辑,直接执行销毁
DebugUtil.LogG($"窗口已关闭,直接销毁UI:{WindowName}");
IsClosing = true;
UIManager.Instance.RemoveWindow(mWindowName);
DoDestroy();
IsClosing = false;
return;
}
IsClosing = true;
// 1. 恢复主相机
RestoreMainCamera();
// 2. 获取窗口配置
var config = UIManager.Instance.GetWindowInfo(WindowName);
// 3. 可见性管理(由 UIWindowVisibilityManager 根据配置决定是否处理)
UIWindowVisibilityManager.Instance.HandleVisibilityOnClose(this, config.VisibilityStrategy);
// 4. 隐藏逻辑
OnHide();
gameObject?.SetActive(false);
_isShow = false;
EventManager.Instance.Send(EventManager.EventName.UIHide, WindowName);
// 5. 播放关闭动画
bool isShowCloseAnim = IsShowOpenAnim;
if (isShowCloseAnim)
{
UIManager.Instance.BlockUIForCloseOperation();
await AnimHide(() =>
{
// 动画播放完毕后的回调
if (destroy)
{
DoDestroy();
UIManager.Instance.RemoveWindow(mWindowName);
}
IsClosing = false;
});
}
else
{
// 7. 如果需要销毁(无动画情况)
if (destroy)
{
DoDestroy();
UIManager.Instance.RemoveWindow(mWindowName);
}
IsClosing = false;
}
}
catch (System.Exception e)
{
DebugUtil.LogError("window : {0} close window error: {1}\n===Stack===\n{2}\n======", WindowName, e.Message, e.StackTrace);
}
}
#endregion
#region User Interaction
///
/// 处理返回键(公共接口,不可重写)
/// 框架会按照从上到下的顺序调用所有可见窗口的Back()方法,
/// 直到某个窗口处理并返回true为止
///
/// 是否成功处理返回键(true表示已处理,false表示未处理,继续向下传递)
public async UniTask Back()
{
try
{
return await OnBack();
}
catch (System.Exception e)
{
DebugUtil.LogError("window : {0} back error: {1}\n===Stack===\n{2}\n======", WindowName, e.Message, e.StackTrace);
return false;
}
}
///
/// 返回键处理(虚方法,子类可重写)
/// 默认通过返回策略模式处理
///
/// 是否成功处理返回键(true表示已处理,false表示未处理,继续向下传递)
protected virtual async UniTask OnBack()
{
if (!GraphicRaycasterManager.IsEnableGraphicRaycaster)
return true;
// 使用策略模式处理返回键
if (BackStrategy != null)
{
return await BackStrategy.HandleBack(this);
}
return false;
}
#endregion
#region Internal Implementation
///
/// 恢复主相机状态
///
private void RestoreMainCamera()
{
var config = UIManager.Instance.GetWindowInfo(WindowName);
if (config.VisibilityStrategy == UIVisibilityStrategy.Occlusion && _shutMainCamera)
{
if (!CameraManager.Instance.CheckMainCameraIsActive())
{
CameraManager.Instance.SetMainCameraActive(true);
}
}
}
///
/// 执行基础初始化
///
private void DoInit()
{
_isShow = false;
var metaInfo = UIManager.Instance.GetWindowInfo(WindowName);
_shutMainCamera = metaInfo.ShutMainCamera;
IsShowOpenAnim = metaInfo.IsAutoAnim;
BackStrategy = UIBackStrategyFactory.CreateStrategy(metaInfo.BackStrategyType);
AutoBindField();
InitScreenAdaption();
BindDefaultCloseButtons();
BindDefaultHomeButtons();
BindDefaultBackButtons();
}
///
/// 执行显示逻辑
///
private void DoShow()
{
if (UIManager.Instance.WindowInfos.TryGetValue(WindowName, out var windowInfo))
{
if (CameraManager.Instance.CheckMainCameraIsActive())
{
if (windowInfo.VisibilityStrategy == UIVisibilityStrategy.Occlusion)
{
// 特殊处理主界面
if (_shutMainCamera)
{
CameraManager.Instance.SetMainCameraActive(false);
}
}
}
}
}
///
/// 执行销毁逻辑(内部方法)
///
private void DoDestroy()
{
DebugUtil.LogG($"DoDestroy:{WindowName}");
// 如果窗口还在显示状态,先调用 OnHide
if (_isShow)
{
OnHide();
_isShow = false;
}
// 确保 GameObject 隐藏
if (gameObject != null)
{
gameObject.SetActive(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;
}
#endregion
#region Virtual Methods
// Init Phase
///
/// 初始化前预处理(不推荐使用,建议在OnInit中处理)
///
[System.Obsolete("OnBeforeInit已废弃,请使用OnInit方法替代。OnInit提供更清晰的初始化流程和更好的代码组织。")]
protected virtual void OnBeforeInit(object data = null){}
///
/// UI初始化
///
protected virtual void OnInit(){}
///
/// 自动绑定字段
///
protected virtual void AutoBindField(){}
// Show Phase
///
/// 显示前检查
///
protected virtual bool OnBeforeShow(object data)
{
return true;
}
///
/// 显示时处理
///
[System.Obsolete("OnShow已废弃,请使用OnAfterShow方法替代。OnAfterShow在窗口完全显示后调用,提供更稳定的显示时机。")]
protected virtual void OnShow(object data){}
///
/// 显示后处理
///
protected virtual void OnAfterShow(object data){}
///
/// 每帧更新
///
protected virtual void OnUpdate(float dt){}
// Refresh Phase
///
/// 数据刷新
///
protected virtual void OnRefresh(object data) {}
// Hide Phase
///
/// 隐藏时处理
///
protected virtual void OnHide(){}
// Release Phase
///
/// 资源释放
///
protected virtual void OnRelease(){}
#endregion
}