1. 项目概述一个手游开发中的“老大难”问题做手游开发尤其是涉及用户交互的社交、MMO或者模拟经营类游戏输入框被虚拟键盘遮挡几乎是每个开发者都会踩的坑。你精心设计的UI在PC模拟器上完美无瑕一旦真机运行用户点击输入框键盘“唰”地弹起你的输入框就消失在了屏幕之外用户只能“盲打”。这不仅仅是体验问题简直是灾难。这个问题之所以棘手是因为移动设备屏幕尺寸、分辨率、宽高比、键盘高度不同输入法、不同系统版本都可能不同存在巨大的碎片化。Unity作为一个跨平台引擎其默认的UI系统UGUI在处理这种动态、外部的布局挤压时并没有提供一个开箱即用的“一键防遮挡”方案。这个项目要解决的就是在Unity手游中实现一个健壮、自适应的输入框防键盘遮挡方案。核心目标不是简单地“把输入框顶上去”而是构建一个能自动感知键盘状态、动态调整UI布局、并确保操作流畅自然的解决方案。它需要兼容不同分辨率的设备适应横屏/竖屏并且对现有UI的侵入性要小。网上有很多零散的代码片段但要么只解决了部分场景要么在复杂UI嵌套下会失效。我将分享一套经过多个上线项目验证的完整方案从原理分析到代码实现再到避坑指南让你在5分钟内理解核心并拥有可直接复用的完整代码。2. 核心思路与方案选型为什么不能简单移动InputField在深入代码之前我们必须先理清思路。很多新手的第一反应是监听输入框焦点事件当键盘弹出时把输入框的RectTransform往上移动一点。这个想法很直接但为什么在实际项目中往往行不通呢2.1 简单移动方案的致命缺陷键盘高度不确定iOS和Android的键盘高度并不固定不同型号手机、不同输入法、甚至系统键盘的不同模式如数字键盘、全键盘、表情键盘高度都不同。你无法写死一个偏移量。UI层级与裁剪问题如果你的输入框在一个Scroll View滚动视图里或者父节点有Mask遮罩组件简单移动输入框本身可能会导致它被父节点裁剪掉或者破坏Scroll View的滚动逻辑。交互连贯性缺失仅仅移动输入框输入框下方的按钮、其他UI元素位置不变会导致整个界面布局错乱视觉上非常突兀。多输入框场景复杂当界面中存在多个可输入的字段时如何协调它们的移动会不会互相冲突因此我们的方案必须是一个基于整个Canvas画布或特定布局区域的、动态的、可测量的调整策略。2.2 主流方案对比与选型业内常见的解决方案主要有以下几种TouchScreenKeyboard.area方案 (局限性大)原理Unity的TouchScreenKeyboard类提供了一个area属性据说可以获取键盘在屏幕上的区域。缺点这个属性在大多数Android和iOS设备上并不可靠经常返回不准确的值或全零矩形官方文档也暗示其支持有限。不推荐作为核心依赖。RectTransformUtility.ScreenPointToLocalPointInRectangle探测方案 (推荐核心)原理这是最可靠的方法。我们不需要直接知道键盘多高而是需要知道“键盘弹起后屏幕底部还有多少空间是安全、可用的”。我们可以通过检测屏幕底部特定点是否被键盘遮挡来反推安全区域。流程在Canvas下创建一个位于屏幕最底部的探测点一个Anchor到底部的RectTransform。当键盘弹出时使用Unity的Screen.height和Keyboard.height(来自TouchScreenKeyboard或IME事件) 计算屏幕被占用的像素高度然后动态调整Canvas的“偏移量”或者调整整个UI布局容器的底部锚点。优点准确、与设备无关、逻辑清晰。修改Canvas的Render Mode或使用Camera方案 (特定场景)原理将Canvas的Render Mode设置为Screen Space - Camera然后当键盘弹出时调整Camera的视口Viewport Rect或位置相当于把整个UI渲染区域“压缩”了。缺点影响所有UI元素可能干扰其他UI特效如粒子系统实现稍复杂。更适合全屏UI都需要适配的场景。我们的选型基于可靠性、易用性和对现有项目改动最小的原则我们选择方案2作为核心。我们将创建一个名为KeyboardAdaptiveLayout的管理器它自动监听键盘状态计算安全区域并驱动一个关键的UI布局组件通常是整个界面的根容器或滚动视图进行动态调整。这个方案就像一个“弹性底座”键盘压上来底座就收缩上面的UI整体就会上移完美避让。3. 核心组件解析与实现构建自适应布局管理器接下来我们一步步构建这个自适应的核心。我们将创建两个核心C#脚本KeyboardAdaptiveLayout.cs管理器和SafeAreaFitter.cs可选用于适配刘海屏等安全区域。3.1 KeyboardAdaptiveLayout 管理器这是大脑负责监听、计算和驱动。using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [RequireComponent(typeof(RectTransform))] public class KeyboardAdaptiveLayout : MonoBehaviour { // 事件键盘状态变化时触发参数是键盘高度屏幕像素 public UnityEventfloat onKeyboardHeightChanged; [Header(适配设置)] [Tooltip(用于计算偏移的Canvas如果为空则使用当前物体的Canvas)] public Canvas targetCanvas; [Tooltip(需要动态调整底部偏移的UI根节点)] public RectTransform layoutRoot; [Tooltip(额外的底部边距像素用于在键盘上方留出一点空隙)] public float bottomMargin 20f; [Tooltip(启用调试日志)] public bool debugLog false; // 内部状态 private RectTransform _rectTransform; private Canvas _canvas; private float _lastKeyboardHeight 0f; private Vector2 _originalAnchorMin; private Vector2 _originalAnchorMax; private Vector2 _originalOffsetMin; private Vector2 _originalOffsetMax; void Start() { Initialize(); } void Initialize() { _rectTransform GetComponentRectTransform(); _canvas targetCanvas ! null ? targetCanvas : GetComponentInParentCanvas(); if (_canvas null) { Debug.LogError(KeyboardAdaptiveLayout: 未找到Canvas请将其放置在Canvas下或手动指定。); enabled false; return; } // 默认使用自己作为布局根除非指定了别的 if (layoutRoot null) layoutRoot _rectTransform; // 保存原始布局信息 _originalAnchorMin layoutRoot.anchorMin; _originalAnchorMax layoutRoot.anchorMax; _originalOffsetMin layoutRoot.offsetMin; _originalOffsetMax layoutRoot.offsetMax; if (debugLog) Debug.Log(KeyboardAdaptiveLayout 初始化完成。); } void Update() { // 方法1尝试通过TouchScreenKeyboard获取高度在部分平台有效 float keyboardHeight GetKeyboardHeight(); // 方法2备选对于Android/iOS可以通过监听Input.imeCompositionMode或插件获取更准确高度 // 此处为了跨平台简化使用方法1实际项目可在此处集成平台相关代码。 if (Mathf.Abs(keyboardHeight - _lastKeyboardHeight) 1f) // 高度变化超过1像素才更新 { _lastKeyboardHeight keyboardHeight; ApplyKeyboardOffset(keyboardHeight); onKeyboardHeightChanged?.Invoke(keyboardHeight); } } float GetKeyboardHeight() { #if UNITY_ANDROID !UNITY_EDITOR // Android: 使用TouchScreenKeyboard但注意其area可能不准。 // 更佳实践是通过Android原生插件获取这里为简化使用TouchScreenKeyboard。 if (TouchScreenKeyboard.visible) { // 注意TouchScreenKeyboard.area 高度是键盘的像素高度但坐标系可能需要转换。 // 一个常见技巧是检查 area.yMin如果大于0说明键盘占用了屏幕空间。 // 这里我们采用一个更通用的估算假设键盘高度约为屏幕高度的三分之一这是一个粗略估计需要优化 return Screen.height / 3f; } return 0f; #elif UNITY_IOS !UNITY_EDITOR // iOS: 情况类似TouchScreenKeyboard.area 可能可用但也不完全可靠。 // 同样可以使用原生插件获取精确值。此处简化处理。 if (TouchScreenKeyboard.visible) { // 估算键盘高度 return TouchScreenKeyboard.area.height 10 ? TouchScreenKeyboard.area.height : Screen.height / 3f; } return 0f; #else // 在编辑器或PC平台模拟一个键盘高度用于测试 if (TouchScreenKeyboard.visible || Input.GetKey(KeyCode.K)) // 按K键模拟键盘 { return 200f; // 模拟200像素高的键盘 } return 0f; #endif } void ApplyKeyboardOffset(float keyboardHeightPixel) { if (_canvas null || layoutRoot null) return; if (keyboardHeightPixel 0) { // 键盘收起恢复原始布局 layoutRoot.anchorMin _originalAnchorMin; layoutRoot.anchorMax _originalAnchorMax; layoutRoot.offsetMin _originalOffsetMin; layoutRoot.offsetMax _originalOffsetMax; if (debugLog) Debug.Log(键盘收起布局已恢复。); return; } // 关键计算将键盘的像素高度转换为Canvas本地空间的高度 // 步骤1. 像素高度 - 转换为Canvas下的标准化高度相对于屏幕。 // 2. 根据Canvas的缩放模式Scale Factor和Render Mode进行调整。 // 简化版假设Canvas为Screen Space - Overlay且Scale Factor为1。 // 更健壮的版本需要考虑CanvasScaler。 float normalizedKeyboardHeight keyboardHeightPixel / Screen.height; // 计算需要为键盘预留的空间占父节点高度的比例 // 注意这里假设layoutRoot的锚点初始是撑满父节点的。 // 我们将layoutRoot的底部锚点向上移动相当于增加了底部内边距。 float bottomPadding normalizedKeyboardHeight (bottomMargin / Screen.height); // 修改anchorMin.y (底部锚点) 来挤压布局 // 新的底部锚点 原底部锚点 键盘占用的比例 // 因为Anchor的y值从下(0)到上(1)增加anchorMin.y就是抬高底部。 Vector2 newAnchorMin layoutRoot.anchorMin; newAnchorMin.y _originalAnchorMin.y bottomPadding; // 确保不会超出范围 newAnchorMin.y Mathf.Clamp(newAnchorMin.y, 0, layoutRoot.anchorMax.y - 0.05f); // 留最小5%高度 layoutRoot.anchorMin newAnchorMin; // offsetMin/offsetMax 通常在我们改变锚点时需要重置或者根据需求调整。 // 这里我们简单地将offsetMin.y设为0意味着底部紧贴新的锚点位置。 layoutRoot.offsetMin new Vector2(_originalOffsetMin.x, 0); // offsetMax.y保持不变顶部位置不变。 if (debugLog) Debug.Log($应用键盘偏移像素高度{keyboardHeightPixel}, 标准化高度{normalizedKeyboardHeight:F3}, 新底部锚点{newAnchorMin.y:F3}); } // 提供一个外部调用的方法用于在特定输入框聚焦时强制刷新可选 public void RefreshForInputField(InputField inputField) { // 可以在这里添加逻辑例如滚动视图确保输入框在可视区域内。 // 这需要和ScrollRect组件配合后面会讲。 Debug.Log($为输入框 {inputField.name} 刷新布局); // 立即更新一次 float kh GetKeyboardHeight(); if (kh 0) { ApplyKeyboardOffset(kh); } } void OnDestroy() { // 脚本销毁时最好恢复布局避免残留影响 if (layoutRoot ! null) { layoutRoot.anchorMin _originalAnchorMin; layoutRoot.anchorMax _originalAnchorMax; layoutRoot.offsetMin _originalOffsetMin; layoutRoot.offsetMax _originalOffsetMax; } } }代码核心解读GetKeyboardHeight方法这是获取键盘高度的关键。代码中提供了跨平台的简化版本。在实际发布项目中强烈建议为Android和iOS编写原生插件或使用可靠的Asset Store插件来获取精确的键盘高度因为TouchScreenKeyboard.area并不可靠。示例中的Screen.height / 3f只是一个粗略估计。ApplyKeyboardOffset方法这是布局调整的核心逻辑。它通过修改layoutRoot通常是你的内容面板的anchorMin.y底部锚点来动态改变其底部边界。当键盘弹起时底部锚点上移整个内容区域就被“顶”上去了。计算时需要注意像素高度到标准化比例Normalized的转换这取决于Canvas的渲染模式和缩放因子。状态保存与恢复脚本在Start时保存了layoutRoot的原始锚点和偏移值。当键盘收起时完美恢复原状这是很多简单方案忽略的一点。重要提示上述代码中的高度获取是简化版。对于生产环境你需要更精确的方法。一个可行的方案是对于Android通过AndroidJavaClass调用View.getRootView().getWindowVisibleDisplayFrame()来计算屏幕可用高度对于iOS可以使用UnityEngine.iOS.Device的相关API或编写Objective-C插件。你也可以考虑使用如Mobile Keyboard、TouchScreenKeyboard Plus等商店资源。3.2 与ScrollRect滚动视图的协同工作如果你的输入框在一个很长的表单里位于ScrollRect滚动视图中仅仅挤压底部可能还不够你需要确保当前激活的输入框自动滚动到可视区域。我们可以创建一个辅助脚本AutoScrollToInputField.cs挂载在ScrollRect上。using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; public class AutoScrollToInputField : MonoBehaviour, ISelectHandler { [Tooltip(关联的ScrollRect)] public ScrollRect scrollRect; [Tooltip(滚动时的额外偏移像素)] public float verticalOffset 50f; private RectTransform _scrollContent; private Canvas _canvas; void Start() { if (scrollRect null) scrollRect GetComponentInParentScrollRect(); if (scrollRect ! null) _scrollContent scrollRect.content; _canvas GetComponentInParentCanvas(); } // 当输入框被选中获得焦点时由EventSystem调用 public void OnSelect(BaseEventData eventData) { ScrollToMe(); } void ScrollToMe() { if (scrollRect null || _scrollContent null || _canvas null) return; RectTransform myRect GetComponentRectTransform(); // 将输入框在ScrollContent中的位置转换到ScrollRect视口的位置 Vector3[] myCorners new Vector3[4]; Vector3[] contentCorners new Vector3[4]; Vector3[] viewportCorners new Vector3[4]; myRect.GetWorldCorners(myCorners); _scrollContent.GetWorldCorners(contentCorners); scrollRect.viewport.GetWorldCorners(viewportCorners); // 计算输入框底部在视口中的归一化位置 // 简单策略确保输入框的底部在视口中上部可见 float myBottomInViewport myCorners[0].y; // 世界空间下的y坐标底部 float viewportBottom viewportCorners[0].y; float viewportTop viewportCorners[1].y; float viewportHeight viewportTop - viewportBottom; // 输入框底部相对于视口底部的位置像素 float relativePos myBottomInViewport - viewportBottom; // 我们希望输入框底部在视口顶部往下 verticalOffset 像素的位置 float targetRelativePos viewportHeight - verticalOffset; // 计算需要滚动的归一化位置 (0-1) if (relativePos targetRelativePos) { // 输入框已经在目标区域上方或内部不需要滚动 return; } // 需要滚动计算内容需要向上移动多少在Content的本地空间 float contentHeight _scrollContent.rect.height; float viewportHeightLocal scrollRect.viewport.rect.height; // 这是一个简化计算实际中可能需要更精确的矩形相交检测 // 使用ScrollRect的verticalNormalizedPosition进行滚动 float myPosInContent (myCorners[0].y - contentCorners[0].y) / _canvas.scaleFactor; // 转换为本地像素 float targetPosInContent myPosInContent - (viewportHeightLocal - verticalOffset); // 转换为归一化位置 (从底部0到顶部1) float normalizedPos 1 - (targetPosInContent / (contentHeight - viewportHeightLocal)); normalizedPos Mathf.Clamp01(normalizedPos); // 使用缓动动画滚动体验更好 StopAllCoroutines(); StartCoroutine(SmoothScrollTo(normalizedPos, 0.2f)); } System.Collections.IEnumerator SmoothScrollTo(float targetPos, float duration) { float startPos scrollRect.verticalNormalizedPosition; float time 0; while (time duration) { time Time.deltaTime; float t time / duration; t Mathf.SmoothStep(0, 1, t); // 平滑过渡 scrollRect.verticalNormalizedPosition Mathf.Lerp(startPos, targetPos, t); yield return null; } scrollRect.verticalNormalizedPosition targetPos; } }使用方法将这个脚本挂载到你的InputField或TMP_InputField游戏对象上并为其指定所属的ScrollRect。当用户点击该输入框时OnSelect事件会触发脚本会自动计算并将该输入框滚动到ScrollRect视口中一个合适的位置默认在顶部往下50像素处。3.3 完整场景搭建与配置步骤现在让我们把所有这些组合起来创建一个可工作的预制体或场景。搭建UI结构创建一个Canvas设置Canvas Scaler的UI Scale Mode为Scale With Screen Size参考分辨率设为1080x1920竖屏或1920x1080横屏。在Canvas下创建一个空物体命名为ContentRoot将其锚点Anchors设置为拉伸全屏Stretch-Stretch左右上下偏移Pos都设为0。这个将作为我们动态调整的layoutRoot。在ContentRoot下添加你的UI内容例如背景、标题、一个带有ScrollRect的长表单表单里包含多个InputField。配置自适应管理器在Canvas对象或ContentRoot对象上添加KeyboardAdaptiveLayout组件。将Layout Root拖拽赋值给ContentRoot对象。可选在On Keyboard Height Changed事件上添加监听用于播放音效或更新其他逻辑。配置输入框自动滚动为ScrollRect内容区域内的每一个InputField都添加AutoScrollToInputField组件。将每个AutoScrollToInputField的Scroll Rect变量拖拽赋值给对应的ScrollRect组件。测试在Unity编辑器中运行选中一个输入框按K键根据我们在GetKeyboardHeight编辑器部分的代码模拟键盘弹出。你应该能看到整个ContentRoot的内容被向上推动。如果输入框在ScrollRect中点击不同的输入框视图应该自动平滑滚动到该输入框的位置。4. 平台特定优化与深度避坑指南上面的方案提供了一个坚实的框架但要达到生产级鲁棒性还需要考虑以下平台细节和常见陷阱。4.1 Android平台的键盘高度精确获取如前所述TouchScreenKeyboard.area在Android上基本无效。以下是使用Android原生API获取键盘高度的示例方法创建一个AndroidManifest.xml文件如果还没有确保你的Activity是UnityPlayerActivity的子类。编写一个Android Java插件类。创建一个新的Android库模块或直接编写Java代码通过getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener()监听全局布局变化计算getWindowVisibleDisplayFrame与屏幕高度的差值即为键盘高度。在Unity中通过AndroidJavaClass和AndroidJavaObject调用这个插件方法。简化C#端调用示例需配合Java插件public static float GetAndroidKeyboardHeight() { if (Application.platform ! RuntimePlatform.Android) return 0f; try { using (AndroidJavaClass unityClass new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (AndroidJavaObject currentActivity unityClass.GetStaticAndroidJavaObject(currentActivity)) using (AndroidJavaObject rootView currentActivity.CallAndroidJavaObject(getWindow).CallAndroidJavaObject(getDecorView)) { // 这里假设你的Java插件有一个静态方法 getKeyboardHeight // 实际你需要自己实现这个Java类。 AndroidJavaClass keyboardHelper new AndroidJavaClass(com.yourcompany.keyboardhelper.KeyboardHeightProvider); return keyboardHelper.CallStaticfloat(getKeyboardHeight, currentActivity, rootView); } } catch (System.Exception e) { Debug.LogError(获取Android键盘高度失败: e.Message); return 0f; } }实操心得实现原生键盘高度获取是移动端Unity开发的一个小门槛。如果项目时间紧可以考虑使用Asset Store上评价较高的现成插件它们通常已经处理好Android和iOS的兼容性问题性价比很高。4.2 iOS平台的注意事项iOS的情况稍好TouchScreenKeyboard.area在某些情况下可能返回值但依然不推荐完全依赖。iOS的键盘会有动画我们的布局调整最好也能以动画形式跟随避免生硬的跳变。可以修改ApplyKeyboardOffset方法使用Mathf.Lerp或DOTween等动画插件在几帧内平滑过渡到目标位置。另外iOS上有**安全区域Safe Area**的问题特别是带有刘海或Home Indicator的全面屏设备。键盘弹出时安全区域的下边界会发生变化。我们的方案需要和Safe Area适配结合。4.3 安全区域Safe Area适配全面屏设备需要避开刘海和底部Home Indicator区域。Unity自带的SafeAreaFitter2019.3或第三方组件可以解决。我们需要确保键盘适配与安全区域适配不冲突。推荐做法创建一个UI层次结构如下Canvas ├── SafeAreaAdapter (负责适配刘海/圆角) │ └── ContentRoot (KeyboardAdaptiveLayout挂在这里负责适配键盘) │ └── 你的所有UI内容这样SafeAreaAdapter先处理好物理安全区域然后KeyboardAdaptiveLayout在此基础上再处理键盘的挤压。两者修改的是不同的RectTransform互不干扰。4.4 横屏游戏的特殊处理对于横屏游戏键盘通常从屏幕右侧或底部弹出取决于设备方向。我们的方案核心是计算“被占用的屏幕空间”。在横屏模式下你需要判断键盘是从哪一侧弹出的通常是底部短边然后相应地调整anchorMin.x或anchorMin.y。GetKeyboardHeight方法需要返回的是在当前屏幕方向下键盘所占用的像素宽度或高度。修改思路在ApplyKeyboardOffset中不仅计算keyboardHeightPixel还要判断当前屏幕方向Screen.orientation和键盘可能弹出的位置这可能需要平台特定代码然后决定是修改垂直锚点anchorMin.y还是水平锚点anchorMin.x。4.5 常见问题与排查技巧实录即使按照上述步骤你可能还是会遇到一些诡异的问题。以下是我在实际项目中踩过的坑和解决方案问题1布局调整后UI元素位置错乱或闪烁。原因很可能是在同一帧内有多个脚本在修改同一个RectTransform的锚点或位置例如你的自适应脚本和某个动画脚本。也可能是因为Canvas在布局调整的同一帧发生了重建Canvas.BuildBatch。解决确保KeyboardAdaptiveLayout在Update或LateUpdate中执行并且是唯一直接修改layoutRoot锚点的脚本。尝试在修改布局前调用LayoutRebuilder.ForceRebuildLayoutImmediate强制重建一次布局然后再进行我们的锚点调整有时能解决子布局元素错位问题。如果使用动画确保动画结束时将布局控制权交还给自适应脚本。问题2在ScrollRect中自动滚动后输入框位置还是不对。原因AutoScrollToInputField中的坐标转换计算可能不准确特别是当Canvas有缩放、或者ScrollRect的Content下还有复杂嵌套布局时。解决使用RectTransformUtility类提供的CalculateRelativeRectTransformBounds和RectangleContainsScreenPoint等方法进行更精确的矩形包含判断。简化计算核心目标是让输入框的世界坐标底部高于键盘顶部一定距离。可以尝试更粗暴但有效的方法获取输入框底部世界坐标worldBottom获取键盘顶部世界坐标可通过屏幕底部键盘高度估算如果worldBottom低于键盘顶部则计算需要滚动的距离。在ScrollToMe方法中增加调试绘制用Debug.DrawLine画出计算出的视口和输入框边界直观查看问题所在。问题3键盘收起后布局没有恢复或者恢复的位置不对。原因GetKeyboardHeight方法在键盘收起后没有立即返回0或者返回0的过程有延迟。也可能是其他逻辑如焦点切换干扰了恢复过程。解决不要只依赖TouchScreenKeyboard.visible。可以增加一个延迟判断如果检测到的键盘高度连续N帧如3帧小于一个阈值如10像素才认为键盘真正收起然后执行恢复。在OnDestroy和OnDisable中强制恢复布局确保场景切换或对象禁用时状态正确。监听输入框的onEndEdit事件作为键盘可能收起的辅助判断。问题4低端设备上布局调整导致明显卡顿。原因每帧都在Update中计算和修改布局可能触发Canvas的批量重建在低端设备上开销较大。优化增加一个阈值只有键盘高度变化超过一定像素如5像素才触发ApplyKeyboardOffset。将布局调整的计算放在LateUpdate中避免一帧内多次修改。如果键盘状态稳定可以考虑在调整完成后禁用KeyboardAdaptiveLayout组件的Update直到再次检测到键盘高度变化。但这需要小心处理避免错过状态变化。问题5与UI动画如Dotween冲突。原因Dotween在动画过程中会持续修改RectTransform的属性与我们的脚本产生写冲突。解决优先级排序确定在键盘弹出时布局适配的优先级高于普通UI动画。可以在键盘弹出时杀死Kill所有正在影响layoutRoot位置或锚点的Tween动画。协作模式设计你的UI动画系统使其感知键盘状态。当KeyboardAdaptiveLayout触发onKeyboardHeightChanged事件时UI动画应该以此为新基准进行。一个实用的调试技巧在编辑器里创建一个简单的测试UI包含一个全屏背景、几个位于不同高度的输入框和我们的自适应脚本。使用一个滑块Slider动态模拟键盘高度并将其值直接传递给ApplyKeyboardOffset方法。这样可以非常方便地可视化调整效果而无需反复连接真机或模拟键盘弹出。