1# 实现一个输入法应用 2 3[InputMethodExtensionAbility](../reference/apis-ime-kit/js-apis-inputmethod-extension-ability.md)提供了onCreate()和onDestroy()生命周期回调,根据需要重写对应的回调方法。InputMethodExtensionAbility的生命周期如下: 4 5- **onCreate()** 6 7 服务被首次创建时触发该回调,开发者可以在此进行一些初始化的操作,例如注册公共事件监听等。 8 9 > **说明:** 10 > 11 > 如果服务已创建,再次启动该InputMethodExtensionAbility不会触发onCreate()回调。 12 13- **onDestroy()** 14 15 当不再使用服务且准备将该实例销毁时,触发该回调。开发者可以在该回调中清理资源,如注销监听等。 16 17 18## 开发步骤 19 20开发者在实现一个输入法应用时,需要在DevEco Studio工程中新建一个InputMethodExtensionAbility,具体步骤如下: 21 221. 在工程Module对应的ets目录下,右键选择“New > Directory”,新建一个目录,并命名为InputMethodExtensionAbility。 23 242. 在InputMethodExtensionAbility目录下,右键选择“New > File”,新建四个文件,分别为KeyboardController.ts、InputMethodService.ts、Index.ets以及KeyboardKeyData.ts。目录如下: 25 26``` 27/src/main/ 28├── ets/InputMethodExtensionAbility 29│ └──model/KeyboardController.ts # 显示键盘 30│ └──InputMethodService.ts # 自定义类继承InputMethodExtensionAbility并加上需要的生命周期回调 31│ └──pages 32│ └── Index.ets # 绘制键盘,添加输入删除功能 33│ └── KeyboardKeyData.ts # 键盘属性定义 34├── resources/base/profile/main_pages.json 35``` 36 37## 文件介绍 38 391. InputMethodService.ts文件。 40 41 在InputMethodService.ts文件中,增加导入InputMethodExtensionAbility的依赖包,自定义类继承InputMethodExtensionAbility并加上需要的生命周期回调。 42 43 ```ts 44 import { Want } from '@kit.AbilityKit'; 45 import keyboardController from './model/KeyboardController'; 46 import { InputMethodExtensionAbility } from '@kit.IMEKit'; 47 48 export default class InputDemoService extends InputMethodExtensionAbility { 49 50 onCreate(want: Want): void { 51 keyboardController.onCreate(this.context); // 初始化窗口并注册对输入法框架的事件监听 52 } 53 54 onDestroy(): void { 55 console.log("onDestroy."); 56 keyboardController.onDestroy(); // 销毁窗口并去注册事件监听 57 } 58 } 59 ``` 60 612. KeyboardController.ts文件。 62 63 ```ts 64 import { display } from '@kit.ArkUI'; 65 import { inputMethodEngine, InputMethodExtensionContext } from '@kit.IMEKit'; 66 67 // 调用输入法框架的getInputMethodAbility方法获取实例,并由此实例调用输入法框架功能接口 68 const inputMethodAbility: inputMethodEngine.InputMethodAbility = inputMethodEngine.getInputMethodAbility(); 69 70 export class KeyboardController { 71 private mContext: InputMethodExtensionContext | undefined = undefined; // 保存InputMethodExtensionAbility中的context属性 72 private panel: inputMethodEngine.Panel | undefined = undefined; 73 private textInputClient: inputMethodEngine.InputClient | undefined = undefined; 74 private keyboardController: inputMethodEngine.KeyboardController | undefined = undefined; 75 76 constructor() { 77 } 78 79 public onCreate(context: InputMethodExtensionContext): void 80 { 81 this.mContext = context; 82 this.initWindow(); // 初始化窗口 83 this.registerListener(); // 注册对输入法框架的事件监听 84 } 85 86 public onDestroy(): void // 应用生命周期销毁 87 { 88 this.unRegisterListener(); // 去注册事件监听 89 if(this.panel) { // 销毁窗口 90 inputMethodAbility.destroyPanel(this.panel); 91 } 92 if(this.mContext) { 93 this.mContext.destroy(); 94 } 95 } 96 97 public insertText(text: string): void { 98 if(this.textInputClient) { 99 this.textInputClient.insertText(text); 100 } 101 } 102 103 public deleteForward(length: number): void { 104 if(this.textInputClient) { 105 this.textInputClient.deleteForward(length); 106 } 107 } 108 109 private initWindow(): void // 初始化窗口 110 { 111 if(this.mContext === undefined) { 112 return; 113 } 114 let dis = display.getDefaultDisplaySync(); 115 let dWidth = dis.width; 116 let dHeight = dis.height; 117 let keyHeightRate = 0.47; 118 let keyHeight = dHeight * keyHeightRate; 119 let nonBarPosition = dHeight - keyHeight; 120 let panelInfo: inputMethodEngine.PanelInfo = { 121 type: inputMethodEngine.PanelType.SOFT_KEYBOARD, 122 flag: inputMethodEngine.PanelFlag.FLG_FIXED 123 }; 124 inputMethodAbility.createPanel(this.mContext, panelInfo).then(async (inputPanel: inputMethodEngine.Panel) => { 125 this.panel = inputPanel; 126 if(this.panel) { 127 await this.panel.resize(dWidth, keyHeight); 128 await this.panel.moveTo(0, nonBarPosition); 129 await this.panel.setUiContent('InputMethodExtensionAbility/pages/Index'); 130 } 131 }); 132 } 133 134 private registerListener(): void 135 { 136 this.registerInputListener(); // 注册对输入法框架服务的监听 137 // 注册隐藏键盘事件监听等 138 } 139 140 private registerInputListener(): void { // 注册对输入法框架服务的开启及停止事件监听 141 inputMethodAbility.on('inputStart', (kbController, textInputClient) => { 142 this.textInputClient = textInputClient; // 此为输入法客户端实例,由此调用输入法框架提供给输入法应用的功能接口 143 this.keyboardController = kbController; 144 }) 145 inputMethodAbility.on('inputStop', () => { 146 this.onDestroy(); // 销毁KeyboardController 147 }); 148 } 149 150 private unRegisterListener(): void 151 { 152 inputMethodAbility.off('inputStart'); 153 inputMethodAbility.off('inputStop', () => {}); 154 } 155 } 156 157 const keyboardController = new KeyboardController(); 158 159 export default keyboardController; 160 ``` 161 1623. KeyboardKeyData.ts文件。 163 164 定义软键盘的按键显示内容。 165 166 ```ts 167 export interface sourceListType { 168 content: string, 169 } 170 171 export let numberSourceListData: sourceListType[] = [ 172 { 173 content: '1' 174 }, 175 { 176 content: '2' 177 }, 178 { 179 content: '3' 180 }, 181 { 182 content: '4' 183 }, 184 { 185 content: '5' 186 }, 187 { 188 content: '6' 189 }, 190 { 191 content: '7' 192 }, 193 { 194 content: '8' 195 }, 196 { 197 content: '9' 198 }, 199 { 200 content: '0' 201 } 202 ] 203 ``` 204 2054. Index.ets文件。 206 207 主要描绘了具体按键功能。如按下数字键,就会将数字内容在输入框中打印出来,按下删除键,就会将内容删除。 208 209 同时在resources/base/profile/main_pages.json文件的src字段中添加此文件路径。 210 211 ```ets 212 import { numberSourceListData, sourceListType } from './KeyboardKeyData'; 213 import keyboardController from '../model/KeyboardController'; 214 215 @Component 216 struct keyItem { 217 private keyValue: sourceListType = numberSourceListData[0]; 218 @State keyBgc: string = "#fff" 219 @State keyFontColor: string = "#000" 220 221 build() { 222 Column() { 223 Flex({ direction: FlexDirection.Column, 224 alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { 225 Text(this.keyValue.content).fontSize(20).fontColor(this.keyFontColor) 226 } 227 } 228 .backgroundColor(this.keyBgc) 229 .borderRadius(6) 230 .width("8%") 231 .height("65%") 232 .onClick(() => { 233 keyboardController.insertText(this.keyValue.content); 234 }) 235 } 236 } 237 238 // 删除组件 239 @Component 240 export struct deleteItem { 241 @State keyBgc: string = "#fff" 242 @State keyFontColor: string = "#000" 243 244 build() { 245 Column() { 246 Flex({ direction: FlexDirection.Column, 247 alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { 248 Text("删除").fontSize(20).fontColor(this.keyFontColor) 249 } 250 } 251 .backgroundColor(this.keyBgc) 252 .width("13%") 253 .borderRadius(6) 254 .onClick(() => { 255 keyboardController.deleteForward(1); 256 }) 257 } 258 } 259 260 // 数字键盘 261 @Component 262 struct numberMenu { 263 private numberList: sourceListType[] = numberSourceListData; 264 265 build() { 266 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceEvenly }) { 267 Flex({ justifyContent: FlexAlign.SpaceBetween }) { 268 ForEach(this.numberList, (item: sourceListType) => { // 数字键盘第一行 269 keyItem({ keyValue: item }) 270 }, (item: sourceListType) => item.content); 271 } 272 .padding({ top: "2%" }) 273 .width("96%") 274 .height("25%") 275 276 Flex({ justifyContent: FlexAlign.SpaceBetween }) { 277 deleteItem() 278 } 279 .width("96%") 280 .height("25%") 281 } 282 } 283 } 284 285 @Entry 286 @Component 287 struct Index { 288 private numberList: sourceListType[] = numberSourceListData 289 290 build() { 291 Stack() { 292 Flex({ 293 direction: FlexDirection.Column, 294 alignItems: ItemAlign.Center, 295 justifyContent: FlexAlign.End 296 }) { 297 Flex({ 298 direction: FlexDirection.Column, 299 alignItems: ItemAlign.Center, 300 justifyContent: FlexAlign.SpaceBetween 301 }) { 302 numberMenu({ 303 numberList: this.numberList 304 }) 305 } 306 .align(Alignment.End) 307 .width("100%") 308 .height("75%") 309 } 310 .height("100%").align(Alignment.End).backgroundColor("#cdd0d7") 311 } 312 .position({ x: 0, y: 0 }).zIndex(99999) 313 } 314 } 315 ``` 316 3175. 在工程Module对应的[module.json5配置文件](../quick-start/module-configuration-file.md)中注册InputMethodExtensionAbility,type标签需要设置为“inputMethod”,srcEntry标签表示当前InputMethodExtensionAbility组件所对应的代码路径。 318 319 ```json 320 { 321 "module": { 322 ... 323 "extensionAbilities": [ 324 { 325 "description": "inputMethod", 326 "name": "InputMethodExtensionAbility", 327 "icon": "$media:app_icon", 328 "srcEntry": "./ets/InputMethodExtensionAbility/InputMethodService.ts", 329 "type": "inputMethod", 330 "exported": true, 331 } 332 ] 333 } 334 } 335 ``` 336 337## 验证方法 338 3391. 在应用中通过接口拉起输入法切换列表弹窗。 340 341 ```ts 342 import { inputMethod } from '@kit.IMEKit'; 343 import { BusinessError } from '@kit.BasicServicesKit'; 344 345 let inputMethodSetting = inputMethod.getSetting(); 346 try { 347 inputMethodSetting.showOptionalInputMethods((err: BusinessError, data: boolean) => { 348 if (err) { 349 console.error(`Failed to showOptionalInputMethods: ${JSON.stringify(err)}`); 350 return; 351 } 352 console.log('Succeeded in showing optionalInputMethods.'); 353 }); 354 } catch (err) { 355 console.error(`Failed to showOptionalInputMethods: ${JSON.stringify(err)}`); 356 } 357 ``` 358 3592. 在弹窗上显示的输入法应用列表中,选择并点击demo应用,将demo应用切换为当前输入法。 360 3613. 点击任意编辑框,即可拉起输入法demo。 362 363## 约束与限制 364 365为了降低InputMethodExtensionAbility能力被三方应用滥用的风险,现通过基础访问模式的功能约束对输入法应用进行安全管控。 366 367> **说明:** 368> 369> - 严格遵从基础访问模式的功能约束。在此模式下,开发者应仅提供基础打字功能,不应提供任何形式与网络交互相关的功能。系统会逐步增加基础访问模式的安全管控能力,包括但不限于:以独立进程和沙箱的方式运行Extension进程;禁止Extension进程创建子进程;进程间通信与网络访问等。因此未遵从此约定可能会导致功能异常。 370 371## 相关实例 372 373针对InputMethodExtensionAbility开发,有以下相关实例可供参考: 374 375- [轻量级输入法](https://gitee.com/openharmony/applications_app_samples/tree/master/code/Solutions/InputMethod/KikaInput) 376