1# 支持键盘输入事件 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @jiangtao92--> 5<!--Designer: @piggyguy--> 6<!--Tester: @songyanhong--> 7<!--Adviser: @HelloCrease--> 8 9物理按键产生的按键事件为非指向性事件,与触摸等指向性事件不同,其事件并没有坐标位置信息,所以其会按照一定次序向用户操作的焦点进行派发,一些文字输入场景,按键事件都会优先派发给输入法软键盘进行处理,以便其处理文字的联想和候选词,应用可以通过`onKeyPreIme`提前感知事件。 10 11> **说明:** 12> 13> 一些系统按键产生的事件并不会传递给UI组件,如电源键。 14 15## 按键事件数据流 16 17 18 19 20按键事件由外设键盘等设备触发,经驱动和多模处理转换后发送给当前获焦的窗口,窗口获取到事件后,会尝试分发三次事件。三次分发的优先顺序如下,一旦事件被消费,则跳过后续分发流程。 21 221. 首先分发给ArkUI框架用于触发获焦组件绑定的onKeyPreIme回调和页面快捷键。 232. 再向输入法分发,输入法会消费按键用作输入。 243. 再次将事件发给ArkUI框架,用于响应系统默认Key事件(例如走焦)以及获焦组件绑定的onKeyEvent回调。 25 26因此,当某输入框组件获焦,且打开了输入法,此时大部分按键事件均会被输入法消费。例如字母键会被输入法用来往输入框中输入对应字母字符、方向键会被输入法用来切换选中备选词。如果在此基础上给输入框组件绑定了快捷键,那么快捷键会优先响应事件,事件也不再会被输入法消费。 27 28按键事件到ArkUI框架之后,会先找到完整的父子节点获焦链。从叶子节点到根节点,逐一发送按键事件。 29 30Web组件的KeyEvent流程与上述过程有所不同。在onKeyPreIme返回false时,Web组件不会匹配快捷键。而在第三次按键派发过程中,Web组件会将未消费的KeyEvent通过ReDispatch重新派发回ArkUI,在ReDispatch中再执行匹配快捷键等操作。 31 32## onKeyEvent & onKeyPreIme 33 34```ts 35onKeyEvent(event: (event: KeyEvent) => void): T 36onKeyEvent(event: Callback<KeyEvent, boolean>): T 37onKeyPreIme(event: Callback<KeyEvent, boolean>): T 38onKeyEventDispatch(event: Callback<KeyEvent, boolean>): T 39``` 40 41上述两种方法的区别仅在于触发的时机(见[按键事件数据流](#按键事件数据流))。其中onKeyPreIme的返回值决定了该按键事件后续是否会被继续分发给页面快捷键、输入法和onKeyEvent。 42 43 44当绑定方法的组件处于获焦状态下,外设键盘的按键事件会触发该方法,回调参数为[KeyEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-key.md#keyevent对象说明),可由该参数获得当前按键事件的按键行为([KeyType](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#keytype))、键码([keyCode](../reference/apis-input-kit/js-apis-keycode.md#keycode))、按键英文名称(keyText)、事件来源设备类型([KeySource](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#keysource))、事件来源设备id(deviceId)、元键按压状态(metaKey)、时间戳(timestamp)、阻止冒泡设置(stopPropagation)。 45 46 47 48```ts 49// xxx.ets 50@Entry 51@Component 52struct KeyEventExample { 53 @State buttonText: string = ''; 54 @State buttonType: string = ''; 55 @State columnText: string = ''; 56 @State columnType: string = ''; 57 58 build() { 59 Column() { 60 Button('onKeyEvent') 61 .defaultFocus(true) 62 .width(140).height(70) 63 .onKeyEvent((event?: KeyEvent) => { // 给Button设置onKeyEvent事件 64 if(event){ 65 if (event.type === KeyType.Down) { 66 this.buttonType = 'Down'; 67 } 68 if (event.type === KeyType.Up) { 69 this.buttonType = 'Up'; 70 } 71 this.buttonText = 'Button: \n' + 72 'KeyType:' + this.buttonType + '\n' + 73 'KeyCode:' + event.keyCode + '\n' + 74 'KeyText:' + event.keyText; 75 } 76 }) 77 78 Divider() 79 Text(this.buttonText).fontColor(Color.Green) 80 81 Divider() 82 Text(this.columnText).fontColor(Color.Red) 83 }.width('100%').height('100%').justifyContent(FlexAlign.Center) 84 .onKeyEvent((event?: KeyEvent) => { // 给父组件Column设置onKeyEvent事件 85 if(event){ 86 if (event.type === KeyType.Down) { 87 this.columnType = 'Down'; 88 } 89 if (event.type === KeyType.Up) { 90 this.columnType = 'Up'; 91 } 92 this.columnText = 'Column: \n' + 93 'KeyType:' + this.columnType + '\n' + 94 'KeyCode:' + event.keyCode + '\n' + 95 'KeyText:' + event.keyText; 96 } 97 }) 98 } 99} 100``` 101 102 103上述示例中给组件Button和其父容器Column绑定onKeyEvent。应用打开页面加载后,组件树上第一个可获焦的非容器组件自动获焦,设置Button为当前页面的默认焦点,由于Button是Column的子节点,Button获焦也同时意味着Column获焦。获焦机制见[焦点事件](arkts-common-events-focus-event.md)。 104 105 106 107 108 109打开应用后,依次在键盘上按这些按键:空格、回车、左Ctrl、左Shift、字母A、字母Z。 110 111 1121. 由于onKeyEvent事件默认是冒泡的,所以Button和Column的onKeyEvent都可以响应。 113 1142. 每个按键都有2次回调,分别对应KeyType.Down和KeyType.Up,表示按键被按下、然后抬起。 115 116 117如果要阻止冒泡,即仅Button响应键盘事件,Column不响应,在Button的onKeyEvent回调中加入event.stopPropagation()方法即可,如下: 118 119 120 121```ts 122@Entry 123@Component 124struct KeyEventExample { 125 @State buttonText: string = ''; 126 @State buttonType: string = ''; 127 @State columnText: string = ''; 128 @State columnType: string = ''; 129 130 build() { 131 Column() { 132 Button('onKeyEvent') 133 .defaultFocus(true) 134 .width(140).height(70) 135 .onKeyEvent((event?: KeyEvent) => { 136 // 通过stopPropagation阻止事件冒泡 137 if(event){ 138 if(event.stopPropagation){ 139 event.stopPropagation(); 140 } 141 if (event.type === KeyType.Down) { 142 this.buttonType = 'Down'; 143 } 144 if (event.type === KeyType.Up) { 145 this.buttonType = 'Up'; 146 } 147 this.buttonText = 'Button: \n' + 148 'KeyType:' + this.buttonType + '\n' + 149 'KeyCode:' + event.keyCode + '\n' + 150 'KeyText:' + event.keyText; 151 } 152 }) 153 154 Divider() 155 Text(this.buttonText).fontColor(Color.Green) 156 157 Divider() 158 Text(this.columnText).fontColor(Color.Red) 159 }.width('100%').height('100%').justifyContent(FlexAlign.Center) 160 .onKeyEvent((event?: KeyEvent) => { // 给父组件Column设置onKeyEvent事件 161 if(event){ 162 if (event.type === KeyType.Down) { 163 this.columnType = 'Down'; 164 } 165 if (event.type === KeyType.Up) { 166 this.columnType = 'Up'; 167 } 168 this.columnText = 'Column: \n' + 169 'KeyType:' + this.columnType + '\n' + 170 'KeyCode:' + event.keyCode + '\n' + 171 'KeyText:' + event.keyText; 172 } 173 }) 174 } 175} 176``` 177 178 179 180 181使用OnKeyPreIme屏蔽在输入框中使用方向左键。 182 183```ts 184import { KeyCode } from '@kit.InputKit'; 185 186@Entry 187@Component 188struct PreImeEventExample { 189 @State buttonText: string = ''; 190 @State buttonType: string = ''; 191 @State columnText: string = ''; 192 @State columnType: string = ''; 193 194 build() { 195 Column() { 196 Search({ 197 placeholder: "Search..." 198 }) 199 .width("80%") 200 .height("40vp") 201 .border({ radius:"20vp" }) 202 .onKeyPreIme((event:KeyEvent) => { 203 if (event.keyCode == KeyCode.KEYCODE_DPAD_LEFT) { 204 return true; 205 } 206 return false; 207 }) 208 } 209 } 210} 211``` 212 213使用onKeyEventDispatch分发按键事件到子组件,子组件使用onKeyEvent。 214 215```ts 216@Entry 217@Component 218struct Index { 219 build() { 220 Row() { 221 Row() { 222 Button('button1').id('button1').onKeyEvent((event) => { 223 console.info("button1"); 224 return true 225 }) 226 Button('button2').id('button2').onKeyEvent((event) => { 227 console.info("button2"); 228 return true 229 }) 230 } 231 .width('100%') 232 .height('100%') 233 .id('Row1') 234 .onKeyEventDispatch((event) => { 235 let context = this.getUIContext(); 236 context.getFocusController().requestFocus('button1'); 237 return context.dispatchKeyEvent('button1', event); 238 }) 239 240 } 241 .height('100%') 242 .width('100%') 243 .onKeyEventDispatch((event) => { 244 if (event.type == KeyType.Down) { 245 let context = this.getUIContext(); 246 context.getFocusController().requestFocus('Row1'); 247 return context.dispatchKeyEvent('Row1', event); 248 } 249 return true; 250 }) 251 } 252} 253``` 254 255使用OnKeyPreIme实现回车提交(建议使用物理键盘)。 256 257```ts 258@Entry 259@Component 260struct TextAreaDemo { 261 @State content: string = ''; 262 @State text: string = ''; 263 controller: TextAreaController = new TextAreaController(); 264 265 build() { 266 Column() { 267 Text('Submissions: ' + this.content) 268 TextArea({ controller: this.controller, text: this.text }) 269 .onKeyPreIme((event: KeyEvent) => { 270 console.log(`${JSON.stringify(event)}`); 271 if (event.keyCode === 2054 && event.type === KeyType.Down) { // 回车键物理码 272 const hasCtrl = event?.getModifierKeyState?.(['Ctrl']); 273 if (hasCtrl) { 274 console.log('Line break'); 275 } else { 276 console.log('Submissions:' + this.text); 277 this.content = this.text; 278 this.text = ''; 279 event.stopPropagation(); 280 } 281 return true; 282 } 283 return false; 284 }) 285 .onChange((value: string) => { 286 this.text = value 287 }) 288 } 289 } 290} 291``` 292 293 294 295在输入框中输入内容后回车。 296 297