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

531 lines
32 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窗口基类 - 生命周期管理部分
/// 包含窗口的创建、显示、隐藏、关闭等生命周期管理功能
/// </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) // 显示窗口(公共接口) ║
║ └─ Show(data) // 内部显示逻辑 ║
║ ├─ SetActive(true) // 激活GameObject ║
║ ├─ 设置相机状态 // 处理全屏窗口的相机逻辑 ║
║ ├─ *OnAfterShow(data) // 显示回调,业务逻辑处理 ║
║ ├─ 播放打开动画 // 根据设置播放动画 ║
║ └─ 播放音效 // 播放默认打开音效 ║
║ ║
║ 4. 【Update 阶段】- 运行时更新 ║
║ Update(dt) -> *OnUpdate(dt) // 每帧更新处理UI逻辑合并原 Update
║ ║
║ 5. 【Refresh 阶段】- 数据刷新 ║
║ RefreshWithData(data) // 数据刷新(原 ReloadWindow
║ └─ *OnDataRefresh(data) // 刷新UI内容更新显示数据原 OnReloadWindow
║ ║
║ 6. 【Hide 阶段】- 隐藏 ║
║ HideWindow() -> Hide() ║
║ ├─ SetActive(false) // 隐藏GameObject ║
║ └─ *OnHideWindow() // 隐藏回调 ║
║ ║
║ 7. 【Close 阶段】- 关闭销毁 ║
║ CloseWindow() -> DestroyWindow() ║
║ ├─ *OnHideWindow() // 隐藏回调 ║
║ ├─ *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() ║
║ ║
║ 🔸 *OnAfterShow(data) - 【常用重写】显示后业务逻辑 ║
║ • 时机:窗口显示之后调用 ║
║ • 用途:处理显示后的业务逻辑,刷新数据,播放动画 ║
║ • 示例:刷新列表数据,开始倒计时,播放入场动画 ║
║ • 注意:通常不需要调用 base.OnAfterShow(data) ║
║ ║
║ 🔸 *OnUpdate(dt) - 【按需重写】每帧更新 ║
║ • 时机:每帧调用(仅在窗口激活且已创建时) ║
║ • 用途:处理需要持续更新的逻辑 ║
║ • 示例:倒计时更新,进度条刷新,实时数据显示 ║
║ • 注意:避免在此进行重度计算,影响性能 ║
║ ║
║ 🔸 *OnDataRefresh(data) - 【按需重写】数据刷新 ║
║ • 时机:调用 RefreshWithData 时触发 ║
║ • 用途:更新已显示窗口的内容,不重新初始化 ║
║ • 示例:更新商品列表,刷新玩家信息,切换显示内容 ║
║ • 注意:通常不需要调用 base.OnDataRefresh(data) ║
║ ║
║ 🔸 *OnHideWindow() - 【按需重写】隐藏时清理 ║
║ • 时机:窗口隐藏时调用 ║
║ • 用途:暂停逻辑,保存状态,停止动画 ║
║ • 示例:暂停倒计时,保存输入内容,停止音效 ║
║ • 注意:建议调用 base.OnHideWindow() 确保事件发送 ║
║ ║
║ 🔸 *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();
return;
}
_onBgCloseFunc?.Invoke();
});
}
}
}
#endregion
#region Core Lifecycle Methods
/// <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>
private void DoInit()
{
_isShow = false;
_bDoNotSetMainCamera = UIManager.Instance.GetIsNotSetCameraByPath(WindowName);
IsShowOpenAnim = UIManager.Instance.GetIsAutoPlayAnim(WindowName);
AutoBindField();
InitScreenAdaption();
_TryBindBgClose();
}
/// <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>
/// <param name="closeType">关闭类型</param>
/// <param name="forceShowAnim">强制设置动画,0不管1强制开-1强制不开</param>
/// <param name="isOpenMainCamera">是否打开主相机</param>
public async void CloseWindow(UIWindowCloseType closeType = UIWindowCloseType.HideAndDestroy, int forceShowAnim = 0,
bool isOpenMainCamera = true)
{
IsClosing = true;
DebugUtil.LogG($"关闭UI:{WindowName}, closeType{closeType}");
if (UIManager.Instance.CheckWindowIsFullScreen(this))
{
if (!_bDoNotSetMainCamera)
{
CameraManager.Instance.SetMainCameraActive(isOpenMainCamera);
}
}
if (closeType == UIWindowCloseType.HideAndDestroy)
{
var last = UIManager.Instance.GetLast();
if (last == this)
{
DebugUtil.LogG($"PopWindow{this.WindowName}");
UIManager.Instance.PopWindow();
}
bool isShowCloseAnim = IsShowOpenAnim;
if (forceShowAnim > 0)
{
isShowCloseAnim = true;
}
if (forceShowAnim < 0)
{
isShowCloseAnim = false;
}
//调试
//isShowCloseAnim = false;
_OnClose();
if (isShowCloseAnim)
{
UIManager.Instance.OnClick(true);
await AnimHide(() =>
{
DestroyWindow();
UIManager.Instance.RemoveWindow(mWindowName);
IsClosing = false;
});
}
else
{
UIManager.Instance.RemoveWindow(mWindowName);
DestroyWindow();
IsClosing = false;
}
}
else if(closeType == UIWindowCloseType.OnlyHide)
{
Hide();
IsClosing = false;
}
else if(closeType == UIWindowCloseType.HideAndPush)
{
Hide();
IsClosing = false;
//UIManager.Instance.PushWindow(this);
}
else
{
//强制清除所有界面时使用
DestroyWindow();
UIManager.Instance.RemoveWindow(mWindowName);
IsClosing = false;
}
}
#endregion
#region Private Internal Methods
/// <summary>
/// 执行实际的显示逻辑
/// </summary>
private void DoShow()
{
if (gameObject == null)
{
DebugUtil.Log("UI已被销毁:{0}", WindowName);
}
else
{
gameObject.SetActive(true);
}
if (UIManager.Instance.WindowInfos.TryGetValue(WindowName, out var windowInfo))
{
if (CameraManager.Instance.CheckMainCameraIsActive())
{
if (windowInfo.UIOpenType == UIOpenType.FullScreen)
{
//特殊处理主界面
if (!_bDoNotSetMainCamera && WindowName != UINameConst.UI_MainPanel)
{
CameraManager.Instance.SetMainCameraActive(false);
}
}
}
}
}
/// <summary>
/// 隐藏窗口
/// </summary>
/// <param name="isOnlyHide">是否仅隐藏</param>
private void Hide(bool isOnlyHide = false)
{
gameObject?.SetActive(false);
if (!isOnlyHide)
{
OnHideWindow();
_isShow = false;
if (UIManager.Instance.WindowInfos.TryGetValue(WindowName, out var windowInfo))
{
if (windowInfo.UIOpenType == UIOpenType.FullScreen)
{
if (!CameraManager.Instance.CheckMainCameraIsActive())
{
CameraManager.Instance.SetMainCameraActive(true);
}
}
}
}
}
/// <summary>
/// 关闭时的内部处理
/// </summary>
private void _OnClose()
{
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);
}
}
/// <summary>
/// 销毁窗口
/// </summary>
private void DestroyWindow()
{
DebugUtil.LogG($"DestroyWindow:{WindowName}");
OnHideWindow();
if (gameObject)
{
if (isInited)
{
OnRelease();
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;
}
#endregion
#region Public Interface
/// <summary>
/// 隐藏窗口(公共接口)
/// </summary>
/// <param name="isOnlyHide">是否仅隐藏</param>
public void HideWindow(bool isOnlyHide = false)
{
Hide(isOnlyHide);
}
/// <summary>
/// 显示窗口(公共接口)
/// </summary>
/// <param name="data">传入数据</param>
public void ShowWindow(object data = null)
{
//未被唤醒不执行Show
if (IsUnEnabled)
{
return;
}
OnBeforeShow(data);
DoShow();
_isShow = true;
OnAfterShow(data);
EventManager.Instance.Send(EventManager.EventName.UIOpen, WindowName);
}
#endregion
#region Virtual Methods (Override Points)
/// <summary>
/// 窗口预处理钩子函数(生命周期方法)
/// 在 AutoBindField() 和 OnInit() 之前调用,用于窗口初始化前的准备工作
/// </summary>
/// <param name="data">传入数据</param>
protected virtual void OnBeforeInit(object data = null)
{
}
/// <summary>
/// 显示前钩子函数
/// 在窗口显示之前调用,用于显示前的准备工作
/// </summary>
/// <param name="data">传入数据</param>
protected virtual void OnBeforeShow(object data)
{
}
/// <summary>
/// 显示后钩子函数
/// 在窗口显示之后调用,用于显示后的业务逻辑处理
/// </summary>
/// <param name="data">传入数据</param>
protected virtual void OnAfterShow(object data)
{
}
/// <summary>
/// 隐藏窗口时调用的回调函数
/// </summary>
protected virtual void OnHideWindow()
{
EventManager.Instance.Send(EventManager.EventName.UIClose, WindowName);
}
/// <summary>
/// 数据刷新时调用的回调函数
/// </summary>
/// <param name="data">新的数据</param>
protected virtual void OnDataRefresh(object data) { }
/// <summary>
/// 初始化UI状态设置默认值
/// 在 DoInit基础初始化之后调用用于子类的UI初始化逻辑
/// </summary>
public virtual void OnInit(){}
/// <summary>
/// 自动绑定字段
/// </summary>
protected virtual void AutoBindField()
{
}
/// <summary>
/// 释放资源,取消订阅,清理引用
/// 对应OnInit在销毁界面时调用子类必须调用 base.OnRelease()
/// </summary>
public virtual void OnRelease()
{
EventManager.Instance.Send(EventManager.EventName.UIDestroy, WindowName);
UnLoad();
_openAnimSeq?.Kill();
// 清理资源引用管理器
_assetReferenceManager?.Dispose();
_assetReferenceManager = null;
// 清理刘海屏适配器
_notchScreenAdapter = null;
// 清理GraphicRaycaster管理器
_graphicRaycasterManager = null;
}
#endregion
}