Unity UGUI虚拟键盘开发指南:从架构设计到移动端优化
1. 项目概述为什么我们需要自制虚拟键盘在Unity开发移动端或触屏应用时系统软键盘System Keyboard是个让人又爱又恨的存在。爱它是因为它开箱即用无需额外开发恨它则是因为它带来的体验割裂感实在太强了。想象一下你精心设计了一款赛博朋克风格的UI结果用户点击输入框屏幕上突然弹出一个风格迥异的安卓或iOS原生键盘那种沉浸感瞬间就被打破了。更别提原生键盘在不同设备上样式不一、遮挡UI、难以自定义输入规则等问题。这就是我决定动手自制一套UGUI虚拟键盘的初衷。它不仅仅是一个UI组件更是提升应用整体沉浸感和交互一致性的关键。通过UGUI的Canvas系统我们可以完全掌控键盘的视觉风格、动画效果、按键布局甚至实现一些原生键盘做不到的“骚操作”比如一键输入预设短语、集成游戏内特殊符号、或者根据当前输入框类型动态切换键盘布局数字键盘、邮箱键盘、密码键盘等。这个项目适合所有使用Unity开发触屏应用的开发者无论是移动游戏、车载中控、工业平板还是教育类应用。如果你正在为系统键盘破坏UI风格而烦恼或者需要实现更复杂的输入逻辑那么这套自研方案将为你提供一套从设计到实现、附赠完整C#源码的“交钥匙”工程。2. 核心设计思路与架构拆解自制虚拟键盘听起来简单但要想做得健壮、易用、可扩展需要在设计之初就考虑清楚几个核心问题。我的设计思路可以概括为“高内聚、低耦合、数据驱动”。2.1 模块化分层架构我采用了典型的三层架构来组织代码确保各司其职方便维护和扩展。1. 表现层View Layer这是用户直接看到和交互的部分完全由UGUI的GameObject构成。核心是一个Canvas下的键盘面板上面排列着许多Button预制体作为按键。每个按键上挂载了Image背景、Text或TextMeshPro显示字符以及一个我们自定义的KeyButton脚本用于处理点击事件。这一层只关心“怎么显示”和“接收点击”不处理任何输入逻辑。2. 逻辑控制层Controller Layer这是整个键盘的大脑我将其封装在一个名为VirtualKeyboardController的单例类中。它的核心职责包括管理输入焦点记录当前哪个InputField或TMP_InputField处于激活状态。处理按键事件接收来自表现层KeyButton的点击判断按键类型字符、退格、回车、切换大小写等并执行相应操作。维护键盘状态如当前是大写模式还是小写模式是主键盘布局还是数字符号布局。控制键盘动画处理键盘的弹出、收起、按键按下反馈等动画。3. 数据模型层Model Layer这部分定义了键盘的布局和映射关系。我设计了一个KeyboardLayout的ScriptableObject资产。你可以为“英文小写”、“英文大写”、“数字符号”、“邮箱专用”等不同模式创建不同的Layout资产。每个Layout资产本质上是一个字符矩阵定义了每一行有哪些键以及每个键在正常状态、Shift状态下的显示文本和输出值。通过ScriptableObject策划或UI设计师可以在不修改代码的情况下轻松调整键盘布局实现了数据与逻辑的分离。2.2 输入事件传递机制输入事件的流向是Button.OnClick() - KeyButton.OnKeyPressed() - VirtualKeyboardController.ProcessKey() - 当前焦点的InputField。这里的关键是控制器并不直接操作InputField的文本而是通过EventSystem模拟一个输入事件或者直接调用InputField的公有方法如Append、Backspace。这样做的好处是兼容Unity原生的输入框以及TextMeshPro的输入框因为它们都接入了相同的事件系统。2.3 与系统输入框的协同自制键盘必须与场景中的InputField无缝协作。我的方案是通过事件监听来实现。在VirtualKeyboardController初始化时会为所有InputField或通过一个Tag/特定组件来标记添加事件监听当输入框被选中OnSelect时控制器记录它为当前焦点并自动弹出虚拟键盘当输入框失去焦点或用户点击键盘外的关闭按钮时键盘自动收起。这个过程对原有输入框逻辑是零侵入的。3. 核心组件详解与UGUI实现要点有了清晰的架构接下来我们深入每个核心组件的实现细节。这里会包含大量你在官方文档里找不到的实操心得和避坑指南。3.1 键盘面板与按键的UGUI搭建Canvas设置要点虚拟键盘的Canvas渲染模式我推荐使用Screen Space - Overlay并确保其Sort Order设置得比游戏主UI的Canvas更高这样键盘才能始终显示在最顶层。同时要为这个Canvas单独设置一个Graphic Raycaster并合理配置其Blocking Objects和Blocking Mask防止键盘背后的UI元素误接收点击事件。按键预制体设计一个标准的按键预制体通常包含以下元素Button组件核心交互组件。需要仔细配置其Transition过渡效果。我强烈推荐使用Animation过渡而非简单的颜色变换。你可以为Normal、Highlighted、Pressed、Disabled状态制作细微的缩放、颜色或位移动画这能极大提升按键的触感。Image组件作为按键背景。建议使用九宫格Sliced类型的Sprite这样在按键缩放时边角不会失真。Text/TextMeshPro - Text组件用于显示键帽字符。务必使用TextMeshPro它比传统的Text拥有更好的清晰度和字体效果尤其是在高分辨率屏幕上。KeyButton脚本自定义这个脚本挂在Button上它有一个KeyCode或字符串类型的字段用于标识这个按键按下后代表什么。它监听Button的onClick事件然后调用VirtualKeyboardController.Instance.OnKeyPressed(this)。实操心得按键触感优化移动端触屏没有物理反馈因此视觉和听觉反馈至关重要。除了为Button设置Animation过渡我还会在KeyButton脚本的点击事件中触发一个简短的音效如“咔哒”声和轻微的屏幕震动Handheld.Vibrate需谨慎使用并给用户设置开关。此外可以将按键的按下动画时间设置得非常短如0.05秒营造出灵敏的响应感。3.2 VirtualKeyboardController 核心逻辑实现这个类是中枢代码会稍多我们拆解几个关键函数。// 伪代码示例展示核心结构 public class VirtualKeyboardController : MonoBehaviour { public static VirtualKeyboardController Instance; [SerializeField] private InputField _currentInputField; // 当前获得焦点的输入框 [SerializeField] private KeyboardLayout _currentLayout; // 当前键盘布局 [SerializeField] private Animator _keyboardAnimator; // 控制弹出收起动画 private bool _isCapsLock false; void Awake() { if (Instance null) Instance this; else Destroy(gameObject); DontDestroyOnLoad(gameObject); // 如果希望键盘全局存在 HideKeyboardImmediate(); } // 由InputField的OnSelect事件调用 public void OnInputFieldSelected(InputField inputField) { if (_currentInputField ! inputField) { _currentInputField inputField; ShowKeyboard(); // 可以根据inputField的contentType如Number, EmailAddress切换不同的键盘布局 SwitchLayoutBasedOnInputType(inputField.contentType); } } // 由KeyButton脚本调用 public void ProcessKey(KeyButton keyButton) { if (_currentInputField null) return; string inputValue keyButton.GetOutputValue(_isCapsLock); // 获取按键在当前状态下的输出值 switch (keyButton.KeyType) { case KeyType.Character: _currentInputField.text inputValue; // 直接追加字符 // 或者使用事件系统模拟输入兼容性更好 // ExecuteEvents.Execute(_currentInputField.gameObject, new BaseEventData(EventSystem.current), ExecuteEvents.submitHandler); break; case KeyType.Backspace: if (!string.IsNullOrEmpty(_currentInputField.text)) { _currentInputField.text _currentInputField.text.Substring(0, _currentInputField.text.Length - 1); } break; case KeyType.Enter: _currentInputField.OnSubmit(new BaseEventData(EventSystem.current)); // 触发提交事件 HideKeyboard(); break; case KeyType.Shift: _isCapsLock !_isCapsLock; UpdateAllKeysAppearance(); // 刷新所有按键的显示大小写 break; case KeyType.Space: _currentInputField.text ; break; // ... 其他功能键 } // 触发输入框的onValueChanged事件确保其他监听文本变化的逻辑能执行 _currentInputField.onValueChanged?.Invoke(_currentInputField.text); } private void ShowKeyboard() { _keyboardAnimator.SetTrigger(Show); // 将键盘位置调整到输入框下方这里需要计算屏幕坐标 // 可以使用RectTransformUtility或直接设置anchoredPosition } private void HideKeyboard() { _keyboardAnimator.SetTrigger(Hide); _currentInputField null; } }3.3 灵活可配的KeyboardLayout设计使用ScriptableObject来定义布局是本项目的一大亮点。你可以在Project窗口右键创建KeyboardLayout。[CreateAssetMenu(fileName New Keyboard Layout, menuName UI/Keyboard Layout)] public class KeyboardLayout : ScriptableObject { [System.Serializable] public class KeyRow { public ListKeyDefinition keys; } [System.Serializable] public class KeyDefinition { public string primaryDisplay; // 正常显示的字符如 a public string primaryValue; // 正常输出的值如 a public string shiftDisplay; // Shift下的显示字符如 A public string shiftValue; // Shift下的输出值如 A public KeyType keyType KeyType.Character; } public ListKeyRow rows; // 一个列表代表键盘的一行行按键 }在编辑器中你可以像填充二维数组一样可视化地配置每个按键在不同状态下的显示和输出。VirtualKeyboardController在初始化或切换布局时会根据这个KeyboardLayout数据动态实例化或更新按键预制体上的KeyButton脚本信息。4. 完整集成与优化实操流程现在让我们把各个部分组装起来并解决实际集成中会遇到的问题。4.1 步骤一搭建场景与UI创建一个新的Canvas命名为VirtualKeyboardCanvas。设置Render Mode为Screen Space - OverlaySort Order设为一个较大的值如9999。在该Canvas下创建一个Panel作为键盘背景命名为KeyboardPanel。为其添加一个Animator组件并创建两个动画状态Show从屏幕下方滑入和Hide滑出屏幕下方。在KeyboardPanel下创建多个水平布局组Horizontal Layout Group每个代表键盘的一行。在这些布局组下实例化你的按键预制体。创建一个空物体挂载VirtualKeyboardController脚本并将KeyboardPanel和其Animator拖拽赋值。4.2 步骤二配置输入框与键盘的联动你需要一个方式让输入框被点击时通知键盘控制器。我推荐创建一个辅助脚本InputFieldFocusHelper。public class InputFieldFocusHelper : MonoBehaviour, ISelectHandler { private InputField _inputField; private TMP_InputField _tmpInputField; // 如果你也用TextMeshPro void Awake() { _inputField GetComponentInputField(); _tmpInputField GetComponentTMP_InputField(); } // 当输入框被选中时包括通过代码.Select() public void OnSelect(BaseEventData eventData) { // 优先通知虚拟键盘 if (VirtualKeyboardController.Instance ! null) { // 传递自己让控制器知道该往哪个输入框输入 VirtualKeyboardController.Instance.OnInputFieldSelected(this); } // 这里可以**选择性**地屏蔽系统键盘 // 对于移动端可以设置 inputField.shouldHideMobileInput true; // 但注意这可能会影响某些设备的体验需要测试。 } // 提供一个统一的接口给键盘控制器获取文本或设置文本 public string Text { get { if (_tmpInputField ! null) return _tmpInputField.text; if (_inputField ! null) return _inputField.text; return string.Empty; } set { if (_tmpInputField ! null) _tmpInputField.text value; else if (_inputField ! null) _inputField.text value; } } }将这个脚本挂载到所有需要调用自制键盘的InputField或TMP_InputField上。这样当用户点击输入框时OnSelect事件会触发从而调用键盘控制器的OnInputFieldSelected方法。4.3 步骤三处理移动端特殊性与性能优化屏蔽系统键盘在移动平台Android/iOSUnity的InputField默认会触发系统软键盘。为了完全使用自制键盘我们需要阻止它。可以在InputFieldFocusHelper的OnSelect方法中或者在VirtualKeyboardController的ShowKeyboard方法里添加以下代码// 对于传统InputField if (_currentInputField ! null) { #if UNITY_ANDROID || UNITY_IOS _currentInputField.shouldHideMobileInput true; // 尝试隐藏移动端系统输入 #endif // 同时让其失去焦点防止光标闪烁因为系统键盘不弹出了但输入框可能还有焦点状态 // 这是一个Hack需要根据实际情况调整 // StartCoroutine(DeselectInputFieldCoroutine()); }重要避坑指南移动端输入框焦点问题设置shouldHideMobileInput true并非在所有设备和Unity版本上都百分百有效。有时系统键盘还是会闪一下。一个更彻底的方案是在输入框被选中时立刻禁用它interactable false然后由我们的虚拟键盘完全接管文本的增删改查。这需要你的VirtualKeyboardController能完全模拟输入框的所有行为包括光标位置、文本选择等实现复杂度会高很多但体验最完美。对于大多数不需要复杂文本编辑的应用直接操作text属性并触发onValueChanged事件已经足够。性能优化键盘通常包含几十个按钮如果每次弹出都实例化会造成GC垃圾回收压力。我的做法是对象池化在VirtualKeyboardController初始化时就根据KeyboardLayout实例化好所有按键的GameObject并设置为禁用状态。显示键盘时只是激活它们并更新显示文本而不是动态创建。避免每帧更新不要在Update里做任何事情。所有状态更新都基于事件驱动按键点击、布局切换。合批优化确保键盘面板和所有按键都在同一个Canvas下并且使用相同的材质和纹理图集Atlas。将键盘用到的所有UI精灵打包进一个图集可以极大减少Draw Call。5. 常见问题排查与进阶技巧在实际使用中你肯定会遇到一些“坑”。下面是我踩过之后总结出来的速查表。问题现象可能原因解决方案点击输入框自制键盘和系统键盘同时弹出1.shouldHideMobileInput未设置或失效。2. 输入框在自制键盘弹出后仍然持有焦点。1. 确保在移动平台设置了inputField.shouldHideMobileInput true。2. 在ShowKeyboard后调用EventSystem.current.SetSelectedGameObject(null)清空事件系统焦点。键盘按键点击无反应1. 按键的Raycast被遮挡。2.KeyButton脚本事件未正确绑定到VirtualKeyboardController。3.VirtualKeyboardController实例为null。1. 检查键盘Canvas的Graphic Raycaster设置以及是否有其他全屏UI遮挡了射线。2. 调试KeyButton的OnClick事件是否触发并检查传递的控制器引用是否正确。3. 确保VirtualKeyboardController脚本已挂载在场景中且单例初始化成功。输入文本时光标不移动或行为怪异自制键盘直接修改inputField.text未模拟光标位置。对于需要光标控制的高级场景需要获取InputField的caretPosition并在插入/删除字符后更新它。或者考虑使用InputField的ProcessEvent接口来发送Event.KeyboardEvent模拟真实键盘输入这能更好地与输入框内部状态同步。键盘在特定屏幕分辨率下位置错乱键盘的锚点Anchors和位置Position设置不当。使用RectTransform的锚点来定位键盘。例如将键盘Panel的锚点设置为底部拉伸Bottom-Stretch这样它的宽度会适应屏幕且底边对齐屏幕底部。弹出动画改为控制其anchoredPosition.y从负值屏幕外移动到0。切换场景后键盘丢失或报空引用VirtualKeyboardController是场景内对象切换场景时被销毁。在Awake方法中使用DontDestroyOnLoad(this.gameObject)使其常驻。同时在新场景中所有需要键盘的输入框其InputFieldFocusHelper脚本需要重新找到这个全局实例进行绑定。进阶技巧实现滑动输入和震动反馈滑动输入Swype这属于高阶功能。你可以在KeyboardPanel上添加一个Image组件并设置为完全透明然后为其添加IBeginDragHandler,IDragHandler,IEndDragHandler接口。在拖拽过程中根据手指位置eventData.position通过GraphicRaycaster检测其下有哪些KeyButton并高亮它们。在结束拖拽时将划过的一系列按键字符组合成词库中可能的单词。这需要额外的词库和算法支持。更细腻的触觉反馈除了简单的Handheld.Vibrate()一些高端安卓设备支持AndroidVibration类UnityEngine.Android命名空间进行不同强度和时长的震动。iOS则可以通过iOSHapticFeedback来实现原生触感引擎的反馈如Impact、Notification类型。实现时记得封装一个工具类并检查平台支持和用户设置。自制UGUI虚拟键盘是一个典型的“麻雀虽小五脏俱全”的项目。它考验了你对UGUI事件系统、动画系统、移动端适配以及代码架构设计的综合理解。当你看到自己设计的键盘完美融入应用界面并带来流畅统一的输入体验时那种成就感远非调用系统API可比。这套方案我已经在多个商业项目中稳定使用源码也经过多次重构具备了良好的可读性和扩展性。你可以直接拿过去作为基础根据自己项目的视觉风格和功能需求进行定制比如改成圆形按键、添加动态背景光效或者集成到你的UI框架中。记住好的工具都是打磨出来的多测试、多思考用户体验的细节你的虚拟键盘就能从“能用”变得“好用”。

相关新闻

抖音批量下载神器:3分钟掌握高效素材收集的终极指南

抖音批量下载神器:3分钟掌握高效素材收集的终极指南

抖音批量下载神器:3分钟掌握高效素材收集的终极指南 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support…

2026/10/18 22:17:50 阅读更多 →
如何轻松实现Zepp Life步数自动化同步:完整的新手指南

如何轻松实现Zepp Life步数自动化同步:完整的新手指南

如何轻松实现Zepp Life步数自动化同步:完整的新手指南 【免费下载链接】mimotion 小米运动刷步数(微信支付宝)支持邮箱登录 项目地址: https://gitcode.com/gh_mirrors/mimo/mimotion 你是否厌倦了每天手动记录步数?是否希…

2026/10/17 4:46:55 阅读更多 →
清华IT系Java+Python双语言教程解析与推荐

清华IT系Java+Python双语言教程解析与推荐

1. 为什么清华IT系的JavaPython视频教程值得推荐? 作为一名在IT行业摸爬滚打多年的开发者,我深知选择一套好的编程教程有多重要。市面上充斥着大量质量参差不齐的教程资源,要么过于基础浅尝辄止,要么晦涩难懂直接劝退新手。清华IT…

2026/10/18 9:19:28 阅读更多 →

最新新闻

YOLOv8n电梯电瓶车检测实战:真实数据+轻量部署+声光报警

YOLOv8n电梯电瓶车检测实战:真实数据+轻量部署+声光报警

简介:本资源是一套基于YOLOv8的电梯场景电瓶车闯入智能识别与报警系统,面向计算机、人工智能、自动化等专业在校学生及初学者,解决公共场所安全监管中对违规载电动车入梯行为的实时检测与预警需求,特别适合作为毕业设计、课程设计…

2026/10/19 6:18:45 阅读更多 →
万亿参数强化学习后训练:如何用10%GPU资源跑通?

万亿参数强化学习后训练:如何用10%GPU资源跑通?

1. 为什么“万亿参数 强化学习”是后训练里最昂贵的组合先把结论放这儿:万亿参数模型做强化学习后训练,如果按传统全参数方案硬推,卡数需求不是“多一点”,而是直接翻一个数量级。我见过不少团队在预训练阶段省吃俭用&#xff0c…

2026/10/19 6:18:45 阅读更多 →
YOLOv8轻量级基建裂缝检测系统(0.15mm级)

YOLOv8轻量级基建裂缝检测系统(0.15mm级)

简介:本资源是一套基于Python与YOLOv8实现的基建裂缝目标检测系统,专为本科毕业设计、课程设计及工程实践项目开发打造,面向计算机视觉初学者与土木工程智能化方向学习者,解决基础设施巡检中裂缝自动识别与定位的实际问题。压缩包…

2026/10/19 6:18:45 阅读更多 →
恶意代码分析实战:Labs-15反调试技术详解与绕过方法

恶意代码分析实战:Labs-15反调试技术详解与绕过方法

拿到《恶意代码分析实战》的Labs-15实验包时,我第一反应是“反调试”。这个编号对应的章节主题就是恶意代码如何检测调试器、如何干扰分析流程,配套的几个样本基本都围绕“你一旦用调试器打开它,它就开始撒谎”来设计。对于刚接触逆向的人来说…

2026/10/19 6:18:45 阅读更多 →
把画图当工程来做:一套可落地的技术制图规范与避坑指南

把画图当工程来做:一套可落地的技术制图规范与避坑指南

1. 复盘 diagram-design:我为什么把画图当成工程来做diagram-design 是我沉淀了大半年的技术制图规范。它不是一个画图软件,也不是某个框架里的自动生成插件,而是一套面向技术文档、架构评审、故障排查和新人培训的“画图方法论”。过去团队里…

2026/10/19 6:18:45 阅读更多 →
C++类型转换避坑指南:隐式转换与四种强制转换详解

C++类型转换避坑指南:隐式转换与四种强制转换详解

相信每个写过一段时间C的人,都有过被类型转换“坑”到的经历。我印象最深的一次,是在维护一段遗留代码时,看到一个函数传入的是double,内部却拿它当int做数组下标。编译器一声不吭,结果在边界条件下数组越界&#xff0…

2026/10/19 6:17:45 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →