using System; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; /// /// 自定义键盘管理器 - 处理任意 TMP_InputField 的键盘弹出 /// 支持不同键盘布局:拼音(中文)、数字、字母等 /// 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 _onConfirm; private Canvas _keyboardCanvas; // 键盘的 Canvas 组件 void Awake() { if (_instance == null) { _instance = this; // 启动拼音词库加载 StartCoroutine(PinyinInputManager.Initialize()); } else if (_instance != this) { Destroy(gameObject); } } /// /// 为 InputField 显示自定义键盘 /// /// 目标输入框 /// 键盘布局类型 /// 确认回调(可选) /// 首次输入时是否清空原内容 public void ShowKeyboard(TMP_InputField inputField, KeyboardLayout layout, Action onConfirm = null, bool clearOnFirstInput = false) { if (inputField == null) return; // 如果已有键盘,先隐藏(这会清空 _targetInputField) HideKeyboard(); // 在隐藏旧键盘后再设置新的目标(避免被 HideKeyboard 清空) _targetInputField = inputField; _onConfirm = onConfirm; // 创建键盘面板 _currentKeyboardPanel = CreateKeyboardPanel(); _currentKeyboard = _currentKeyboardPanel.GetComponent(); if (_currentKeyboard != null) { // 使用适配器模式让 CustomKeyboard 直接修改 InputField var adapter = _currentKeyboardPanel.AddComponent(); 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(); // 取消激活状态 } /// /// 根据键盘布局自动计算大小 /// private void AdjustKeyboardSize(KeyboardLayout layout) { if (_currentKeyboardPanel == null || _currentKeyboard == null) return; var panelRT = _currentKeyboardPanel.GetComponent(); 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}"); } /// /// 获取当前键盘所需的行数(用于计算键盘高度) /// 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; } /// /// 调整键盘位置避免遮挡输入框 /// private void AdjustKeyboardPosition(TMP_InputField inputField) { if (_currentKeyboardPanel == null || inputField == null) return; var panelRT = _currentKeyboardPanel.GetComponent(); var inputRT = inputField.GetComponent(); 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; } } /// /// 隐藏当前键盘 /// public void HideKeyboard() { if (_currentKeyboardRoot != null) { Destroy(_currentKeyboardRoot); _currentKeyboardRoot = null; _currentKeyboard = null; _keyboardCanvas = null; } else if (_currentKeyboardPanel != null) { Destroy(_currentKeyboardPanel); } _currentKeyboardPanel = null; _targetInputField = null; } /// /// 确认输入 /// public void ConfirmInput() { if (_targetInputField != null) { _onConfirm?.Invoke(_targetInputField.text); } HideKeyboard(); } /// /// 设置键盘位置 /// public void SetKeyboardPosition(Vector2 position) { if (_currentKeyboardPanel != null) { var panelRT = _currentKeyboardPanel.GetComponent(); if (panelRT != null) { panelRT.anchoredPosition = position; } } } /// /// 设置键盘层级 /// public void SetKeyboardSortingOrder(int sortingOrder) { if (_keyboardCanvas != null) { _keyboardCanvas.sortingOrder = sortingOrder; } } /// /// 获取当前键盘是否显示 /// public bool IsKeyboardShowing() { return _currentKeyboardRoot != null && _currentKeyboardRoot.activeInHierarchy; } /// /// 创建键盘面板 /// 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(); rootRT.anchorMin = Vector2.zero; rootRT.anchorMax = Vector2.one; rootRT.offsetMin = Vector2.zero; rootRT.offsetMax = Vector2.zero; // 添加 Canvas 以控制层级(在 DialogLayer 之上) _keyboardCanvas = rootGO.AddComponent(); _keyboardCanvas.overrideSorting = true; _keyboardCanvas.sortingOrder = 350; // 高于 DialogLayer (300) 但低于 SystemLayer (400) rootGO.AddComponent(); var blockerGO = new GameObject("KeyboardOutsideDismissArea", typeof(RectTransform), typeof(Image), typeof(EventTrigger)); blockerGO.transform.SetParent(rootGO.transform, false); var blockerRT = blockerGO.GetComponent(); blockerRT.anchorMin = Vector2.zero; blockerRT.anchorMax = Vector2.one; blockerRT.offsetMin = Vector2.zero; blockerRT.offsetMax = Vector2.zero; var blockerImage = blockerGO.GetComponent(); blockerImage.color = new Color(0f, 0f, 0f, 0f); blockerImage.raycastTarget = true; var blockerTrigger = blockerGO.GetComponent(); 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(); 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(); panelImg.color = new Color(0.08f, 0.1f, 0.12f, 0.95f); // 添加圆角和阴影效果(可选,让键盘更美观) var shadow = panelGO.AddComponent(); 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(); 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(); grid.cellSize = KeySize; grid.spacing = KeySpacing; grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount; grid.constraintCount = 10; // 每行10个按键 // 添加 CustomKeyboard 脚本 var keyboard = panelGO.AddComponent(); keyboard.KeysContainer = containerGO.transform; keyboard.KeySize = KeySize; keyboard.KeySpacing = KeySpacing; return panelGO; } /// /// 为 InputField 设置点击监听 /// public static void SetupInputField(TMP_InputField inputField, KeyboardLayout layout, Action 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(); if (eventTrigger != null) { Destroy(eventTrigger); } } }