413 lines
14 KiB
C#
413 lines
14 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using UnityEngine.EventSystems;
|
||
|
||
/// <summary>
|
||
/// 自定义键盘管理器 - 处理任意 TMP_InputField 的键盘弹出
|
||
/// 支持不同键盘布局:拼音(中文)、数字、字母等
|
||
/// </summary>
|
||
public class CustomKeyboardManager : MonoBehaviour
|
||
{
|
||
[Header("键盘面板配置")]
|
||
public Transform KeyboardContainer; // 键盘放置的容器(如果为空则使用 UIManager 的 DialogLayer)
|
||
public GameObject KeyPrefab; // 按键预制体(可选)
|
||
public Vector2 KeyboardSize = new Vector2(900, 350);
|
||
public Vector2 KeySize = new Vector2(60, 60);
|
||
public Vector2 KeySpacing = new Vector2(6, 6);
|
||
public int KeysPerRow = 10;
|
||
[Header("键盘位置")]
|
||
public Vector2 KeyboardAnchoredPosition = new Vector2(0, 50); // 键盘相对锚点的偏移
|
||
|
||
private static CustomKeyboardManager _instance;
|
||
public static CustomKeyboardManager Instance => _instance;
|
||
|
||
private GameObject _currentKeyboardPanel;
|
||
private GameObject _currentKeyboardRoot;
|
||
private CustomKeyboard _currentKeyboard;
|
||
private TMP_InputField _targetInputField;
|
||
private Action<string> _onConfirm;
|
||
private Canvas _keyboardCanvas; // 键盘的 Canvas 组件
|
||
|
||
void Awake()
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
_instance = this;
|
||
// 启动拼音词库加载
|
||
StartCoroutine(PinyinInputManager.Initialize());
|
||
}
|
||
else if (_instance != this)
|
||
{
|
||
Destroy(gameObject);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 为 InputField 显示自定义键盘
|
||
/// </summary>
|
||
/// <param name="inputField">目标输入框</param>
|
||
/// <param name="layout">键盘布局类型</param>
|
||
/// <param name="onConfirm">确认回调(可选)</param>
|
||
/// <param name="clearOnFirstInput">首次输入时是否清空原内容</param>
|
||
public void ShowKeyboard(TMP_InputField inputField, KeyboardLayout layout, Action<string> onConfirm = null, bool clearOnFirstInput = false)
|
||
{
|
||
if (inputField == null) return;
|
||
|
||
// 如果已有键盘,先隐藏(这会清空 _targetInputField)
|
||
HideKeyboard();
|
||
|
||
// 在隐藏旧键盘后再设置新的目标(避免被 HideKeyboard 清空)
|
||
_targetInputField = inputField;
|
||
_onConfirm = onConfirm;
|
||
|
||
// 创建键盘面板
|
||
_currentKeyboardPanel = CreateKeyboardPanel();
|
||
_currentKeyboard = _currentKeyboardPanel.GetComponent<CustomKeyboard>();
|
||
|
||
if (_currentKeyboard != null)
|
||
{
|
||
// 使用适配器模式让 CustomKeyboard 直接修改 InputField
|
||
var adapter = _currentKeyboardPanel.AddComponent<KeyboardInputAdapter>();
|
||
adapter.SetTarget(inputField, this, clearOnFirstInput);
|
||
|
||
_currentKeyboard.KeyPrefab = KeyPrefab;
|
||
_currentKeyboard.KeySize = KeySize;
|
||
_currentKeyboard.KeySpacing = KeySpacing;
|
||
_currentKeyboard.SetTarget(adapter, layout, false);
|
||
|
||
// 根据布局自动计算键盘大小
|
||
AdjustKeyboardSize(layout);
|
||
}
|
||
|
||
// 调整键盘位置避免遮挡输入框
|
||
AdjustKeyboardPosition(inputField);
|
||
|
||
// 完全禁用 InputField 的系统键盘和交互
|
||
inputField.shouldHideMobileInput = true;
|
||
inputField.readOnly = true; // 设为只读,防止系统键盘弹出
|
||
inputField.DeactivateInputField(); // 取消激活状态
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据键盘布局自动计算大小
|
||
/// </summary>
|
||
private void AdjustKeyboardSize(KeyboardLayout layout)
|
||
{
|
||
if (_currentKeyboardPanel == null || _currentKeyboard == null) return;
|
||
|
||
var panelRT = _currentKeyboardPanel.GetComponent<RectTransform>();
|
||
if (panelRT == null) return;
|
||
|
||
// 获取键盘所需的行数
|
||
int rows = GetKeyboardRows(layout);
|
||
|
||
// 计算高度:每行高度 = 按键高度 + 间距 + 内边距
|
||
float rowHeight = KeySize.y + KeySpacing.y;
|
||
float colWidth = KeySize.x + KeySpacing.x;
|
||
float totalHeight = rows * rowHeight + 20; // 20 是上下内边距
|
||
|
||
// 宽度保持不变,只调整高度
|
||
Vector2 newSize;
|
||
if(layout == KeyboardLayout.Pinyin)
|
||
{
|
||
newSize = new Vector2(KeyboardSize.x, totalHeight);
|
||
}else{
|
||
newSize = new Vector2(colWidth * KeysPerRow + 20, totalHeight);
|
||
}
|
||
|
||
panelRT.sizeDelta = newSize;
|
||
|
||
// Debug.Log($"Keyboard size adjusted: layout={layout}, rows={rows}, height={totalHeight}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前键盘所需的行数(用于计算键盘高度)
|
||
/// </summary>
|
||
public int GetKeyboardRows(KeyboardLayout _layout)
|
||
{
|
||
int keyCount = 0;
|
||
|
||
switch (_layout)
|
||
{
|
||
case KeyboardLayout.Numeric:
|
||
keyCount = 12; // 数字键盘
|
||
break;
|
||
case KeyboardLayout.Hex:
|
||
keyCount = 16; // 十六进制
|
||
break;
|
||
case KeyboardLayout.Pinyin:
|
||
keyCount = 26 + 1; // 26个字母 + 空格
|
||
break;
|
||
default:
|
||
keyCount = 39; // 默认键盘
|
||
break;
|
||
}
|
||
|
||
// 加上控制行(Clear, Backspace, OK)
|
||
keyCount += 3;
|
||
|
||
// 假设每行10个按键
|
||
int cols = KeysPerRow;
|
||
int rows = Mathf.CeilToInt((float)keyCount / cols);
|
||
|
||
// 拼音键盘需要额外的候选字行
|
||
if (_layout == KeyboardLayout.Pinyin)
|
||
{
|
||
rows += 1; // 候选字区域
|
||
}
|
||
|
||
return rows;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调整键盘位置避免遮挡输入框
|
||
/// </summary>
|
||
private void AdjustKeyboardPosition(TMP_InputField inputField)
|
||
{
|
||
if (_currentKeyboardPanel == null || inputField == null) return;
|
||
|
||
var panelRT = _currentKeyboardPanel.GetComponent<RectTransform>();
|
||
var inputRT = inputField.GetComponent<RectTransform>();
|
||
|
||
if (panelRT == null || inputRT == null) return;
|
||
|
||
// 获取输入框在屏幕上的位置
|
||
Vector3[] inputCorners = new Vector3[4];
|
||
inputRT.GetWorldCorners(inputCorners);
|
||
float inputBottomY = inputCorners[0].y; // 输入框底部 Y 坐标
|
||
|
||
// 获取键盘高度
|
||
float keyboardHeight = KeyboardSize.y;
|
||
|
||
// 如果输入框位置较低,键盘可能会遮挡,则向上调整
|
||
if (inputBottomY < keyboardHeight + 20) // 20 是安全边距
|
||
{
|
||
// 键盘放在输入框上方
|
||
float newY = inputBottomY + keyboardHeight / 2 + 10;
|
||
panelRT.anchoredPosition = new Vector2(KeyboardAnchoredPosition.x, newY);
|
||
}
|
||
else
|
||
{
|
||
// 使用默认位置(底部)
|
||
panelRT.anchoredPosition = KeyboardAnchoredPosition;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 隐藏当前键盘
|
||
/// </summary>
|
||
public void HideKeyboard()
|
||
{
|
||
if (_currentKeyboardRoot != null)
|
||
{
|
||
Destroy(_currentKeyboardRoot);
|
||
_currentKeyboardRoot = null;
|
||
_currentKeyboard = null;
|
||
_keyboardCanvas = null;
|
||
}
|
||
else if (_currentKeyboardPanel != null)
|
||
{
|
||
Destroy(_currentKeyboardPanel);
|
||
}
|
||
|
||
_currentKeyboardPanel = null;
|
||
_targetInputField = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确认输入
|
||
/// </summary>
|
||
public void ConfirmInput()
|
||
{
|
||
if (_targetInputField != null)
|
||
{
|
||
_onConfirm?.Invoke(_targetInputField.text);
|
||
}
|
||
HideKeyboard();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置键盘位置
|
||
/// </summary>
|
||
public void SetKeyboardPosition(Vector2 position)
|
||
{
|
||
if (_currentKeyboardPanel != null)
|
||
{
|
||
var panelRT = _currentKeyboardPanel.GetComponent<RectTransform>();
|
||
if (panelRT != null)
|
||
{
|
||
panelRT.anchoredPosition = position;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置键盘层级
|
||
/// </summary>
|
||
public void SetKeyboardSortingOrder(int sortingOrder)
|
||
{
|
||
if (_keyboardCanvas != null)
|
||
{
|
||
_keyboardCanvas.sortingOrder = sortingOrder;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前键盘是否显示
|
||
/// </summary>
|
||
public bool IsKeyboardShowing()
|
||
{
|
||
return _currentKeyboardRoot != null && _currentKeyboardRoot.activeInHierarchy;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建键盘面板
|
||
/// </summary>
|
||
private GameObject CreateKeyboardPanel()
|
||
{
|
||
// 优先使用 EnhancedUIManager 的 DialogLayer
|
||
Transform container = KeyboardContainer;
|
||
if (container == null && EnhancedUIManager.Instance != null)
|
||
{
|
||
container = EnhancedUIManager.Instance.DialogLayer;
|
||
}
|
||
if (container == null)
|
||
{
|
||
container = this.transform;
|
||
}
|
||
|
||
var rootGO = new GameObject("CustomKeyboardRoot", typeof(RectTransform));
|
||
rootGO.transform.SetParent(container, false);
|
||
_currentKeyboardRoot = rootGO;
|
||
|
||
var rootRT = rootGO.GetComponent<RectTransform>();
|
||
rootRT.anchorMin = Vector2.zero;
|
||
rootRT.anchorMax = Vector2.one;
|
||
rootRT.offsetMin = Vector2.zero;
|
||
rootRT.offsetMax = Vector2.zero;
|
||
|
||
// 添加 Canvas 以控制层级(在 DialogLayer 之上)
|
||
_keyboardCanvas = rootGO.AddComponent<Canvas>();
|
||
_keyboardCanvas.overrideSorting = true;
|
||
_keyboardCanvas.sortingOrder = 350; // 高于 DialogLayer (300) 但低于 SystemLayer (400)
|
||
rootGO.AddComponent<UnityEngine.UI.GraphicRaycaster>();
|
||
|
||
var blockerGO = new GameObject("KeyboardOutsideDismissArea", typeof(RectTransform), typeof(Image), typeof(EventTrigger));
|
||
blockerGO.transform.SetParent(rootGO.transform, false);
|
||
var blockerRT = blockerGO.GetComponent<RectTransform>();
|
||
blockerRT.anchorMin = Vector2.zero;
|
||
blockerRT.anchorMax = Vector2.one;
|
||
blockerRT.offsetMin = Vector2.zero;
|
||
blockerRT.offsetMax = Vector2.zero;
|
||
|
||
var blockerImage = blockerGO.GetComponent<Image>();
|
||
blockerImage.color = new Color(0f, 0f, 0f, 0f);
|
||
blockerImage.raycastTarget = true;
|
||
|
||
var blockerTrigger = blockerGO.GetComponent<EventTrigger>();
|
||
var blockerClick = new EventTrigger.Entry { eventID = EventTriggerType.PointerClick };
|
||
blockerClick.callback.AddListener(_ => HideKeyboard());
|
||
blockerTrigger.triggers.Add(blockerClick);
|
||
|
||
var panelGO = new GameObject("CustomKeyboardPanel", typeof(RectTransform));
|
||
panelGO.transform.SetParent(rootGO.transform, false);
|
||
|
||
var panelRT = panelGO.GetComponent<RectTransform>();
|
||
panelRT.sizeDelta = KeyboardSize;
|
||
// 底部居中锚点
|
||
panelRT.anchorMin = new Vector2(0.5f, 0);
|
||
panelRT.anchorMax = new Vector2(0.5f, 0);
|
||
panelRT.pivot = new Vector2(0.5f, 0);
|
||
panelRT.anchoredPosition = KeyboardAnchoredPosition;
|
||
|
||
// 添加背景
|
||
var panelImg = panelGO.AddComponent<Image>();
|
||
panelImg.color = new Color(0.08f, 0.1f, 0.12f, 0.95f);
|
||
|
||
// 添加圆角和阴影效果(可选,让键盘更美观)
|
||
var shadow = panelGO.AddComponent<UnityEngine.UI.Shadow>();
|
||
shadow.effectColor = new Color(0, 0, 0, 0.5f);
|
||
shadow.effectDistance = new Vector2(3, -3);
|
||
|
||
// 创建按键容器
|
||
var containerGO = new GameObject("KeysContainer", typeof(RectTransform));
|
||
containerGO.transform.SetParent(panelGO.transform, false);
|
||
var containerRT = containerGO.GetComponent<RectTransform>();
|
||
containerRT.anchorMin = Vector2.zero;
|
||
containerRT.anchorMax = Vector2.one;
|
||
containerRT.offsetMin = new Vector2(10, 10);
|
||
containerRT.offsetMax = new Vector2(-10, -10);
|
||
|
||
// 添加 GridLayoutGroup
|
||
var grid = containerGO.AddComponent<GridLayoutGroup>();
|
||
grid.cellSize = KeySize;
|
||
grid.spacing = KeySpacing;
|
||
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
|
||
grid.constraintCount = 10; // 每行10个按键
|
||
|
||
// 添加 CustomKeyboard 脚本
|
||
var keyboard = panelGO.AddComponent<CustomKeyboard>();
|
||
keyboard.KeysContainer = containerGO.transform;
|
||
keyboard.KeySize = KeySize;
|
||
keyboard.KeySpacing = KeySpacing;
|
||
|
||
return panelGO;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 为 InputField 设置点击监听
|
||
/// </summary>
|
||
public static void SetupInputField(TMP_InputField inputField, KeyboardLayout layout, Action<string> onConfirm = null, bool clearOnFirstInput = false)
|
||
{
|
||
if (inputField == null)
|
||
{
|
||
Debug.LogError("CustomKeyboardManager.SetupInputField: inputField is null!");
|
||
return;
|
||
}
|
||
|
||
if (inputField.gameObject == null)
|
||
{
|
||
Debug.LogError("CustomKeyboardManager.SetupInputField: inputField.gameObject is null!");
|
||
return;
|
||
}
|
||
|
||
// 检查 Instance 是否存在
|
||
if (_instance == null)
|
||
{
|
||
Debug.LogError("CustomKeyboardManager.SetupInputField: Instance is null. Make sure CustomKeyboardManager exists in scene and Awake() has been called.");
|
||
return;
|
||
}
|
||
|
||
// 禁用系统键盘
|
||
inputField.shouldHideMobileInput = true;
|
||
inputField.readOnly = true;
|
||
|
||
|
||
// 移除已有的 onSelect 事件
|
||
inputField.onSelect.RemoveAllListeners();
|
||
|
||
// 添加 onSelect 事件监听(InputField 被选中/点击时触发)
|
||
inputField.onSelect.AddListener((text) =>
|
||
{
|
||
if (Instance != null)
|
||
{
|
||
Instance.ShowKeyboard(inputField, layout, onConfirm, clearOnFirstInput);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("CustomKeyboardManager Instance is null when trying to show keyboard.");
|
||
}
|
||
});
|
||
|
||
// 确保 InputField 本身不响应点击激活
|
||
var eventTrigger = inputField.GetComponent<UnityEngine.EventSystems.EventTrigger>();
|
||
if (eventTrigger != null)
|
||
{
|
||
Destroy(eventTrigger);
|
||
}
|
||
}
|
||
}
|