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

762 lines
22 KiB
C#
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden 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 System.Collections;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using Framework;
using Obfuz;
using PhxhSDK;
using PhxhSDK.AOT.Aspect;
using UnityEngine;
[ObfuzIgnore]
public partial class UIManager : Singlenton<UIManager>, IInitable, IUpdatable
{
public const string UI_PREFAB_PATH = "Assets/Art_Out/UI/Prefab/{0}.prefab";
public Dictionary<string, UIWindow> mMemoryWindows = new Dictionary<string, UIWindow>();
// ✅ 使用独立的层级管理器
private UICanvasLayerManager LayerManager => UICanvasLayerManager.Instance;
private ScreenOrientation _currentOrientation;
private Dictionary<string, string> _openingWindows = new Dictionary<string, string>();
private List<UIWindow> _hideCache = new List<UIWindow>();
private UIWindow _curFullScreen;
private Dictionary<GameObject, UIWindow> _uiWindowObjDic = new Dictionary<GameObject, UIWindow>();
public Dictionary<GameObject, UIWindow> UIWindowObjDic => _uiWindowObjDic;
public Vector2 Size;
/// <summary>
/// UI和主相机屏幕缩放
/// </summary>
public Vector2 UIAndMainScreenScale;
/// <summary>
/// UI和UI屏幕缩放
/// </summary>
public Vector2 UIAndUIScreenScale;
/// <summary>
/// 禁用返回键计数
/// </summary>
private int disableBackCount;
/// <summary>
/// 是否响应返回键
/// </summary>
public bool IsEnableBack
{
get { return disableBackCount <= 0; }
set
{
if (value)
{
if (disableBackCount > 0)
--disableBackCount;
}
else
++disableBackCount;
}
}
public void Init()
{
IsEnableBack = true;
_AllWindowMeta();
// 初始化层级管理器
LayerManager.Initialize(UIRoot.Instance.GetAllLayerRoots());
if (DeviceHelper.IsFullScreenIOS())
{
SetFullScreenIOS();
}
EventManager.Instance.Register(EventManager.EventName.Android_Click_Back, _OnAndroidClickBack);
EventManager.Instance.Register(EventManager.EventName.ClearUIStack, (Action<string[]>)ClearHideStack);
OnScreenSizeChange();
AspectManager.Instance.ScreenSizeChange += OnScreenSizeChange;
}
public void Release()
{
LayerManager.Clear();
AspectManager.Instance.ScreenSizeChange -= OnScreenSizeChange;
EventManager.Instance.Unregister(EventManager.EventName.Android_Click_Back, _OnAndroidClickBack);
EventManager.Instance.Unregister(EventManager.EventName.ClearUIStack, (Action<string[]>)ClearHideStack);
}
public bool disableUILogic = false;
public void Update(float dt)
{
if (disableUILogic) return;
foreach (var winKv in mMemoryWindows)
{
var window = winKv.Value;
if (window != null && !window.IsClosing)
{
if (window.IsCreated() && window.gameObject)
{
if (window.gameObject)
{
window.Update(dt);
}
}
}
}
_UpdateInputBlockTimer(dt);
}
#region UIWindow Manager
public static void BindWindowObj(UIWindow window, GameObject uiObj)
{
if (window == null || uiObj == null)
{
if (window == null)
DebugUtil.LogError("window is null");
if (uiObj == null)
DebugUtil.LogError("uiObj is null");
return;
}
window.gameObject = uiObj.gameObject;
window.transform = uiObj.transform;
window.name = uiObj.name;
}
#endregion
#region OpenWindowAPI
/// <summary>
/// 打开一个UI
/// UIManager.Instance.OpenWindow("Common/GameUI")
/// </summary>
/// <summary>
/// 创建并打开窗口
/// </summary>
/// <param name="path">窗口路径</param>
/// <param name="data">传递的数据</param>
/// <param name="overrideStackMode">覆盖栈模式null则使用配置。⚠ 谨慎使用,大部分情况应使用配置</param>
public async UniTask<UIWindow> CreateAndOpenWindow(string path, object data = null, UIStackMode? overrideStackMode = null)
{
DebugUtil.LogP($"打开窗口:{path}");
if (mMemoryWindows.ContainsKey(path))
{
DebugUtil.LogG($"窗口已打开!!!{path}");
//return null;
}
var window = await _LoadWindow(path, true);
if (window != null)
{
// 初始化和显示窗口
window.Init(data);
window.ShowWindow(data);
// 获取配置
var config = GetWindowInfo(path);
var stackMode = overrideStackMode ?? config.StackMode;
// 1. 栈管理
UINavigationManager.Instance.HandleWindowOpen(window, stackMode);
// 2. 可见性管理
UIWindowVisibilityManager.Instance.HandleVisibilityOnOpen(window, config.VisibilityStrategy);
}
return window;
}
public bool Back()
{
return false;
}
public bool RemoveWindow(string wName)
{
string windowName = wName;
bool result = false;
UIWindow window;
if (mMemoryWindows.TryGetValue(windowName, out window))
{
LayerManager.RemoveWindow(window);
mMemoryWindows.Remove(windowName);
result = true;
}
return result;
}
/// <summary>
/// 关闭窗口
/// </summary>
/// <param name="wName">窗口名称</param>
/// <param name="destroy">是否销毁窗口</param>
public void CloseWindow(string wName, bool destroy)
{
var window = GetOpenedWindowByPath<UIWindow>(wName);
if (window != null)
{
window.CloseWindow(destroy);
}
}
/// <summary>
/// 打开已加载的窗口(显示并根据配置处理导航栈)
/// 适用于预加载窗口的显示场景
/// </summary>
/// <param name="window">已加载的窗口</param>
/// <param name="data">传递的数据(可选)</param>
/// <param name="overrideStackMode">覆盖栈模式null则使用配置</param>
public void OpenLoadedWindow(UIWindow window, object data = null, UIStackMode? overrideStackMode = null)
{
if (window == null)
{
DebugUtil.LogError("OpenLoadedWindow: window is null");
return;
}
// 显示窗口
window.ShowWindow(data);
// 获取配置
var config = GetWindowInfo(window.WindowName);
var stackMode = overrideStackMode ?? config.StackMode;
// 1. 栈管理
UINavigationManager.Instance.HandleWindowOpen(window, stackMode);
// 2. 可见性管理
UIWindowVisibilityManager.Instance.HandleVisibilityOnOpen(window, config.VisibilityStrategy);
}
/// <summary>
/// 打开已加载的窗口(通过路径)
/// </summary>
public void OpenLoadedWindow(string path, object data = null, UIStackMode? overrideStackMode = null)
{
var window = GetOpenedWindowByPath(path);
if (window != null)
{
OpenLoadedWindow(window, data, overrideStackMode);
}
else
{
DebugUtil.LogError($"OpenLoadedWindow: window not found: {path}");
}
}
public T GetOpenedWindowByPath<T>(string uipath) where T : UIWindow
{
var window = GetOpenedWindowByPath(uipath) as T;
//return window.gameObject.activeSelf ? window : null;
return window;
}
public UIWindow GetOpenedWindowByPath(string uipath)
{
if (string.IsNullOrEmpty(uipath))
{
DebugUtil.LogError("UIManager.GetOpenedWindowByPath, uipath is null or empty");
return null;
}
if (mMemoryWindows.ContainsKey(uipath))
{
//return window.gameObject.activeSelf ? window : null;
return mMemoryWindows[uipath];
}
return null;
}
public bool GetTopWindow(out UIWindow window, out UICanvasLayer layer, int banLayerMask)
{
return LayerManager.GetTopWindow(out window, out layer, banLayerMask);
}
public int LayerMask(UICanvasLayer layer)
{
return LayerManager.GetLayerMask(layer);
}
async void SetFullScreenIOS()
{
await UniTask.Delay(670);
// while (true)
// {
// var screenOrientation = DragonNativeBridge.GetScreenOrientation();
// if (screenOrientation != _currentOrientation)
// {
// _currentOrientation = screenOrientation;
// EventDispatcher.Instance.DispatchEvent(new ScreenOrientationChangedEvent(_currentOrientation));
// }
// yield return wait;
// }
}
/// <summary>
/// 加载窗口(不显示)
/// 用于预加载场景
/// </summary>
public async UniTask<UIWindow> LoadWindow(string path)
{
return await _LoadWindow(path, false);
}
private async UniTask<UIWindow> _LoadWindow(string path, bool isActive = false)
{
string uipath = path;
UIWindow window = null;
WindowInfo wInfo;
if (!_windowsInfo.ContainsKey(uipath))
{
//if (_windowsInfo.TryGetValue(uipath, out wInfo))
{
DebugUtil.LogError("Create ui fail: {0}, no windows info inited", uipath);
return window;
}
}
wInfo = _windowsInfo[uipath];
var uiWindowLayer = wInfo.windowLayer;
if (!mMemoryWindows.TryGetValue(uipath, out window))
{
if (_openingWindows.ContainsKey(uipath))
{
return window;
}
_openingWindows.Add(uipath, uipath);
window = await UIRoot.Instance.CreateWindow(uipath, uiWindowLayer);
_openingWindows.Remove(uipath);
if (window != null)
{
mMemoryWindows[uipath] = window;
window.WindowName = uipath;
window.uiWindowLayer = uiWindowLayer;
}
else
{
DebugUtil.LogError("Create ui fail: {0}", uipath);
return window;
}
}
LayerManager.AddWindow(window);
if (window == null)
{
DebugUtil.LogError("Create ui fail: {0}", uipath);
return null;
}
if (!isActive)
{
window.gameObject.SetActive(false);
}
_uiWindowObjDic.TryAdd(window.gameObject, window);
return window;
}
//展示全部已加载的NormalUI
public void OpenLoadedNormalUI(string uiName, object data = null)
{
WindowInfo wInfo;
wInfo = _windowsInfo[uiName];
if (mMemoryWindows.ContainsKey(uiName))
{
mMemoryWindows[uiName].Init(data);
mMemoryWindows[uiName].ShowWindow(data);
}
}
public void ForeachUI(Action<UIWindow> action)
{
var windowList = mMemoryWindows.ToList();
foreach (var kv in windowList)
{
var window = kv.Value;
if (window != null)
{
action?.Invoke(kv.Value);
}
}
}
public void CloseAllUIExcept(string path)
{
var windowList = mMemoryWindows.ToList();
foreach (var kv in windowList)
{
var window = kv.Value;
if(window.WindowName != path)
{
window.CloseWindow(destroy: true);
mMemoryWindows.Remove(window.WindowName);
LayerManager.RemoveWindow(window);
}
}
ClearHideStack();
}
#endregion
#region ShowAndHide
/// <summary>
/// 隐藏所有UI
/// 和 ShowAllUI 成对调用
/// </summary>
/// <param name="exceptions">不隐藏的UI窗口名称列表</param>
public void HideAllUI(params string[] exceptions)
{
HideAllUI(UICanvasLayer.Max, exceptions);
}
/// <summary>
/// 隐藏指定层级及以下的所有UI
/// 和 ShowAllUI 成对调用
/// </summary>
/// <param name="maxLayer">最大层级包含该层级及以下的所有UI都会被隐藏</param>
/// <param name="exceptions">不隐藏的UI窗口名称列表</param>
public void HideAllUI(UICanvasLayer maxLayer, params string[] exceptions)
{
var hiddenWindows = LayerManager.HideWindowsUpToLayer(maxLayer, exceptions);
_hideCache.AddRange(hiddenWindows);
}
/// <summary>
/// 隐藏指定层级列表中的所有UI
/// 和 ShowAllUI 成对调用
/// </summary>
/// <param name="layers">要隐藏的层级列表</param>
/// <param name="exceptions">不隐藏的UI窗口名称列表</param>
public void HideAllUI(UICanvasLayer[] layers, params string[] exceptions)
{
var hiddenWindows = LayerManager.HideWindowsInLayers(layers, exceptions);
_hideCache.AddRange(hiddenWindows);
}
/// <summary>
/// 和HideAllUI成对调用
/// </summary>
public void ShowAllUI()
{
foreach (var v in _hideCache)
{
var window = v;
if (window != null)
window.ShowWindow();
}
_hideCache.Clear();
}
public async void ShowClickShield(string uiPath, int time = 0)
{
//放到最上层
if (!_windowsInfo.ContainsKey(uiPath))
{
return;
}
CreateAndOpenWindow(uiPath);
mMemoryWindows.TryGetValue(uiPath, out var window);
if (time > 0)
{
await UniTask.Delay(time);
window.CloseWindow(destroy: true);
}
}
public void CloseClickShield(string uiPath)
{
if (mMemoryWindows.ContainsKey(uiPath))
{
var window = mMemoryWindows[uiPath];
window.CloseWindow(destroy: true);
}
}
#endregion
//根据路径判断UI界面是否打开
public bool IsWindowOpening(string uiPath)
{
string windowName;
if (_openingWindows.TryGetValue(uiPath, out windowName))
{
if (windowName != null)
{
return true;
}
}
return false;
}
/// <summary>
/// 关掉所有Normal层已打开的界面保留传参的界面
/// </summary>
/// <param name="path"></param>
public void CloseAllNormalExcept(string path, bool isShowAnim = true)
{
var windowsL = LayerManager.GetWindowsInLayer(UICanvasLayer.Normal);
if (!mMemoryWindows.ContainsKey(path))
{
DebugUtil.LogError("请检查Home返回方法传入UI窗口路径参数{0}", path);
return;
}
List<UIWindow> toBeRemoved = new List<UIWindow>();
if (windowsL.Count > 0)
{
var node = windowsL.First;
while (node != null)
{
var window = node.Value;
if (window.WindowName != path)
{
toBeRemoved.Add(window);
}
if (node == windowsL.Last)
{
break;
}
node = node.Next;
}
}
string[] strL = { path };
ClearHideStack(strL);
if (toBeRemoved.Count > 0)
{
for (int i = toBeRemoved.Count - 2; i >= 0; i--)
{
toBeRemoved[i].CloseWindow(destroy: true);
}
}
if (UINavigationManager.Instance.IsWindowInStack(mMemoryWindows[path]))
{
mMemoryWindows[path].ShowWindow();
}
}
/// <summary>
/// 打开指定界面,并关闭其他界面
/// </summary>
/// <param name="paths"></param>
/// <remarks>如果只需要关闭界面请使用ClearAllNormalExcept方法</remarks>
public void CloseAllNormalExcept(params string[] paths)
{
bool isShowAnim = true;
var windowsL = LayerManager.GetWindowsInLayer(UICanvasLayer.Normal);
foreach (var path in paths)
{
if (!mMemoryWindows.ContainsKey(path))
{
DebugUtil.LogError("请检查Home返回方法传入UI窗口路径参数{0}", path);
return;
}
}
List<UIWindow> toBeRemoved = new List<UIWindow>();
if (windowsL.Count > 0)
{
var node = windowsL.First;
while (node != null)
{
var window = node.Value;
if (!paths.Contains(window.WindowName))
{
toBeRemoved.Add(window);
}
if (node == windowsL.Last)
{
break;
}
node = node.Next;
}
}
//string[] strL = { path };
ClearHideStack(paths);
if (toBeRemoved.Count > 0)
{
for (int i = toBeRemoved.Count - 2; i >= 0; i--)
{
toBeRemoved[i].CloseWindow(destroy: true);
}
}
foreach (var path in paths)
{
if (UINavigationManager.Instance.IsWindowInStack(mMemoryWindows[path]))
{
mMemoryWindows[path].ShowWindow();
}
}
}
/// <summary>
/// 清理除指定UI以外的界面(只包含关闭操作)
/// </summary>
/// <param name="paths"></param>
public void ClearAllNormalExcept(params string[] paths)
{
bool isShowAnim = true;
var windowsL = LayerManager.GetWindowsInLayer(UICanvasLayer.Normal);
foreach (var path in paths)
{
if (!mMemoryWindows.ContainsKey(path))
{
DebugUtil.LogError("请检查Home返回方法传入UI窗口路径参数{0}", path);
return;
}
}
List<UIWindow> toBeRemoved = new List<UIWindow>();
if (windowsL.Count > 0)
{
var node = windowsL.First;
while (node != null)
{
var window = node.Value;
if (!paths.Contains(window.WindowName))
{
toBeRemoved.Add(window);
}
if (node == windowsL.Last)
{
break;
}
node = node.Next;
}
}
ClearHideStack(paths);
if (toBeRemoved.Count > 0)
{
for (int i = toBeRemoved.Count - 2; i >= 0; i--)
{
toBeRemoved[i].CloseWindow(destroy: true);
}
}
}
private async void _OnAndroidClickBack()
{
if (!IsEnableBack)
return;
var topWindow = UINavigationManager.Instance.GetTopWindow();
if (topWindow != null && await topWindow.OnBack())
{
return;
}
if (!CheckWindowCreated(UINameConst.UIBackReturn))
{
await CreateAndOpenWindow(UINameConst.UIBackReturn);
}
}
public bool CheckWindowCreated(string uiPath)
{
if (mMemoryWindows.TryGetValue(uiPath, out var window))
{
return window.IsCreated();
}
return false;
}
private async void OnScreenSizeChange()
{
await UniTask.NextFrame(); // 必须等待一帧等RectTransform大小刷新后获取
RectTransform rectTrans = UIRoot.Instance.Root.GetComponent<RectTransform>();
Size = new(rectTrans.sizeDelta.x, rectTrans.sizeDelta.y);
UIAndMainScreenScale = new Vector3(Size.x / Screen.width, Size.y / Screen.height);
UIAndUIScreenScale = new Vector3(Size.x / AspectManager.Instance.ScreenSize.x, Size.y / AspectManager.Instance.ScreenSize.y);
}
#region Navigation Stack Management
/// <summary>
/// 清空导航栈(保留指定窗口)
/// </summary>
public void ClearHideStack(params string[] windowName)
{
var stack = UINavigationManager.Instance.NavigationStack;
// 倒序遍历,避免索引问题
for (int i = stack.Count - 1; i >= 0; i--)
{
var window = stack[i];
// 检查是否是要保留的窗口
bool shouldKeep = false;
for (int j = 0; j < windowName.Length; j++)
{
if (window.WindowName == windowName[j])
{
shouldKeep = true;
break;
}
}
if (!shouldKeep)
{
// 先从导航栈移除,再销毁窗口
UINavigationManager.Instance.HandleWindowClose(window);
window.CloseWindow(destroy: true);
}
}
}
/// <summary>
/// 清空所有导航栈
/// </summary>
public void ClearHideStack()
{
var stack = UINavigationManager.Instance.NavigationStack;
// 先复制列表,避免在遍历时修改集合
var windowsToClose = new List<UIWindow>(stack);
UINavigationManager.Instance.Clear();
// 再关闭所有窗口
for (int i = 0; i < windowsToClose.Count; i++)
{
windowsToClose[i].CloseWindow(destroy: true);
}
}
#endregion
}