NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/Framework/UI/UIWindow.Lifecycle.cs

560 lines
33 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using Framework;
using PhxhSDK;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// UI窗口基类 - 生命周期管理部分
///
/// 功能概述:
/// - 窗口生命周期管理Init → Show → Update → Hide → Close
/// - 资源加载与释放管理
/// - 屏幕适配与相机控制
/// - 动画播放与UI事件处理
///
/// 设计特点:
/// - Init和Show分离设计支持窗口预初始化
/// - 完整的生命周期钩子函数,便于子类扩展
/// - 异常安全的显示/隐藏逻辑
/// - 自动资源管理和内存清理
/// </summary>
public abstract partial class UIWindow
{
/*
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ UIWindow 生命周期 (Lifecycle) ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ 1. 【Load 阶段】- 资源加载 ║
║ UIRoot.CreateWindow() ║
║ ├─ 加载预制体资源 ║
║ ├─ 实例化GameObject ║
║ └─ 创建UIWindow实例 ║
║ ║
║ 2. 【Init 阶段】- 初始化 ║
║ UIWindow.Init(data) // 窗口初始化(独立调用) ║
║ ├─ *OnBeforeInit(data) // 预处理数据 (原 PreLoad
║ ├─ DoInit() // 基础初始化 ║
║ │ ├─ AutoBindField() // 绑定UI组件引用 ║
║ │ ├─ InitScreenAdaption() // 屏幕适配 ║
║ │ └─ _TryBindBgClose() // 绑定通用事件 ║
║ └─ *OnInit() // 初始化UI状态设置默认值 ║
║ ║
║ 3. 【Show 阶段】- 显示 ║
║ UIWindow.ShowWindow(data) // 显示窗口(公共接口) ║
║ ├─ DoShow() // 处理相机状态等显示逻辑 ║
║ ├─ *OnShow(data) // 显示时回调,业务逻辑处理 ║
║ ├─ SetActive(true) // 激活GameObject ║
║ ├─ *OnAfterShow(data) // 显示后回调,最终处理 ║
║ └─ 发送UI打开事件 // EventManager.Send ║
║ 4. 【Update 阶段】- 运行时更新 ║
║ Update(dt) -> *OnUpdate(dt) // 每帧更新处理UI逻辑合并原 Update
║ ║
║ 5. 【Refresh 阶段】- 数据刷新 ║
║ RefreshWithData(data) // 数据刷新(原 ReloadWindow
║ └─ *OnDataRefresh(data) // 刷新UI内容更新显示数据原 OnReloadWindow
║ ║
║ 6. 【Hide 阶段】- 隐藏 ║
║ HideWindow() -> Hide() ║
║ ├─ SetActive(false) // 隐藏GameObject ║
║ └─ *OnHide() // 隐藏回调 ║
║ ║
║ 7. 【Close 阶段】- 关闭销毁 ║
║ CloseWindow() -> DestroyWindow() ║
║ ├─ *OnHide() // 隐藏回调 ║
║ ├─ *OnRelease() // 释放资源,清理引用 ║
║ ├─ Destroy(gameObject) // 销毁GameObject ║
║ └─ 卸载资源 ║
║ ║
║ 【当前架构特点】 ║
║ • 分离式设计: Init和Show独立调用支持预初始化窗口 ║
║ • 使用方式: 先调用Init(data)初始化再调用ShowWindow(data)显示 ║
║ • 灵活性: 可以先初始化多个窗口,根据需要选择性显示 ║
║ • 性能优化: 避免显示时的初始化卡顿,提升用户体验 ║
║ ║
║ 【子类需要关注的虚函数 - 标记为 * 的函数】 ║
║ ║
║ 🔸 *OnBeforeInit(data) - 【可选重写】数据预处理 ║
║ • 时机:在 DoInitAutoBindField、InitScreenAdaption和 OnInit 之前调用 ║
║ • 用途解析传入数据设置初始状态准备UI显示内容 ║
║ • 示例解析OpenData设置默认选中项预加载必要资源 ║
║ • 注意:不需要调用 base.OnBeforeInit() ║
║ ║
║ 🔸 *OnInit() - 【常用重写】UI初始化 ║
║ • 时机:在 DoInit基础初始化之后窗口显示之前调用 ║
║ • 用途初始化UI组件绑定事件设置默认值 ║
║ • 示例:按钮绑定,列表初始化,默认文本设置 ║
║ • 注意:不需要调用 base.OnInit() ║
║ ║
║ 🔸 *OnShow(data) - 【常用重写】显示时业务逻辑 ║
║ • 时机窗口显示过程中调用在SetActive之前 ║
║ • 用途处理显示时的主要业务逻辑数据刷新UI更新 ║
║ • 示例刷新列表数据更新UI显示准备动画数据 ║
║ • 注意:不需要调用 base.OnShow(data) ║
║ ║
║ 🔸 *OnAfterShow(data) - 【按需重写】显示后最终处理 ║
║ • 时机窗口完全显示后调用在SetActive之后 ║
║ • 用途:处理显示完成后的后续逻辑,启动动画,开始计时 ║
║ • 示例:播放入场动画,开始倒计时,发送显示完成事件 ║
║ • 注意:不需要调用 base.OnAfterShow(data) ║
║ ║
║ 🔸 *OnUpdate(dt) - 【按需重写】每帧更新 ║
║ • 时机:每帧调用(仅在窗口激活且已创建时) ║
║ • 用途:处理需要持续更新的逻辑 ║
║ • 示例:倒计时更新,进度条刷新,实时数据显示 ║
║ • 注意:避免在此进行重度计算,影响性能 ,不需要调用 base.OnUpdate(dt) ║
║ ║
║ 🔸 *OnDataRefresh(data) - 【按需重写】数据刷新 ║
║ • 时机:调用 RefreshWithData 时触发 ║
║ • 用途:更新已显示窗口的内容,不重新初始化 ║
║ • 示例:更新商品列表,刷新玩家信息,切换显示内容 ║
║ • 注意:通常不需要调用 base.OnDataRefresh(data) ║
║ ║
║ 🔸 *OnHide() - 【按需重写】隐藏时清理 ║
║ • 时机:窗口隐藏时调用 ║
║ • 用途:暂停逻辑,保存状态,停止动画 ║
║ • 示例:暂停倒计时,保存输入内容,停止音效 ║
║ • 注意:不需要调用 base.OnHide() ║
║ ║
║ 🔸 *OnRelease() - 【重要重写】资源释放 ║
║ • 时机:窗口销毁时调用 ║
║ • 用途:释放资源,取消订阅,清理引用 ║
║ • 示例取消事件监听释放Tween清理对象池 ║
║ • 注意:不需要调用 base.OnRelease() ║
║ ║
║ 【其他说明】 ║
║ • AutoBindField: 自动绑定字段,由代码生成工具处理,不要手动重写 ║
║ • RefreshWithData: 数据刷新函数窗口已初始化时调用OnDataRefresh未初始化时报错 ║
║ • Init: 初始化函数确保窗口只初始化一次包含OnBeforeInit、DoInit、OnInit的完整流程 ║
║ • ShowWindow: 显示窗口的公共接口内部调用私有的Show函数执行实际显示逻辑 ║
║ • 架构特点: Init和Show分离支持窗口预初始化然后在需要时显示 ║
║ • 重写虚函数时,不需要调用 base.OnXxx(),具体见上述说明 ║
║ ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
*/
#region Private Helper Methods
/// <summary>
/// 绑定背景关闭按钮
/// </summary>
private void _TryBindBgClose()
{
var trans = transform.Find("BgClose");
if (trans)
{
var btn = trans.GetComponent<Button>();
if (btn)
{
BindButton(btn, () =>
{
var clickAudio = btn.GetComponent<ClickAudio>();
if (clickAudio)
{
if (clickAudio.IsPlayAudio && !string.IsNullOrEmpty(clickAudio.AudioKey))
{
AudioManager.Instance.PlayAudio(clickAudio.AudioKey);
}
else
{
CommonUtilsFramework.PlayUICommonSound();
}
}
if (_onBgCloseFunc == null)
{
CloseWindow(UIWindowCloseType.HideAndDestroy);
return;
}
_onBgCloseFunc?.Invoke();
});
}
}
}
#endregion
#region Lifecycle Management
/// <summary>
/// 初始化窗口
/// </summary>
/// <param name="data">初始化数据</param>
public void Init(object data = null)
{
OnBeforeInit(data);
if (isInited)
{
DebugUtil.LogWarning("window : {0} already inited", WindowName);
}
else
{
DoInit();
OnInit();
isInited = true;
}
}
/// <summary>
/// 显示窗口
/// </summary>
/// <param name="data">显示数据</param>
public void ShowWindow(object data = null)
{
// 未被唤醒不执行Show
if (IsUnEnabled)
{
return;
}
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 == null)
{
DebugUtil.Log("UI已被销毁:{0}", WindowName);
}
else
{
gameObject.SetActive(true);
}
_isShow = true;
OnAfterShow(data);
EventManager.Instance.Send(EventManager.EventName.UIOpen, WindowName);
}
catch (System.Exception e)
{
DebugUtil.LogError("window : {0} show window error: {1}", WindowName, e.Message);
}
}
/// <summary>
/// 更新入口
/// </summary>
public void Update(float dt)
{
_notchScreenAdapter?.UpdateCheck();
OnUpdate(dt);
}
/// <summary>
/// 刷新窗口数据
/// </summary>
/// <param name="data">新数据</param>
public void RefreshWithData(object data)
{
if (isInited)
{
OnDataRefresh(data);
}
else
{
DebugUtil.LogError("window : {0} not inited", WindowName);
}
}
/// <summary>
/// 隐藏窗口
/// </summary>
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);
}
}
/// <summary>
/// 关闭窗口
/// </summary>
/// <param name="closeType">关闭类型</param>
public async void CloseWindow(UIWindowCloseType closeType)
{
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();
}
bool isShowCloseAnim = IsShowOpenAnim;
if (UIManager.Instance.UiWindowStack.Count <= 0)
{
return;
}
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);
}
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;
}
}
#endregion
#region Internal Implementation
/// <summary>
/// 执行基础初始化
/// </summary>
private void DoInit()
{
_isShow = false;
_shutMainCamera = UIManager.Instance.IsShutMainCamera(WindowName);
IsShowOpenAnim = UIManager.Instance.GetIsAutoPlayAnim(WindowName);
AutoBindField();
InitScreenAdaption();
_TryBindBgClose();
}
/// <summary>
/// 执行显示逻辑
/// </summary>
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);
}
}
}
}
}
/// <summary>
/// 销毁窗口
/// </summary>
private void DestroyWindow()
{
DebugUtil.LogG($"DestroyWindow:{WindowName}");
OnHide();
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;
}
/// <summary>
/// 基础资源释放
/// </summary>
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
/// <summary>
/// 初始化前预处理
/// </summary>
protected virtual void OnBeforeInit(object data = null)
{
}
/// <summary>
/// UI初始化
/// </summary>
public virtual void OnInit(){}
/// <summary>
/// 自动绑定字段
/// </summary>
protected virtual void AutoBindField()
{
}
// Show Phase
/// <summary>
/// 显示前检查
/// </summary>
protected virtual bool OnBeforeShow(object data)
{
return true;
}
/// <summary>
/// 显示时处理
/// </summary>
protected virtual void OnShow(object data)
{
}
/// <summary>
/// 显示后处理
/// </summary>
protected virtual void OnAfterShow(object data)
{
}
/// <summary>
/// 每帧更新
/// </summary>
protected virtual void OnUpdate(float dt)
{
}
// Refresh Phase
/// <summary>
/// 数据刷新
/// </summary>
protected virtual void OnDataRefresh(object data) { }
// Hide Phase
/// <summary>
/// 隐藏时处理
/// </summary>
protected virtual void OnHide()
{
}
// Release Phase
/// <summary>
/// 资源释放
/// </summary>
public virtual void OnRelease()
{
}
#endregion
}