• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![zh-cn_image_0000001511580944](figures/zh-cn_image_0000001511580944.png)
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![zh-cn_image_0000001511421324](figures/zh-cn_image_0000001511421324.gif)
107
108
109打开应用后,依次在键盘上按这些按键:空格、回车、左Ctrl、左Shift、字母A、字母Z。
110
111
1121. 由于onKeyEvent事件默认是冒泡的,所以Button和Column的onKeyEvent都可以响应。
113
1142. 每个按键都有2次回调,分别对应KeyType.DownKeyType.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![zh-cn_image_0000001511900508](figures/zh-cn_image_0000001511900508.gif)
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![onKeyPreIme1](figures/onKeyPreIme1.png)
294
295在输入框中输入内容后回车。
296
297![onKeyPreIme2](figures/onKeyPreIme2.png)