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 }