大家好我是[晚风依旧似温柔]新人一枚欢迎大家关注~本文目录前言一、金额输入为什么适合专用键盘二、先把版本和官方能力边界确认清楚三、搭一个最小金额输入场景四、核心代码TextInput customKeyboard五、真正需要关注的是光标而不只是数字按钮六、删除和清空最好分开处理七、收起自定义键盘不要再绕回输入法服务八、自定义键盘弹出来以后页面怎么避让九、自定义键盘为什么有时第一次弹出的还是系统键盘十、敏感输入场景不能只停留在“自定义键盘”十一、实际项目中建议这样排查开发经验总结前言支付金额、转账金额、充值金额这类输入看起来只是一个TextInput真正落到交互上却比普通文本框多不少约束用户主要输入数字需要小数点、删除、清空和完成编辑已有金额时还要处理光标和选区键盘弹出后又不能把输入框或页面关键操作区域挡住。这种场景不一定要完全交给系统键盘。HarmonyOS 官方已经提供自定义键盘能力。当前官方指南明确说明TextArea、TextInput、RichEditor、Search等输入控件可以通过customKeyboard绑定自定义键盘绑定后输入控件获得焦点时加载指定的自定义键盘而不是系统键盘。这次就围绕一个足够具体的场景展开给金额TextInput做一套只包含数字、小数点、删除、清空和完成操作的自定义键盘并处理光标与页面避让。一、金额输入为什么适合专用键盘普通文本输入强调的是通用性金额输入强调的则是业务约束。例如一个简单的金额输入框可能只允许数字0~9最多一个小数点小数点后最多两位在光标当前位置插入或删除一键清空点击“完成”结束编辑。如果继续使用通用系统键盘业务层仍然要处理金额格式。而使用customKeyboard后键盘本身就可以只暴露业务真正需要的按键输入路径更直接。不过这里有一个容易混淆的地方自定义键盘负责的是输入交互金额是否合法仍然应该由业务逻辑判断。比如“两位小数”“整数最多八位”并不是customKeyboard自动提供的规则需要应用自己实现。二、先把版本和官方能力边界确认清楚本文以HarmonyOS 7 / API 26.0.0为当前开发基线。华为官方升级适配文档明确说明HarmonyOS 7.0 对应 API 26.0.0并建议升级开发套件并完成对应版本适配。本文涉及的主体是 ArkUI 文本输入能力不需要引入三方库也没有为了实现customKeyboard而增加额外的运行时权限。几个核心点可以先整理出来能力本文用途官方依据TextInput金额输入框ArkUI 单行文本输入组件customKeyboard绑定自定义数字键盘官方自定义键盘指南Builder描述键盘 UIBuilder自 API version 7 起支持onTextSelectionChange获取光标/选区官方自定义键盘及光标 FAQTextInputController.caretPosition()修改文本后恢复光标官方自定义键盘指南TextInputController.stopEditing()点击“完成”收起键盘官方指南和 FAQsupportAvoidance开启系统自定义键盘避让官方自定义键盘指南还有一个版本问题需要特别说明当前 HarmonyOS 7 文档已经采用新的 API 版本体系本文代码以 API 26.0.0 文档为核验基线不根据旧资料反推 ArkTScustomKeyboard的最早首发版本。官方 Native ArkUI C API 中的NODE_TEXT_INPUT_CUSTOM_KEYBOARD标注起始版本为 API 12但这是Native ArkUI 属性接口不能直接拿来当作 ArkTSTextInput.customKeyboard的首发版本。如果项目还需要兼容较旧的 HarmonyOS API应在 DevEco Studio 对应 SDK/API Reference 下重新检查目标版本而不是把 Native 和 ArkTS 的版本号混在一起。三、搭一个最小金额输入场景这个示例只做一件事一个TextInput负责展示金额输入框获得焦点后弹出自定义键盘键盘包含0~9、.、删除、清空、完成。金额规则设为整数部分最多 8 位小数部分最多 2 位只允许一个小数点空内容时点击小数点自动形成0.。这几条属于示例业务规则并不是 HarmonyOS API 的限制。自定义键盘本身使用Builder描述。官方文档说明customKeyboard的第一个参数是用于描述自定义 UI 的CustomBuilder官方 FAQ 也特别提醒自定义键盘构建函数应正确使用Builder。四、核心代码TextInput customKeyboard下面代码按照官方TextInput、customKeyboard、onTextSelectionChange、caretPosition()和stopEditing()的使用方式组织成一个最小金额输入示例。EntryComponentstruct AmountInputPage{Stateamount:string;privatecontroller:TextInputControllernewTextInputController();privateselectionStart:number0;privateselectionEnd:number0;privatetargetCaret:number0;BuilderkeyboardKey(label:string){Button(label,{type:ButtonType.Normal}).layoutWeight(1).height(52).fontSize(20).onClick((){this.handleKey(label);})}BuilderamountKeyboard(){Column({space:8}){Row({space:8}){this.keyboardKey(1)this.keyboardKey(2)this.keyboardKey(3)}Row({space:8}){this.keyboardKey(4)this.keyboardKey(5)this.keyboardKey(6)}Row({space:8}){this.keyboardKey(7)this.keyboardKey(8)this.keyboardKey(9)}Row({space:8}){this.keyboardKey(.)this.keyboardKey(0)this.keyboardKey(删除)}Row({space:8}){this.keyboardKey(清空)this.keyboardKey(完成)}}.padding(12).height(300).backgroundColor(#F1F3F5)}privatehandleKey(key:string):void{if(key删除){this.deleteBackward();return;}if(key清空){this.amount;this.targetCaret0;this.selectionStart0;this.selectionEnd0;return;}if(key完成){this.controller.stopEditing();return;}this.insertValue(key);}privateinsertValue(value:string):void{conststartMath.min(this.selectionStart,this.selectionEnd);constendMath.max(this.selectionStart,this.selectionEnd);letinsertTextvalue;if(value.this.amount.length0){insertText0.;}constcandidatethis.amount.slice(0,start)insertTextthis.amount.slice(end);if(!this.isValidAmount(candidate)){return;}this.amountcandidate;this.targetCaretstartinsertText.length;this.selectionStartthis.targetCaret;this.selectionEndthis.targetCaret;}privatedeleteBackward():void{conststartMath.min(this.selectionStart,this.selectionEnd);constendMath.max(this.selectionStart,this.selectionEnd);if(start!end){this.amountthis.amount.slice(0,start)this.amount.slice(end);this.targetCaretstart;}elseif(start0){this.amountthis.amount.slice(0,start-1)this.amount.slice(start);this.targetCaretstart-1;}else{return;}this.selectionStartthis.targetCaret;this.selectionEndthis.targetCaret;}privateisValidAmount(value:string):boolean{if(value.length0){returntrue;}letdotCount0;for(leti0;ivalue.length;i){constchvalue[i];if(ch.){dotCount;if(dotCount1){returnfalse;}}elseif(ch0||ch9){returnfalse;}}constpartsvalue.split(.);if(parts[0].length8){returnfalse;}if(parts.length2parts[1].length2){returnfalse;}returntrue;}build(){Column({space:24}){Text(付款金额).fontSize(18).fontWeight(FontWeight.Medium).alignSelf(ItemAlign.Start)TextInput({placeholder:0.00,text:$$this.amount,controller:this.controller}).height(56).fontSize(24).onTextSelectionChange((start:number,end:number){this.selectionStartstart;this.selectionEndend;}).onChange((){this.controller.caretPosition(this.targetCaret);}).customKeyboard(this.amountKeyboard(),{supportAvoidance:true})Text(当前金额${this.amount}).fontSize(16).alignSelf(ItemAlign.Start)}.width(100%).padding(24)}}这段代码没有声称已经在具体设备上编译或真机运行实际项目仍应使用目标 HarmonyOS 7 设备和项目自身的 SDK 配置进一步验证。五、真正需要关注的是光标而不只是数字按钮如果金额输入永远只能从末尾追加逻辑其实很简单12.3 → 点击 4 → 12.34问题出现在用户点击输入框中间以后。例如当前内容为1234.56 ^用户把光标移动到2后面再点击数字键这个数字应该插入光标位置而不是直接追加到字符串末尾。官方自定义键盘指南给出的思路就是通过onTextSelectionChange获取当前选择位置在内容修改后通过TextInputController.caretPosition()设置新的光标位置。华为官方 FAQ 也专门解释了“输入字符后光标跳到最后”的问题当文本重新赋值后如果没有重新定位光标就容易出现光标回到末尾的现象官方示例同样记录选择位置并在内容变化后调用caretPosition()。所以这里记录的不是一个caret而是privateselectionStart:number0;privateselectionEnd:number0;这是因为 TextInput 不只有“光标”还可能存在一段选区。如果用户选中了12[34].56再点击8更自然的编辑结果是128.56因此插入和删除逻辑都应该先判断start和end是否相同。六、删除和清空最好分开处理金额键盘里的“删除”和“清空”不是一回事。删除键应该遵循文本编辑语义有选区时删除整个选区没有选区时删除光标前一个字符光标已经位于开头时不处理。而清空属于业务快捷操作直接把金额设为空即可。这种处理还有一个好处键盘组件只负责产生“删除”“清空”这样的动作金额字符串如何变化仍由页面状态统一管理不需要让每个 Button 自己操作字符串。七、收起自定义键盘不要再绕回输入法服务自定义键盘已经绑定到TextInputController后点击“完成”可以直接this.controller.stopEditing();HarmonyOS 官方自定义键盘指南明确给出了通过TextInputController.stopEditing()关闭自定义键盘的方式。这比为了一个 TextInput 再额外引入输入法控制逻辑更直接。官方关于 TextInput 收起软键盘的 FAQ 同样给出controller.stopEditing()方案当页面存在多个输入框、需要统一管理输入会话时才可能考虑输入法框架提供的其他控制方式。八、自定义键盘弹出来以后页面怎么避让这是customKeyboard很容易被忽略的一部分。官方提供了.customKeyboard(this.amountKeyboard(),{supportAvoidance:true})supportAvoidance: true用来开启系统提供的自定义键盘避让能力。但这里不要理解成“整个页面都会自动完美避让”。官方文档明确说明系统提供的自定义键盘避让主要保证输入框本身不被键盘遮挡输入框下面的其他组件仍然可能被挡住。对于需要整体上移、底部操作区也必须可见的页面官方给出的进一步方案是监听自定义键盘根节点的onAreaChange获得键盘高度再根据业务布局调整页面避让距离。所以实际项目可以分成两层处理简单金额页先开启supportAvoidance如果页面底部还有“确认付款”“下一步”等关键按钮再根据键盘实际高度调整页面 padding 或其他布局空间。九、自定义键盘为什么有时第一次弹出的还是系统键盘如果后续项目需要在“系统键盘”和“自定义键盘”之间动态切换这里还有一个官方已经记录过的问题。华为 FAQ 指出customKeyboard的自定义 UI 描述需要正确使用Builder同时如果先修改“是否绑定自定义键盘”的状态然后在同一帧立即通过当前帧生效的焦点接口请求焦点可能出现自定义键盘尚未完成更新、第一次仍拉起系统键盘的情况。官方给出的处理方式之一是让组件在后续帧获取焦点例如使用相应的focusControl.requestFocus()方案。本文示例始终绑定自定义金额键盘因此不需要做动态切换。但如果项目后面加入“数字键盘 / 系统键盘”切换按钮这个时序问题就应该一起检查。十、敏感输入场景不能只停留在“自定义键盘”金额本身未必属于密码类输入但支付页往往还会出现银行卡、验证码、账户信息等敏感数据。这里需要把两个概念分清楚自定义键盘是一种输入 UI 能力不应直接等同于可信输入、安全键盘或完整的数据安全方案。HarmonyOS 官方自定义键盘指南确实提到自定义键盘可以用于增强敏感信息输入场景的安全性同时官方还专门给出了“自定义键盘实现防截屏”的指导入口。因此涉及真正敏感数据时建议继续按官方安全能力核对防截屏、窗口隐私模式及其对应权限和适用条件而不是因为换成了自定义数字键盘就认为敏感信息保护已经完成。本文的金额键盘本身不需要在module.json5中增加额外权限因此没有为了展示配置而虚构requestPermissions。如果后续接入独立的隐私窗口或其他安全能力应以对应 API 文档列出的权限要求为准。十一、实际项目中建议这样排查如果自定义金额键盘表现不符合预期可以按这个顺序检查先看版本。确认当前 SDK、compileSdkVersion、targetSdkVersion与项目实际运行设备的 API 能力是否匹配HarmonyOS 7 当前对应 API 26.0.0。再看 Builder。customKeyboard使用的 UI 构建函数是否正确通过Builder描述。检查绑定。TextInput 是否真正执行了.customKeyboard(...)而不是在获焦以后才临时改变绑定状态。检查光标。修改绑定文本后有没有根据onTextSelectionChange保存的位置重新调用caretPosition()。检查选区。删除和插入是否同时考虑selectionStart与selectionEnd。检查金额规则。小数位数、整数位数、重复小数点等属于业务规则不要误以为系统会自动完成。检查避让。supportAvoidance只能解决基础遮挡页面底部其他内容仍被挡住时再处理键盘高度与页面布局。最后看焦点时序。动态切换系统键盘和自定义键盘时重点检查状态更新和请求焦点是否发生在正确的渲染时机。开发经验总结金额输入的自定义键盘真正有价值的地方并不是“自己画了十几个按钮”而是把输入行为收缩到业务真正需要的范围内。实现时可以抓住四个核心关系customKeyboard负责替换键盘状态变量负责保存金额onTextSelectionChange caretPosition()负责维护编辑位置stopEditing()负责结束编辑。页面避让则要单独考虑。简单页面使用supportAvoidance已经能覆盖输入框不被遮挡的基本需求复杂支付页如果底部还有关键操作区就应该继续监听键盘区域变化并调整页面布局。最后还有一点比较重要自定义键盘并不会自动获得“安全键盘”的全部语义。进入银行卡、密码或其他敏感数据场景后应继续沿着 HarmonyOS 官方提供的隐私与安全能力做完整设计而不是把输入 UI 和安全能力混为一谈。如果你的金额输入框目前只考虑了“末尾追加数字”可以重点试一下把光标移到金额中间再执行插入、删除和选区替换。自定义键盘真正容易出现问题的地方往往就在这里。如果觉得有帮助别忘了点个赞关注支持一下~喜欢记得关注别让好内容被埋没
企业数字化 ERP 产品动态
相关推荐
Ollama :大模型本地部署与轻量化优化 实操:可直接看四、九 (文中地址均为作者的本地地址 实际目录地址自行选择) 本文档以《Ollama笔记.md》为内容大纲基准,参考《课程一:基于 Ollama 的大模型本地部署与轻量化优化(2 小时体验课讲义&#… · 2026/9/26 4:14:33
2026中小企业进销存系统选型指南:6款热门软件横向对比与最终推荐 1. 2026年中小企业进销存选型,先看清这三个变化1.1 选型决策从"单点好用"变成"整体协同"前几年我问客户需求,听到最多的回答是"能开单、能查库存就行"。但从去年开始,明显感觉到老板们的关注重点变了——他们开… · 2026/9/26 4:14:33
L2、分类攻击应用于reid等相关理解 一、L2范数L2-norm 欧几里得范数/2-范数 向量元素的平方和的平方根L2-normalized L2归一化 (L2用于模长的计算) 在几何学中,衡量两个向量之间方向夹角远近的标准数学公式是余弦相似度(Cosine Similarity):向量除以模长… · 2026/9/26 4:14:33
LPRNet车牌识别数据集制作全指南:从标注到NPU量化校准 做端侧车牌识别也有一段时间了,这段时间经常有朋友问我:全志开发板上跑 LPRNet,模型转换和部署倒是好说,数据集这一步到底怎么搞才靠谱?说实话,数据集制作是我见过的翻车概率最高的环节。LPRNet 是端到端的… · 2026/9/26 4:59:19
罗技鼠标在macOS突然失灵?Logi Options+证书过期排查修复指南 上周我遇到一个特别典型的罗技鼠标故障:MX Master 3在macOS上突然只剩光标还能动,侧键没反应、滚轮一顿一顿、DPI切换失灵、Flow完全瘫痪。刚开始我以为是蓝牙出问题,重连、重启、换优联接收器都没用。折腾到晚上,打开Console翻日… · 2026/9/26 4:59:19
基于蒙特卡洛模拟的电动汽车充电负荷曲线生成方法 做配电网规划或者充电设施规划的朋友,应该都碰过这种场景:只知道某片区大概有几百辆电动汽车,却要估算它们晚上八点同时充电会产生多大负荷。拍脑袋套一个同时率,或者直接拿别的小区充电曲线来平移,误差经常大到无法交… · 2026/9/26 4:59:19
HTML5教育模板改造实战:语义化、响应式与SEO优化全解析 简介:HTML5教育类网站模板是一套面向教育培训行业的网页设计解决方案,适用于在线课程平台、教育机构官网或个人教学博客。模板基于HTML5语义化标签、多媒体元素、离线存储与表单控件构建,兼顾SEO优化与交互体验,并采用响应式设计适… · 2026/9/26 4:59:18
小红书上架软件:彻底解决IP关联与硬件指纹穿帮 小红书上架软件:彻底解决IP关联与硬件指纹穿帮
说句掏心窝的话,做店群的,工具选对了事半功倍。小红书的自动化上架,是店群运营中最耗人力也最容易出错的环节。
手动上架一个商品从填写标题、上传主图、设置SKU、填写详情到发布&am… · 2026/9/26 4:59:18
Omarchy:面向专业工作流的GNOME+Wayland原生桌面重构 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 4:59:12
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46