文档管理中心
我的
您当前正在浏览新版开发者文档中心,目录分类和层级有所调整。点击左侧当前文档分类名称前的“☰”图标,可切换文档分类。 了解新版目录

支持键盘输入事件

物理按键产生的按键事件为非指向性事件,与触摸等指向性事件不同,其事件并没有坐标位置信息,所以其会按照一定次序向获焦组件进行派发,大多数文字输入场景下,按键事件都会优先派发给输入法进行处理,以便其处理文字的联想和候选词,应用可以通过onKeyPreIme提前感知事件。

说明

一些系统按键产生的事件并不会传递给UI组件,如电源键。

按键事件数据流

按键事件由外设键盘等设备触发,经驱动和多模处理转换后发送给当前获焦的窗口,窗口获取到事件后,会尝试分发三次事件。三次分发的优先顺序如下,一旦事件被消费,则跳过后续分发流程。

  1. 首先分发给ArkUI框架用于触发获焦组件绑定的onKeyPreIme回调和页面快捷键。
  2. 再向输入法分发,输入法会消费按键用作输入。
  3. 再次将事件发给ArkUI框架,用于响应onKeyEventDispatch事件、获焦组件绑定的onKeyEvent回调以及走焦。

因此,当某输入框组件获焦,且打开了输入法,此时大部分按键事件均会被输入法消费。例如字母键会被输入法用来往输入框中输入对应字母字符、方向键会被输入法用来切换选中备选词。如果在此基础上给输入框组件绑定了快捷键,那么快捷键会优先响应事件,事件也不再会被输入法消费。

按键事件到ArkUI框架之后,会先找到完整的节点获焦链。从叶子节点到根节点,逐一发送按键事件,若有子组件可以处理则优先给子组件处理,若子组件无法处理,则进行冒泡寻找父组件进行处理。

Web组件的KeyEvent流程与上述过程有所不同。在onKeyPreIme返回false时,Web组件不会匹配快捷键。而在第三次按键派发过程中,Web组件会将未消费的KeyEvent重新派发回ArkUI,在重新派发过程中再执行匹配快捷键等操作。

onKeyEvent、onKeyPreIme和onKeyEventDispatch

收起
自动换行
深色代码主题
复制
  1. onKeyEvent(event: (event: KeyEvent) => void): T
  2. onKeyEvent(event: Callback<KeyEvent, boolean>): T
  3. onKeyPreIme(event: Callback<KeyEvent, boolean>): T
  4. onKeyEventDispatch(event: Callback<KeyEvent, boolean>): T

上述四种方法的区别仅在于触发的时机(见按键事件数据流)。其中onKeyPreIme的返回值决定了该按键事件后续是否会被继续分发给页面快捷键、输入法、onKeyEventDispatch和onKeyEvent。

当绑定方法的组件处于获焦状态下,外设键盘的按键事件会触发该方法,回调参数为KeyEvent,可由该参数获得当前按键事件的按键行为(KeyType)、键码(KeyCode)、按键英文名称(keyText)、事件来源设备类型(KeySource)、事件来源设备id(deviceId)、元键按压状态(metaKey)、时间戳(timestamp)、阻止冒泡设置(stopPropagation)。

收起
自动换行
深色代码主题
复制
  1. @Entry
  2. @Component
  3. struct KeyEventExample {
  4. @State buttonText: string = '';
  5. @State buttonType: string = '';
  6. @State columnText: string = '';
  7. @State columnType: string = '';
  8. build() {
  9. Column() {
  10. Button('onKeyEvent')
  11. .defaultFocus(true)
  12. .width(140).height(70)
  13. .onKeyEvent((event?: KeyEvent) => { // 给Button设置onKeyEvent事件
  14. if (event) {
  15. if (event.type === KeyType.Down) {
  16. this.buttonType = 'Down';
  17. }
  18. if (event.type === KeyType.Up) {
  19. this.buttonType = 'Up';
  20. }
  21. this.buttonText = 'Button: \n' +
  22. 'KeyType:' + this.buttonType + '\n' +
  23. 'KeyCode:' + event.keyCode + '\n' +
  24. 'KeyText:' + event.keyText;
  25. }
  26. })
  27. Divider()
  28. Text(this.buttonText).fontColor(Color.Green)
  29. Divider()
  30. Text(this.columnText).fontColor(Color.Red)
  31. }.width('100%').height('100%').justifyContent(FlexAlign.Center)
  32. .onKeyEvent((event?: KeyEvent) => { // 给父组件Column设置onKeyEvent事件
  33. if (event) {
  34. if (event.type === KeyType.Down) {
  35. this.columnType = 'Down';
  36. }
  37. if (event.type === KeyType.Up) {
  38. this.columnType = 'Up';
  39. }
  40. this.columnText = 'Column: \n' +
  41. 'KeyType:' + this.columnType + '\n' +
  42. 'KeyCode:' + event.keyCode + '\n' +
  43. 'KeyText:' + event.keyText;
  44. }
  45. })
  46. }
  47. }

上述示例中给组件Button和其父容器Column绑定onKeyEvent。应用打开页面加载后,组件树上第一个可获焦的非容器组件自动获焦,设置Button为当前页面的默认焦点,由于Button是Column的子节点,Button获焦也同时意味着Column获焦。获焦机制见支持焦点处理。

打开应用后,依次在键盘上按这些按键:空格、回车、左Ctrl、左Shift、字母A、字母Z。

  1. 由于onKeyEvent事件默认是冒泡的,所以Button和Column的onKeyEvent都可以响应。

  2. 每个按键都有2次回调,分别对应KeyType.Down和KeyType.Up,表示按键被按下,然后抬起。

如果要阻止冒泡,即仅Button响应键盘事件,Column不响应,在Button的onKeyEvent回调中加入event.stopPropagation()方法即可,如下:

收起
自动换行
深色代码主题
复制
  1. @Entry
  2. @Component
  3. struct KeyEventPreventBubble {
  4. @State buttonText: string = '';
  5. @State buttonType: string = '';
  6. @State columnText: string = '';
  7. @State columnType: string = '';
  8. build() {
  9. Column() {
  10. Button('onKeyEvent')
  11. .defaultFocus(true)
  12. .width(140).height(70)
  13. .onKeyEvent((event?: KeyEvent) => {
  14. // 通过stopPropagation阻止事件冒泡
  15. if (event) {
  16. if (event.stopPropagation) {
  17. event.stopPropagation();
  18. }
  19. if (event.type === KeyType.Down) {
  20. this.buttonType = 'Down';
  21. }
  22. if (event.type === KeyType.Up) {
  23. this.buttonType = 'Up';
  24. }
  25. this.buttonText = 'Button: \n' +
  26. 'KeyType:' + this.buttonType + '\n' +
  27. 'KeyCode:' + event.keyCode + '\n' +
  28. 'KeyText:' + event.keyText;
  29. }
  30. })
  31. Divider()
  32. Text(this.buttonText).fontColor(Color.Green)
  33. Divider()
  34. Text(this.columnText).fontColor(Color.Red)
  35. }.width('100%').height('100%').justifyContent(FlexAlign.Center)
  36. .onKeyEvent((event?: KeyEvent) => { // 给父组件Column设置onKeyEvent事件
  37. if (event) {
  38. if (event.type === KeyType.Down) {
  39. this.columnType = 'Down';
  40. }
  41. if (event.type === KeyType.Up) {
  42. this.columnType = 'Up';
  43. }
  44. this.columnText = 'Column: \n' +
  45. 'KeyType:' + this.columnType + '\n' +
  46. 'KeyCode:' + event.keyCode + '\n' +
  47. 'KeyText:' + event.keyText;
  48. }
  49. })
  50. }
  51. }

使用onKeyPreIme屏蔽在输入框中使用方向左键。

收起
自动换行
深色代码主题
复制
  1. import { KeyCode } from '@kit.InputKit';
  2. @Entry
  3. @Component
  4. struct PreImeEventExample {
  5. @State buttonText: string = '';
  6. @State buttonType: string = '';
  7. @State columnText: string = '';
  8. @State columnType: string = '';
  9. build() {
  10. Column() {
  11. Search({
  12. placeholder: 'Search...'
  13. })
  14. .width('80%')
  15. .height('40vp')
  16. .border({ radius: '20vp' })
  17. .onKeyPreIme((event: KeyEvent) => {
  18. if (event.keyCode == KeyCode.KEYCODE_DPAD_LEFT) {
  19. return true;
  20. }
  21. return false;
  22. })
  23. }
  24. }
  25. }

使用onKeyEventDispatch分发按键事件到子组件,子组件使用onKeyEvent。

收起
自动换行
深色代码主题
复制
  1. import { hilog } from '@kit.PerformanceAnalysisKit';
  2. const TAG = '[Sample_Eventproject]';
  3. const DOMAIN = 0xF811;
  4. const BUNDLE = 'Eventproject_';
  5. @Entry
  6. @Component
  7. struct Index {
  8. build() {
  9. Row() {
  10. Row() {
  11. Button('button1')
  12. .id('button1')
  13. .margin({ left: 70, right: 30 })
  14. .onKeyEvent((event) => {
  15. hilog.info(DOMAIN, TAG, BUNDLE + 'button1');
  16. return true;
  17. })
  18. Button('button2')
  19. .id('button2')
  20. .onKeyEvent((event) => {
  21. hilog.info(DOMAIN, TAG, BUNDLE + 'button2');
  22. return true;
  23. })
  24. }
  25. .width('100%')
  26. .height('100%')
  27. .id('Row1')
  28. .onKeyEventDispatch((event) => {
  29. let context = this.getUIContext();
  30. context.getFocusController().requestFocus('button1');
  31. return context.dispatchKeyEvent('button1', event);
  32. })
  33. }
  34. .height('100%')
  35. .width('100%')
  36. .onKeyEventDispatch((event) => {
  37. if (event.type == KeyType.Down) {
  38. let context = this.getUIContext();
  39. context.getFocusController().requestFocus('Row1');
  40. return context.dispatchKeyEvent('Row1', event);
  41. }
  42. return true;
  43. })
  44. }
  45. }

使用onKeyPreIme实现回车提交(建议使用物理键盘)。

收起
自动换行
深色代码主题
复制
  1. import { hilog } from '@kit.PerformanceAnalysisKit';
  2. import { KeyCode } from '@kit.InputKit';
  3. const TAG = '[Sample_Eventproject]';
  4. const DOMAIN = 0xF811;
  5. const BUNDLE = 'Eventproject_';
  6. @Entry
  7. @Component
  8. struct TextAreaDemo {
  9. @State content: string = '';
  10. @State text: string = '';
  11. controller: TextAreaController = new TextAreaController();
  12. build() {
  13. Column() {
  14. Text('Submissions: ' + this.content)
  15. TextArea({ controller: this.controller, text: this.text })
  16. .onKeyPreIme((event: KeyEvent) => {
  17. hilog.info(DOMAIN, TAG, `${BUNDLE + JSON.stringify(event)}`);
  18. if (event.keyCode === KeyCode.KEYCODE_ENTER && event.type === KeyType.Down) { // 回车键物理码
  19. const hasCtrl = event?.getModifierKeyState?.(['Ctrl']);
  20. if (hasCtrl) {
  21. hilog.info(DOMAIN, TAG, BUNDLE + 'Line break');
  22. } else {
  23. hilog.info(DOMAIN, TAG, BUNDLE + 'Submissions:' + this.text);
  24. this.content = this.text;
  25. this.text = '';
  26. event.stopPropagation();
  27. }
  28. return true;
  29. }
  30. return false;
  31. })
  32. .onChange((value: string) => {
  33. this.text = value;
  34. })
  35. }
  36. }
  37. }

在输入框中输入内容后回车。