1# 键鼠事件
2
3
4键鼠事件指键盘,鼠标外接设备的输入事件。
5
6
7## 鼠标事件
8
9支持的鼠标事件包含通过外设鼠标、触控板触发的事件。
10
11鼠标事件可触发以下回调:
12
13| 名称                                       | 描述                                       |
14| ---------------------------------------- | ---------------------------------------- |
15| onHover(event:&nbsp;(isHover:&nbsp;boolean)&nbsp;=&gt;&nbsp;void) | 鼠标进入或退出组件时触发该回调。<br/>isHover:表示鼠标是否悬浮在组件上,鼠标进入时为true,&nbsp;退出时为false。 |
16| onMouse(event:&nbsp;(event?:&nbsp;MouseEvent)&nbsp;=&gt;&nbsp;void) | 当前组件被鼠标按键点击时或者鼠标在组件上悬浮移动时,触发该回调,event返回值包含触发事件时的时间戳、鼠标按键、动作、鼠标位置在整个屏幕上的坐标和相对于当前组件的坐标。 |
17
18当组件绑定onHover回调时,可以通过[hoverEffect](../reference/apis-arkui/arkui-ts/ts-universal-attributes-hover-effect.md#hovereffect)属性设置该组件的鼠标悬浮态显示效果。
19
20
21  **图1** 鼠标事件数据流  
22
23
24![zh-cn_image_0000001511900504](figures/zh-cn_image_0000001511900504.png)
25
26
27鼠标事件传递到ArkUI之后,会先判断鼠标事件是否是左键的按下/抬起/移动,然后做出不同响应:
28
29
30- 是:鼠标事件先转换成相同位置的触摸事件,执行触摸事件的碰撞测试、手势判断和回调响应。接着去执行鼠标事件的碰撞测试和回调响应。
31
32- 否:事件仅用于执行鼠标事件的碰撞测试和回调响应。
33
34
35>**说明:**
36>
37>所有单指可响应的触摸事件/手势事件,均可通过鼠标左键来操作和响应。例如当我们需要开发单击Button跳转页面的功能、且需要支持手指点击和鼠标左键点击,那么只绑定一个点击事件(onClick)就可以实现该效果。若需要针对手指和鼠标左键的点击实现不一样的效果,可以在onClick回调中,使用回调参数中的source字段即可判断出当前触发事件的来源是手指还是鼠标。
38
39
40### onHover
41
42
43```ts
44onHover(event: (isHover: boolean) => void)
45```
46
47
48鼠标悬浮事件回调。参数isHover类型为boolean,表示鼠标进入组件或离开组件。该事件不支持自定义冒泡设置,默认父子冒泡。
49
50
51若组件绑定了该接口,当鼠标指针从组件外部进入到该组件的瞬间会触发事件回调,参数isHover等于true;鼠标指针离开组件的瞬间也会触发该事件回调,参数isHover等于false。
52
53
54>**说明:**
55>
56>事件冒泡:在一个树形结构中,当子节点处理完一个事件后,再将该事件交给它的父节点处理。
57
58
59
60
61```ts
62// xxx.ets
63@Entry
64@Component
65struct MouseExample {
66  @State hoverText: string = 'Not Hover';
67  @State Color: Color = Color.Gray;
68
69  build() {
70    Column() {
71      Button(this.hoverText)
72        .width(200).height(100)
73        .backgroundColor(this.Color)
74        .onHover((isHover?: boolean) => { // 使用onHover接口监听鼠标是否悬浮在Button组件上
75          if (isHover) {
76            this.hoverText = 'Hovered!';
77            this.Color = Color.Green;
78          }
79          else {
80            this.hoverText = 'Not Hover';
81            this.Color = Color.Gray;
82          }
83        })
84    }.width('100%').height('100%').justifyContent(FlexAlign.Center)
85  }
86}
87```
88
89
90该示例创建了一个Button组件,初始背景色为灰色,内容为“Not Hover”。示例中的Button组件绑定了onHover回调,在该回调中将this.isHovered变量置为回调参数:isHover。
91
92
93当鼠标从Button外移动到Button内的瞬间,回调响应,isHover值等于true,isHovered的值变为true,将组件的背景色改成Color.Green,内容变为“Hovered!”。
94
95
96当鼠标从Button内移动到Button外的瞬间,回调响应,isHover值等于false,又将组件变成了初始的样式。
97
98
99![onHover](figures/onHover.gif)
100
101
102### onMouse
103
104
105```ts
106onMouse(event: (event?: MouseEvent) => void)
107```
108
109
110鼠标事件回调。绑定该API的组件每当鼠标指针在该组件内产生行为(MouseAction)时,触发事件回调,参数为[MouseEvent](../reference/apis-arkui/arkui-ts/ts-universal-mouse-key.md#mouseevent对象说明)对象,表示触发此次的鼠标事件。该事件支持自定义冒泡设置,默认父子冒泡。常用于开发者自定义的鼠标行为逻辑处理。
111
112
113开发者可以通过回调中的MouseEvent对象获取触发事件的坐标(displayX/displayY/windowX/windowY/x/y)、按键([MouseButton](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#mousebutton8))、行为([MouseAction](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#mouseaction8))、时间戳(timestamp)、交互组件的区域([EventTarget](../reference/apis-arkui/arkui-ts/ts-universal-events-click.md#eventtarget8对象说明))、事件来源([SourceType](../reference/apis-arkui/arkui-ts/ts-gesture-settings.md#sourcetype枚举说明))等。MouseEvent的回调函数stopPropagation用于设置当前事件是否阻止冒泡。
114
115
116>**说明:**
117>
118>按键(MouseButton)的值:Left/Right/Middle/Back/Forward 均对应鼠标上的实体按键,当这些按键被按下或松开时触发这些按键的事件。None表示无按键,会出现在鼠标没有按键按下或松开的状态下,移动鼠标所触发的事件中。
119
120
121
122```ts
123// xxx.ets
124@Entry
125@Component
126struct MouseExample {
127  @State buttonText: string = '';
128  @State columnText: string = '';
129  @State hoverText: string = 'Not Hover';
130  @State Color: Color = Color.Gray;
131
132  build() {
133    Column() {
134      Button(this.hoverText)
135        .width(200)
136        .height(100)
137        .backgroundColor(this.Color)
138        .onHover((isHover?: boolean) => {
139          if (isHover) {
140            this.hoverText = 'Hovered!';
141            this.Color = Color.Green;
142          }
143          else {
144            this.hoverText = 'Not Hover';
145            this.Color = Color.Gray;
146          }
147        })
148        .onMouse((event?: MouseEvent) => { // 设置Button的onMouse回调
149          if (event) {
150            this.buttonText = 'Button onMouse:\n' + '' +
151              'button = ' + event.button + '\n' +
152              'action = ' + event.action + '\n' +
153              'x,y = (' + event.x + ',' + event.y + ')' + '\n' +
154              'windowXY=(' + event.windowX + ',' + event.windowY + ')';
155          }
156        })
157      Divider()
158      Text(this.buttonText).fontColor(Color.Green)
159      Divider()
160      Text(this.columnText).fontColor(Color.Red)
161    }
162    .width('100%')
163    .height('100%')
164    .justifyContent(FlexAlign.Center)
165    .borderWidth(2)
166    .borderColor(Color.Red)
167    .onMouse((event?: MouseEvent) => { // Set the onMouse callback for the column.
168      if (event) {
169        this.columnText = 'Column onMouse:\n' + '' +
170          'button = ' + event.button + '\n' +
171          'action = ' + event.action + '\n' +
172          'x,y = (' + event.x + ',' + event.y + ')' + '\n' +
173          'windowXY=(' + event.windowX + ',' + event.windowY + ')';
174      }
175    })
176  }
177}
178```
179
180
181在onHover示例的基础上,给Button绑定onMouse接口。在回调中,打印出鼠标事件的button/action等回调参数值。同时,在外层的Column容器上,也做相同的设置。整个过程可以分为以下两个动作:
182
183
1841. 移动鼠标:当鼠标从Button外部移入Button的过程中,仅触发了Column的onMouse回调;当鼠标移入到Button内部后,由于onMouse事件默认是冒泡的,所以此时会同时响应Column的onMouse回调和Button的onMouse回调。此过程中,由于鼠标仅有移动动作没有点击动作,因此打印信息中的button均为0(MouseButton.None的枚举值)、action均为3(MouseAction.Move的枚举值)。
185
1862. 点击鼠标:鼠标进入Button后进行了2次点击,分别是左键点击和右键点击。
187   左键点击时:button = 1(MouseButton.Left的枚举值),按下时:action = 1(MouseAction.Press的枚举值),抬起时:action = 2(MouseAction.Release的枚举值)。
188
189   右键点击时:button = 2(MouseButton.Right的枚举值),按下时:action = 1(MouseAction.Press的枚举值),抬起时:action = 2(MouseAction.Release的枚举值)。
190
191
192![onMouse1](figures/onMouse1.gif)
193
194
195如果需要阻止鼠标事件冒泡,可以通过调用stopPropagation()方法进行设置。
196
197
198
199```ts
200class ish{
201  isHovered:boolean = false
202  set(val:boolean){
203    this.isHovered = val;
204  }
205}
206class butf{
207  buttonText:string = ''
208  set(val:string){
209    this.buttonText = val
210  }
211}
212@Entry
213@Component
214struct MouseExample {
215  @State isHovered:ish = new ish()
216  build(){
217    Column(){
218      Button(this.isHovered ? 'Hovered!' : 'Not Hover')
219        .width(200)
220        .height(100)
221        .backgroundColor(this.isHovered ? Color.Green : Color.Gray)
222        .onHover((isHover?: boolean) => {
223          if(isHover) {
224            let ishset = new ish()
225            ishset.set(isHover)
226          }
227        })
228        .onMouse((event?: MouseEvent) => {
229          if (event) {
230            if (event.stopPropagation) {
231              event.stopPropagation(); // 在Button的onMouse事件中设置阻止冒泡
232            }
233            let butset = new butf()
234            butset.set('Button onMouse:\n' + '' +
235              'button = ' + event.button + '\n' +
236              'action = ' + event.action + '\n' +
237              'x,y = (' + event.x + ',' + event.y + ')' + '\n' +
238              'windowXY=(' + event.windowX + ',' + event.windowY + ')');
239          }
240        })
241    }
242  }
243}
244```
245
246
247在子组件(Button)的onMouse中,通过回调参数event调用stopPropagation回调方法(如下)即可阻止Button子组件的鼠标事件冒泡到父组件Column上。
248
249
250
251```ts
252event.stopPropagation()
253```
254
255
256效果是:当鼠标在Button组件上操作时,仅Button的onMouse回调会响应,Column的onMouse回调不会响应。
257
258
259### hoverEffect
260
261
262```ts
263hoverEffect(value: HoverEffect)
264```
265
266
267鼠标悬浮态效果设置的通用属性。参数类型为HoverEffect,HoverEffect提供的Auto、Scale、Highlight效果均为固定效果,开发者无法自定义设置效果参数。
268
269
270  **表1** HoverEffect说明
271
272| HoverEffect枚举值 | 效果说明                                     |
273| -------------- | ---------------------------------------- |
274| Auto           | 组件默认提供的悬浮态效果,由各组件定义。                     |
275| Scale          | 动画播放方式,鼠标悬浮时:组件大小从100%放大至105%,鼠标离开时:组件大小从105%缩小至100%。 |
276| Highlight      | 动画播放方式,鼠标悬浮时:组件背景色叠加一个5%透明度的白色,视觉效果是组件的原有背景色变暗,鼠标离开时:组件背景色恢复至原有样式。 |
277| None           | 禁用悬浮态效果。                                  |
278
279
280
281```ts
282// xxx.ets
283@Entry
284@Component
285struct HoverExample {
286  build() {
287    Column({ space: 10 }) {
288      Button('Auto')
289        .width(170).height(70)
290      Button('Scale')
291        .width(170).height(70)
292        .hoverEffect(HoverEffect.Scale)
293      Button('Highlight')
294        .width(170).height(70)
295        .hoverEffect(HoverEffect.Highlight)
296      Button('None')
297        .width(170).height(70)
298        .hoverEffect(HoverEffect.None)
299    }.width('100%').height('100%').justifyContent(FlexAlign.Center)
300  }
301}
302```
303
304
305![hoverEffect](figures/hoverEffect.gif)
306
307
308Button默认的悬浮态效果就是Highlight效果,因此Auto和Highlight的效果一样,Highlight会使背板颜色变暗,Scale会让组件缩放,None会禁用悬浮态效果。
309
310
311## 按键事件
312
313### 按键事件数据流
314
315![zh-cn_image_0000001511580944](figures/zh-cn_image_0000001511580944.png)
316
317
318按键事件由外设键盘等设备触发,经驱动和多模处理转换后发送给当前获焦的窗口,窗口获取到事件后,会尝试分发三次事件。三次分发的优先顺序如下,一旦事件被消费,则跳过后续分发流程。
319
3201. 首先分发给ArkUI框架用于触发获焦组件绑定的onKeyPreIme回调和页面快捷键。
3212. 再向输入法分发,输入法会消费按键用作输入。
3223. 再次将事件发给ArkUI框架,用于响应系统默认Key事件(例如走焦),以及获焦组件绑定的onKeyEvent回调。
323
324因此,当某输入框组件获焦,且打开了输入法,此时大部分按键事件均会被输入法消费。例如字母键会被输入法用来往输入框中输入对应字母字符、方向键会被输入法用来切换选中备选词。如果在此基础上给输入框组件绑定了快捷键,那么快捷键会优先响应事件,事件也不再会被输入法消费。
325
326按键事件到ArkUI框架之后,会先找到完整的父子节点获焦链。从叶子节点到根节点,逐一发送按键事件。
327
328Web组件的KeyEvent流程与上述过程有所不同。对于Web组件,不会在onKeyPreIme返回false时候,去匹配快捷。而是第三次按键派发中,Web对于未消费的KeyEvent会通过ReDispatch重新派发回ArkUI。在ReDispatch中再执行匹配快捷键等操作。
329
330### onKeyEvent & onKeyPreIme
331
332
333```ts
334onKeyEvent(event: (event: KeyEvent) => void): T
335onKeyPreIme(event: Callback<KeyEvent, boolean>): T
336```
337
338
339上述两种方法的区别仅在于触发的时机(见 [按键事件数据流](#按键事件数据流))。其中onKeyPreIme的返回值决定了该按键事件后续是否会被继续分发给页面快捷键、输入法和onKeyEvent。
340
341
342当绑定方法的组件处于获焦状态下,外设键盘的按键事件会触发该方法,回调参数为[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)。
343
344
345
346```ts
347// xxx.ets
348@Entry
349@Component
350struct KeyEventExample {
351  @State buttonText: string = '';
352  @State buttonType: string = '';
353  @State columnText: string = '';
354  @State columnType: string = '';
355
356  build() {
357    Column() {
358      Button('onKeyEvent')
359        .defaultFocus(true)
360        .width(140).height(70)
361        .onKeyEvent((event?: KeyEvent) => { // 给Button设置onKeyEvent事件
362          if(event){
363            if (event.type === KeyType.Down) {
364              this.buttonType = 'Down';
365            }
366            if (event.type === KeyType.Up) {
367              this.buttonType = 'Up';
368            }
369            this.buttonText = 'Button: \n' +
370            'KeyType:' + this.buttonType + '\n' +
371            'KeyCode:' + event.keyCode + '\n' +
372            'KeyText:' + event.keyText;
373          }
374        })
375
376      Divider()
377      Text(this.buttonText).fontColor(Color.Green)
378
379      Divider()
380      Text(this.columnText).fontColor(Color.Red)
381    }.width('100%').height('100%').justifyContent(FlexAlign.Center)
382    .onKeyEvent((event?: KeyEvent) => { // 给父组件Column设置onKeyEvent事件
383      if(event){
384        if (event.type === KeyType.Down) {
385          this.columnType = 'Down';
386        }
387        if (event.type === KeyType.Up) {
388          this.columnType = 'Up';
389        }
390        this.columnText = 'Column: \n' +
391        'KeyType:' + this.buttonType + '\n' +
392        'KeyCode:' + event.keyCode + '\n' +
393        'KeyText:' + event.keyText;
394      }
395    })
396  }
397}
398```
399
400
401上述示例中给组件Button和其父容器Column绑定onKeyEvent。应用打开页面加载后,组件树上第一个可获焦的非容器组件自动获焦,设置Button为当前页面的默认焦点,由于Button是Column的子节点,Button获焦也同时意味着Column获焦。获焦机制见[焦点事件](arkts-common-events-focus-event.md)。
402
403
404![zh-cn_image_0000001511421324](figures/zh-cn_image_0000001511421324.gif)
405
406
407打开应用后,依次在键盘上按这些按键:“空格、回车、左Ctrl、左Shift、字母A、字母Z”。
408
409
4101. 由于onKeyEvent事件默认是冒泡的,所以Button和Column的onKeyEvent都可以响应。
411
4122. 每个按键都有2次回调,分别对应KeyType.DownKeyType.Up,表示按键被按下、然后抬起。
413
414
415如果要阻止冒泡,即仅Button响应键盘事件,Column不响应,在Button的onKeyEvent回调中加入event.stopPropagation()方法即可,如下:
416
417
418
419```ts
420@Entry
421@Component
422struct KeyEventExample {
423  @State buttonText: string = '';
424  @State buttonType: string = '';
425  @State columnText: string = '';
426  @State columnType: string = '';
427
428  build() {
429    Column() {
430      Button('onKeyEvent')
431        .defaultFocus(true)
432        .width(140).height(70)
433        .onKeyEvent((event?: KeyEvent) => {
434          // 通过stopPropagation阻止事件冒泡
435          if(event){
436            if(event.stopPropagation){
437              event.stopPropagation();
438            }
439            if (event.type === KeyType.Down) {
440              this.buttonType = 'Down';
441            }
442            if (event.type === KeyType.Up) {
443              this.buttonType = 'Up';
444            }
445            this.buttonText = 'Button: \n' +
446              'KeyType:' + this.buttonType + '\n' +
447              'KeyCode:' + event.keyCode + '\n' +
448              'KeyText:' + event.keyText;
449          }
450        })
451
452      Divider()
453      Text(this.buttonText).fontColor(Color.Green)
454
455      Divider()
456      Text(this.columnText).fontColor(Color.Red)
457    }.width('100%').height('100%').justifyContent(FlexAlign.Center)
458    .onKeyEvent((event?: KeyEvent) => { // 给父组件Column设置onKeyEvent事件
459      if(event){
460        if (event.type === KeyType.Down) {
461          this.columnType = 'Down';
462        }
463        if (event.type === KeyType.Up) {
464          this.columnType = 'Up';
465        }
466        this.columnText = 'Column: \n' +
467          'KeyType:' + this.buttonType + '\n' +
468          'KeyCode:' + event.keyCode + '\n' +
469          'KeyText:' + event.keyText;
470      }
471    })
472  }
473}
474```
475
476
477![zh-cn_image_0000001511900508](figures/zh-cn_image_0000001511900508.gif)
478
479使用OnKeyPreIme屏蔽在输入框中使用方向左键。
480```ts
481import { KeyCode } from '@kit.InputKit';
482
483@Entry
484@Component
485struct PreImeEventExample {
486  @State buttonText: string = '';
487  @State buttonType: string = '';
488  @State columnText: string = '';
489  @State columnType: string = '';
490
491  build() {
492    Column() {
493      Search({
494        placeholder: "Search..."
495      })
496        .width("80%")
497        .height("40vp")
498        .border({ radius:"20vp" })
499        .onKeyPreIme((event:KeyEvent) => {
500          if (event.keyCode == KeyCode.KEYCODE_DPAD_LEFT) {
501            return true;
502          }
503          return false;
504        })
505    }
506  }
507}
508```
509