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