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

508 lines
30 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 Framework;
using PhxhSDK;
using UnityEngine;
using UnityEngine.UI;
/*
╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ UIWindow 按钮绑定模块 (Button Binding) ║
╠═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ 【模块功能】 ║
║ 本模块提供了两大核心功能: ║
║ 1. 手动按钮绑定 API - 在OnInit()中手动绑定自定义按钮 ║
║ 2. 默认按钮自动绑定 - 约定节点名自动绑定常用按钮(关闭、回主界面、返回) ║
║ ║
║ 【使用方式】 ║
║ ║
║ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ ║
║ │ 方式一:使用默认按钮(推荐) │ ║
║ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ║
║ ║
║ 只需在预制体中创建约定名称的按钮节点,框架会自动绑定: ║
║ ║
║ 🔴 关闭按钮: ║
║ 节点名BgClose背景关闭或 BtnClose标题栏关闭
║ 默认行为:关闭并销毁窗口 CloseWindow(true) ║
║ 重写方法OnCloseClick() ║
║ ║
║ 🏠 回主界面按钮: ║
║ 节点名BtnHome、BtnBackHome 或 HomeButton ║
║ 默认行为:关闭当前窗口 + 打开主界面 ║
║ 重写方法OnHomeClick() ║
║ ║
║ ⬅️ 返回按钮: ║
║ 节点名BtnBack、BackButton 或 BtnReturn ║
║ 默认行为:调用 Back() → 触发 OnBack() → 使用 BackStrategy ║
║ 重写方法OnBack() (async) ║
║ 特点与返回键Android Back/ESC行为完全一致 ║
║ ║
║ 示例: ║
║ public class MyWindow : UIWindow ║
║ { ║
║ // 无需任何代码,自动绑定 ║
║ } ║
║ ║
║ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ ║
║ │ 方式二:手动绑定自定义按钮 │ ║
║ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ║
║ ║
║ 在 OnInit() 中使用 BindButton() API 手动绑定: ║
║ ║
║ 示例: ║
║ protected override void OnInit() ║
║ { ║
║ // 通过路径绑定 ║
║ BindButton("BtnStart", OnStartClick); ║
║ ║
║ // 通过Button组件绑定 ║
║ BindButton(myButton, OnMyButtonClick); ║
║ ║
║ // 带参数绑定 ║
║ BindButton(itemButton, OnItemClick, itemData); ║
║ ║
║ // 静音绑定(不播放音效) ║
║ BindButton(silentButton, OnSilentClick, playAudio: false); ║
║ } ║
║ ║
║ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ ║
║ │ 方式三:自定义默认按钮 │ ║
║ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ║
║ ║
║ 重写虚属性或虚方法自定义默认按钮: ║
║ ║
║ 示例: ║
║ public class CustomWindow : UIWindow ║
║ { ║
║ // 自定义节点名 ║
║ protected override string[] DefaultCloseButtonNames => new[] { "MyCloseBtn" }; ║
║ ║
║ // 自定义关闭逻辑 ║
║ protected override void OnCloseClick() ║
║ { ║
║ ShowConfirmDialog("确定关闭?", () => CloseWindow(true)); ║
║ } ║
║ ║
║ // 自定义返回逻辑(与返回键行为一致) ║
║ protected override async UniTask<bool> OnBack() ║
║ { ║
║ SaveCurrentState(); ║
║ CloseWindow(false); ║
║ return true; ║
║ } ║
║ } ║
║ ║
║ 【更多信息】 ║
║ 详细使用指南请参考Assets/Code/Scripts/Framework/UI/README_DefaultButtons.md ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
*/
/// <summary>
/// UI窗口基类 - 按钮绑定部分
/// 提供按钮绑定API和默认按钮自动绑定功能
/// </summary>
public abstract partial class UIWindow
{
#region Button Binding Core - 按钮绑定核心逻辑
/// <summary>
/// 处理按钮点击的音频逻辑
/// </summary>
private void HandleButtonAudio(Button button, bool playAudio)
{
if (!playAudio) return;
var clickAudio = button.GetComponent<ClickAudio>();
if (clickAudio)
{
if (clickAudio.IsPlayAudio && !string.IsNullOrEmpty(clickAudio.AudioKey))
{
AudioManager.Instance.PlayAudio(clickAudio.AudioKey);
}
}
else
{
CommonUtilsFramework.PlayUICommonSound();
}
}
/// <summary>
/// 绑定按钮点击事件的核心逻辑(无参数)
/// </summary>
private void BindButtonCore(Button button, Action action, bool playAudio = true)
{
if (button is null)
{
DebugUtil.LogError("传入按钮为空,无法绑定点击事件!");
return;
}
button.onClick.RemoveAllListeners();
button.onClick.AddListener(() =>
{
DebugUtil.Log("按钮被点击: {0}", button.gameObject.name);
if (UIManager.IsUIInputBlocked) return;
UIManager.Instance.BlockUIForNormalOperation();
HandleButtonAudio(button, playAudio);
action?.Invoke();
});
DebugUtil.Log("按钮绑定完成: {0}", button.gameObject.name);
}
/// <summary>
/// 绑定按钮点击事件的核心逻辑(带参数)
/// </summary>
private void BindButtonCore<T>(Button button, Action<T> action, T parameter, bool playAudio = true)
{
if (button is null) return;
button.onClick.RemoveAllListeners();
button.onClick.AddListener(() =>
{
if (UIManager.IsUIInputBlocked) return;
UIManager.Instance.BlockUIForNormalOperation();
HandleButtonAudio(button, playAudio);
if (parameter == null)
{
DebugUtil.LogError("=== 传入参数为空! ===");
}
action?.Invoke(parameter);
});
}
#endregion
#region Button Binding Public API - 手动绑定按钮的公共API
/// <summary>
/// 通过路径绑定按钮
/// </summary>
/// <param name="target">按钮节点路径(相对于窗口根节点)</param>
/// <param name="action">点击回调</param>
/// <param name="playAudio">是否播放点击音效默认true</param>
/// <returns>Button组件</returns>
/// <example>
/// BindButton("TopBar/BtnSettings", OnSettingsClick);
/// </example>
public Button BindButton(string target, Action action, bool playAudio = true)
{
GameObject obj = transform.Find(target)?.gameObject;
if (obj is not null)
{
Button button = obj.GetComponent<Button>();
BindButtonCore(button, action, playAudio);
return button;
}
else
{
DebugUtil.LogError("未找到按钮节点: {0}/{1}", gameObject.name, target);
}
return null;
}
/// <summary>
/// 直接绑定Button组件
/// </summary>
/// <param name="button">Button组件</param>
/// <param name="action">点击回调</param>
/// <param name="playAudio">是否播放点击音效默认true</param>
/// <returns>Button组件</returns>
/// <example>
/// BindButton(myButton, OnMyButtonClick);
/// </example>
public Button BindButton(Button button, Action action, bool playAudio = true)
{
if (button is not null)
{
BindButtonCore(button, action, playAudio);
return button;
}
else
{
DebugUtil.LogError("传入按钮为空,请检查按钮绑定参数!");
}
return button;
}
/// <summary>
/// 通过GameObject绑定按钮带参数
/// </summary>
/// <typeparam name="T">参数类型</typeparam>
/// <param name="obj">按钮GameObject</param>
/// <param name="action">点击回调</param>
/// <param name="parameter">回调参数</param>
/// <param name="playAudio">是否播放点击音效默认true</param>
/// <returns>Button组件</returns>
/// <example>
/// BindButton(itemObj, OnItemClick, itemData);
/// </example>
public Button BindButton<T>(GameObject obj, Action<T> action, T parameter, bool playAudio = true)
{
if (obj is not null)
{
Button button = obj.GetComponent<Button>();
BindButtonCore(button, action, parameter, playAudio);
return button;
}
else
{
DebugUtil.LogError("传入GameObject为空请检查按钮绑定参数!");
}
return null;
}
/// <summary>
/// 直接绑定Button组件带参数
/// </summary>
/// <typeparam name="T">参数类型</typeparam>
/// <param name="button">Button组件</param>
/// <param name="action">点击回调</param>
/// <param name="parameter">回调参数</param>
/// <param name="playAudio">是否播放点击音效默认true</param>
/// <returns>Button组件</returns>
/// <example>
/// BindButton(itemButton, OnItemClick, itemData);
/// </example>
public Button BindButton<T>(Button button, Action<T> action, T parameter, bool playAudio = true)
{
if (button is not null)
{
BindButtonCore(button, action, parameter, playAudio);
return button;
}
else
{
DebugUtil.LogError("传入按钮为空,请检查按钮绑定参数!");
}
return button;
}
#endregion
#region Default Close Button - 默认关闭按钮自动绑定
/// <summary>
/// 默认关闭按钮的节点名配置(子类可重写)
///
/// 默认支持的节点名:
/// - BgClose: 背景关闭按钮(最常用,点击背景关闭)
/// - BtnClose: 标题栏关闭按钮(通常在右上角)
///
/// 自定义示例:
/// protected override string[] DefaultCloseButtonNames => new[] { "MyCloseBtn", "Btn_Exit" };
/// </summary>
protected virtual string[] DefaultCloseButtonNames => new[] { "BgClose", "BtnClose" };
/// <summary>
/// 绑定所有配置的默认关闭按钮
/// 在 DoInit() 阶段自动调用,无需手动调用
/// </summary>
private void BindDefaultCloseButtons()
{
foreach (var buttonName in DefaultCloseButtonNames)
{
TryBindCloseButton(buttonName, OnCloseClick);
}
}
/// <summary>
/// 尝试绑定关闭按钮
/// 如果节点不存在,不会报错,只是不绑定
/// </summary>
private void TryBindCloseButton(string nodeName, Action callback)
{
var trans = transform.Find(nodeName);
if (trans)
{
var btn = trans.GetComponent<Button>();
if (btn)
{
BindButton(btn, callback);
DebugUtil.Log("✅ 已自动绑定关闭按钮: {0} -> {1}", WindowName, nodeName);
}
}
}
/// <summary>
/// 关闭按钮点击回调(虚方法,子类可重写)
///
/// 默认行为:
/// - 关闭并销毁窗口 CloseWindow(true)
///
/// 自定义示例:
/// protected override void OnCloseClick()
/// {
/// // 显示确认对话框
/// ShowConfirmDialog("确定关闭?", () => CloseWindow(true));
/// }
///
/// 或者仅隐藏不销毁:
/// protected override void OnCloseClick()
/// {
/// CloseWindow(false); // 保留在内存,快速重开
/// }
/// </summary>
protected virtual void OnCloseClick()
{
CloseWindow(true);
}
#endregion
#region Default Home Button - 默认回主界面按钮自动绑定
/// <summary>
/// 默认回主界面按钮的节点名配置(子类可重写)
///
/// 默认支持的节点名:
/// - BtnHome: 回主界面按钮(推荐,常用)
/// - BtnBackHome: 返回主界面按钮
/// - HomeButton: 主页按钮
///
/// 自定义示例:
/// protected override string[] DefaultHomeButtonNames => new[] { "MainMenuBtn" };
/// </summary>
protected virtual string[] DefaultHomeButtonNames => new[] { "BtnHome", "BtnBackHome", "HomeButton" };
/// <summary>
/// 绑定所有配置的默认回主界面按钮
/// 在 DoInit() 阶段自动调用,无需手动调用
/// </summary>
private void BindDefaultHomeButtons()
{
foreach (var buttonName in DefaultHomeButtonNames)
{
TryBindHomeButton(buttonName, OnHomeClick);
}
}
/// <summary>
/// 尝试绑定回主界面按钮
/// 如果节点不存在,不会报错,只是不绑定
/// </summary>
private void TryBindHomeButton(string nodeName, Action callback)
{
var trans = transform.Find(nodeName);
if (trans)
{
var btn = trans.GetComponent<Button>();
if (btn)
{
BindButton(btn, callback);
DebugUtil.Log("✅ 已自动绑定回主界面按钮: {0} -> {1}", WindowName, nodeName);
}
}
}
/// <summary>
/// 回主界面按钮点击回调(虚方法,子类可重写)
///
/// 默认行为:
/// 1. 关闭当前窗口 CloseWindow(true)
/// 2. 打开主界面 UIManager.Instance.CreateAndOpenWindow(UINameConst.UI_MainPanel)
///
/// 自定义示例:
/// protected override void OnHomeClick()
/// {
/// // 清理数据
/// CleanupGameData();
///
/// // 调用基类方法
/// base.OnHomeClick();
/// }
///
/// 或者完全自定义:
/// protected override void OnHomeClick()
/// {
/// CloseWindow(true);
/// UIManager.Instance.CreateAndOpenWindow("MyCustomMainPanel").Forget();
/// }
/// </summary>
protected virtual void OnHomeClick()
{
// 关闭当前窗口
CloseWindow(true);
}
#endregion
#region Default Back Button - 默认返回按钮自动绑定
/// <summary>
/// 默认返回按钮的节点名配置(子类可重写)
///
/// 默认支持的节点名:
/// - BtnBack: 返回按钮(推荐,最常用)
/// - BackButton: 返回按钮
/// - BtnReturn: 返回按钮
///
/// 自定义示例:
/// protected override string[] DefaultBackButtonNames => new[] { "BtnPrevious" };
///
/// 禁用返回按钮:
/// protected override string[] DefaultBackButtonNames => new string[0];
/// </summary>
protected virtual string[] DefaultBackButtonNames => new[] { "BtnBack", "BackButton", "BtnReturn" };
/// <summary>
/// 绑定所有配置的默认返回按钮
/// 在 DoInit() 阶段自动调用,无需手动调用
///
/// 重要说明:
/// - 返回按钮会调用 Back() 方法,触发 OnBack() 虚方法
/// - 点击返回按钮 = 按下返回键Android Back / ESC
/// - 子类通过重写 OnBack() 来自定义返回逻辑
/// - 默认使用 BackStrategy 配置的策略
/// </summary>
private void BindDefaultBackButtons()
{
foreach (var buttonName in DefaultBackButtonNames)
{
TryBindBackButton(buttonName);
}
}
/// <summary>
/// 尝试绑定返回按钮
/// 如果节点不存在,不会报错,只是不绑定
///
/// 绑定后,点击按钮会调用 Back() 方法,触发 OnBack() 虚方法
/// </summary>
private void TryBindBackButton(string nodeName)
{
var trans = transform.Find(nodeName);
if (trans)
{
var btn = trans.GetComponent<Button>();
if (btn)
{
// 绑定到 Back() 方法,会触发 OnBack() 虚方法
BindButton(btn, () => Back().Forget());
DebugUtil.Log("✅ 已自动绑定返回按钮: {0} -> {1} -> Back()", WindowName, nodeName);
}
}
}
// 注意:返回按钮不需要 OnBackClick() 方法
// 它直接调用 Back() 方法,触发 OnBack() 虚方法
// OnBack() 方法定义在 UIWindow.Lifecycle.cs 中
//
// 自定义返回逻辑示例:
// protected override async UniTask<bool> OnBack()
// {
// SaveCurrentState();
// CloseWindow(false);
// return true; // 返回true表示已处理
// }
#endregion
}