HarmonyOS 7.0 Input Kit 键盘拦截与避让实战
在金融转账等企业级移动应用中,软键盘交互常常是影响留存和转化的关键环节。一个页面可能同时包含收款人姓名、银行卡号、转账金额和交易密码:姓名用系统输入法,金额需要带格式化校验和快捷金额的自定义数字键盘,密码则需要与系统级密码箱打通的安全键盘。早期做法中,多键盘切换容易出现页面闪烁、焦点丢失、输入框被遮挡;自定义键盘还会遇到系统软键盘“不请自来”,开发者只能用延迟失焦规避,容易引发焦点抢占。第三方输入法场景下,密码输入若缺少底层隔离,还会带来泄露与合规风险。HarmonyOS 7.0(API 26)中,Input Kit 的输入法服务框架 IMF 做了架构演进:开放 InputMethodController 的拉起拦截、自定义键盘高度设置,并在系统底层引入安全键盘与密码输入的物理级、虚拟内存级隔离。下面围绕 IMF 运转逻辑,整理一套无闪烁的输入法拦截与安全隔离实现。
一、示例工程结构
entry/src/main/ets/
├── components/
│ ├── CustomNumberKeyboard.ets
│ ├── SecurePasswordKeyboard.ets
│ └── AdaptiveInputWrapper.ets
├── services/
│ ├── KeyboardInterceptorService.ets
│ └── SecureInputManager.ets
├── pages/
│ └── TransferCheckoutPage.ets
└── utils/
└── AvoidAreaCalculator.ets
二、核心 API 与安全隔离机制
1. InputMethodController 拦截与接管
过去调用 inputMethod.getController() 后,主要监听 keyboardShow 或执行 hideTextInput()。API 26 暴露了基于回调挂起的拦截管道,可在键盘拉起请求发出后、窗口重算前介入。
2. 安全键盘与密码隔离
当 TextInput 类型为 InputType.Password 或使用新的 SecureInput 标签时,Input Kit 会在 TEE 与 ArkUI 渲染引擎整合下触发隔离:
渲染隔离:安全键盘 UI 不在应用进程空间,由系统高权限安全进程独立渲染,再通过 Surface 共享覆盖在当前 Window 上。
事件隔离:安全键盘上的触摸事件在驱动层直接分发到安全进程,应用层包括悬浮窗、无障碍服务均无法捕获按键具体坐标。
数据隔离:输入结果不会明文经 IPC 传回应用层,而是加密后传递给应用底层 Secure Context,应用层只通过特定 Token 解密验证。
三、IMF 与 WindowManager 避让逻辑
API 26 的拦截器位于 IPC 请求之后、窗口重新计算之前,因此应用上报的自定义高度会直接参与窗口避让计算。HarmonyOS 使用动态 Avoid Area 方案:WindowManager 获取键盘 Rect,判断当前焦点组件 Rect 是否与键盘 Rect 重叠;若组件不在 Scroll 或 List 中,系统通过 Matrix 变换将 ViewRoot 向上偏移;若组件处于 Scroll 容器中,系统会将避让区域大小以 onAvoidAreaChanged 事件抛给 ArkUI,由 ArkUI 调整 Scroll 底部 Padding 或 offset,使聚焦输入框位于键盘上方。
四、核心拦截服务实现
import inputMethod from '@ohos.inputMethod';
import window from '@ohos.window';
import promptAction from '@ohos.promptAction';
export class KeyboardInterceptorService {
private static instance: KeyboardInterceptorService;
private controller: inputMethod.InputMethodController = inputMethod.getController();
private isCustomKeyboardActive: boolean = false;
private customKeyboardHeightVp: number = 320;
public static getInstance(): KeyboardInterceptorService {
if (!KeyboardInterceptorService.instance) {
KeyboardInterceptorService.instance = new KeyboardInterceptorService();
}
return KeyboardInterceptorService.instance;
}
public setupInterceptor(): void {
this.controller.interceptKeyboardRequest((request: inputMethod.KeyboardRequest) => {
const inputType = request.editorInfo.inputType;
if (inputType === inputMethod.InputType.NUMBER_DECIMAL) {
request.accept(true);
this.isCustomKeyboardActive = true;
this.controller.setCustomKeyboardConfiguration({
height: this.customKeyboardHeightVp,
animationDuration: 250
});
this.notifyUIToShowCustomKeyboard();
} else {
request.accept(false);
this.isCustomKeyboardActive = false;
}
});
}
public hideCustomKeyboard(): void {
if (this.isCustomKeyboardActive) {
this.isCustomKeyboardActive = false;
this.controller.setCustomKeyboardConfiguration({ height: 0, animationDuration: 0 });
this.notifyUIToHideCustomKeyboard();
}
}
private notifyUIToShowCustomKeyboard() {
AppStorage.setOrCreate('isCustomKeyboardVisible', true);
}
private notifyUIToHideCustomKeyboard() {
AppStorage.setOrCreate('isCustomKeyboardVisible', false);
}
}
这段代码通过 interceptKeyboardRequest 判断 request.editorInfo.inputType。若为 InputType.NUMBER_DECIMAL,则 request.accept(true) 接管,并调用 setCustomKeyboardConfiguration 上报高度与动画时长;否则 accept(false) 放行。隐藏时重置 height:0、animationDuration:0,避免影响后续标准输入框。
五、输入框包装与安全模式
import window from '@ohos.window';
import { KeyboardInterceptorService } from '../services/KeyboardInterceptorService';
@Component
export struct AdaptiveInputWrapper {
@Prop placeholderText: string;
@Prop isSecureInput: boolean = false;
@Prop isAmountInput: boolean = false;
@Link inputValue: string;
@State avoidOffset: number = 0;
aboutToAppear() {
window.getLastWindow(getContext(this)).then((win) => {
win.on('avoidAreaChange', (data) => {
if (data.type === window.AvoidAreaType.TYPE_KEYBOARD) {
this.avoidOffset = px2vp(data.area.bottomRect.height);
}
});
});
}
build() {
Column() {
TextInput({ text: this.inputValue, placeholder: this.placeholderText })
.type(this.getInputType())
.height(50)
.width('100%')
.fontSize(18)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.padding({ left: 16, right: 16 })
.securityMode(this.isSecureInput ? true : false)
.onChange((value: string) => {
this.inputValue = value;
})
.onBlur(() => {
if (this.isAmountInput) {
KeyboardInterceptorService.getInstance().hideCustomKeyboard();
}
})
}
.padding({ bottom: this.avoidOffset })
.animation({ duration: 250, curve: Curve.EaseOut })
}
private getInputType(): InputType {
if (this.isSecureInput) {
return InputType.Password;
}
if (this.isAmountInput) {
return InputType.NUMBER_DECIMAL;
}
return InputType.Normal;
}
}
AdaptiveInputWrapper 监听 window.avoidAreaChange,当 data.type 为 window.AvoidAreaType.TYPE_KEYBOARD 时读取 bottomRect.height 并换算 vp,作为底部偏移;securityMode(true) 用于开启安全隔离;密码类型返回 InputType.Password,金额类型返回 InputType.NUMBER_DECIMAL,普通输入返回 InputType.Normal。金额框失焦时调用 hideCustomKeyboard。
六、业务页面组装思路
业务页面可用 Stack 将自定义键盘绝对定位在底部,由 @StorageLink('isCustomKeyboardVisible') 控制显隐。示例中 CustomNumberKeyboard 以 320vp 高度配置,并通过 TransitionEffect.translate 与 250ms 动画弹出;因为拦截器已向 WindowManager 上报 320 高度,AdaptiveInputWrapper 监听到的避让高度准确,页面会自动上抬,无需手写复杂算式。密码输入框通过 isSecureInput 触发 InputType.Password,并由 securityMode(true) 进入安全隔离路径。
七、避坑指南
并发时序:不要在 TextInput 的 onFocus 里拦截并弹自定义键盘。onFocus 触发时 InputConnection 已建立,系统键盘弹出指令已发往 IMMS,此时阻止会导致系统键盘闪现后被覆盖。应使用 API 26 的 interceptKeyboardRequest 前置回调,采用回调挂起机制。
安全隔离生命周期泄漏:开启 securityMode(true) 后会分配独立隔离内存区。在自定义弹窗、半屏模态面板中使用安全输入框,销毁时若未显式 blur(),安全内存上下文可能无法及时回收,下次进入页面可能报错。建议在 aboutToDisappear 时强制释放焦点连接。
复杂嵌套避让失灵:输入框嵌套在固定高度且关闭内部滚动的容器内时,onAvoidAreaChanged 无法把内部输入框顶上来,因为外层尺寸锁死。可在避让事件中动态 translate({ y: -offset }) 整体上移包裹层,而不是只依赖 Padding 或 Scroll 自动滚动。
自定义配置重置遗漏:hideCustomKeyboard 必须重置 setCustomKeyboardConfiguration({ height: 0, animationDuration: 0 })。否则用户关闭自定义键盘后点击普通搜索框,系统仍按 320vp 预留避让空间,屏幕下方出现空白。
八、总结
HarmonyOS 7.0 Input Kit 的演进体现了从隐藏底层细节到在安全隔离前提下交还控制权的思路。interceptKeyboardRequest 斩断输入框焦点与系统键盘唤起的强耦合;setCustomKeyboardConfiguration 让自定义 UI 进入 WindowManager 避让计算;securityMode(true) 与底层安全环境把防窃听、防截图下沉到系统驱动层。合理使用这套体系,可以解决键盘遮挡与闪烁,并为安全合规场景提供坚实底座。
Re: HarmonyOS 7.0 Input Kit 键盘拦截与避让实战
感谢分享,这篇把 IMF 拦截、WindowManager 避让和安全键盘隔离串起来讲得很清楚,尤其是拦截点位于 IPC 之后、窗口重算之前这一点,解释了为什么自定义键盘高度能直接参与避让计算。金融转账页同时有姓名、金额、密码三类输入,确实很容易遇到焦点抢占和闪烁,按文中 accept 控制加自定义键盘配置的思路,应该比延迟失焦稳很多。 想请教两个点:一是同一页面从系统输入法切到自定义数字键盘时,焦点和光标位置需要额外保存恢复吗,还是拦截管道本身能保证不丢失?二是安全键盘数据隔离里提到的 Token 解密验证,是在 SecureInputManager 里统一管理,还是每次密码输入单独签发?另外看到核心拦截服务实现到 hideCustomKeyboard 处截断了,期待后续把 TransferCheckoutPage 和 AvoidAreaCalculator 的完整流程补上,特别是 320vp 高度和 250ms 动画在不同机型安全区下是否需要动态计算。再次感谢。Re: HarmonyOS 7.0 Input Kit 键盘拦截与避让实战
楼主这个梳理很实用。金融转账这种多输入框场景,最怕的就是系统键盘和自定义键盘抢焦点,之前用延迟失焦确实容易把焦点弄丢。API 26 把拦截点放在 IPC 之后、窗口重算之前,这个位置很关键,等于让自定义键盘高度提前进入避让计算,应该能减少页面闪烁。安全键盘的渲染、事件、数据三层隔离也讲得清楚,尤其输入结果不明文走 IPC、只通过 Token 回到 Secure Context,对合规和防第三方输入法截获都更有底。想请教一下,NUMBER_DECIMAL 被拦截后拉起自定义数字键盘,实际在第三方输入法默认开启时是否还会偶发系统键盘闪现?另外 Scroll 容器里 onAvoidAreaChanged 调 padding 和 offset,多个输入框连续切换时有没有推荐的处理顺序?帖子末尾代码好像没贴完,期待后续 hideCustomKeyboard 和 AdaptiveInputWrapper 的完整实现。Re: HarmonyOS 7.0 Input Kit 键盘拦截与避让实战
看完收获很大,之前做转账页最头疼的就是金额自定义键盘和系统键盘互相抢焦点,页面一闪一闪,输入框还被遮住。你把拦截时机放在 IPC 请求之后、窗口重算之前,再让自定义高度参与 AvoidArea 计算,这个思路很清晰;密码走安全键盘的渲染隔离、事件隔离和数据隔离,也解释了不少合规上的疑问。 想请教几个落地细节:固定 320vp 在折叠屏、横屏和分屏下会动态调整吗,还是靠 AdaptiveInputWrapper 重新上报?自定义键盘 250ms 动画和 onAvoidAreaChanged 里 Scroll 的 padding 或 offset 调整怎么同步,才能做到无闪烁?安全键盘返回的 Token 一般是在提交前统一解密校验,还是输入过程中就逐步验证?另外代码到 hideCustomKeyboard 好像截断了,后面释放拦截和 SecureInputManager 的用法如果方便也希望能展开讲讲。
页:
[1]