• 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![mouse](figures/device_mouse.png)
10
11鼠标设备是2in1类型设备必不可少的输入设备,其特点是可以通过按键达成点击或滑动操作,也可以通过滚轮触发滑动,另外还有一些按键,这些分别通过MouseEvent及AxisEvent上报给应用。
12
13>**说明:**
14>
15>所有单指可响应的触摸事件/手势事件,均可通过鼠标左键来操作和响应。
16> - 例如当我们需要开发单击Button跳转页面的功能、且需要支持手指点击和鼠标左键点击,那么只绑定一个点击事件(onClick)就可以实现该效果;
17> - 若需要针对手指和鼠标左键的点击实现不一样的效果,可以在onClick回调中,使用回调参数中的source字段判断当前触发事件的来源是手指还是鼠标。
18
19## 处理鼠标移动
20
21鼠标事件通过onMouse接口注册一个回调来接收,当鼠标事件发生时,会按照鼠标光标所在位置下的组件进行派发,派发过程同样遵循事件冒泡机制。
22
23### onMouse
24
25```ts
26onMouse(event: (event?: MouseEvent) => void)
27```
28
29鼠标事件回调。每当鼠标指针在绑定该API的组件内产生行为(MouseAction)时,触发事件回调,参数为[MouseEvent](../reference/apis-arkui/arkui-ts/ts-universal-mouse-key.md#mouseevent对象说明)对象,表示触发此次的鼠标事件。该事件支持自定义冒泡设置,默认父子冒泡。常用于开发者自定义的鼠标行为逻辑处理。
30
31
32开发者可以通过回调中的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](../reference/apis-arkui/arkui-ts/ts-gesture-customize-judge.md#baseevent8))、交互组件的区域([EventTarget](../reference/apis-arkui/arkui-ts/ts-universal-events-click.md#eventtarget8对象说明))、事件来源([SourceType](../reference/apis-arkui/arkui-ts/ts-gesture-settings.md#sourcetype枚举说明8))等。MouseEvent的回调函数stopPropagation用于设置当前事件是否阻止冒泡。
33
34>**说明:**
35>
36>按键(MouseButton)的值:Left/Right/Middle/Back/Forward 均对应鼠标上的实体按键,当这些按键被按下或松开时触发这些按键的事件。None表示无按键,会出现在鼠标没有按键按下或松开的状态下,移动鼠标所触发的事件中。
37
38```ts
39// xxx.ets
40@Entry
41@Component
42struct MouseExample {
43  @State buttonText: string = '';
44  @State columnText: string = '';
45  @State hoverText: string = 'Not Hover';
46  @State Color: Color = Color.Gray;
47
48  build() {
49    Column() {
50      Button(this.hoverText)
51        .width(200)
52        .height(100)
53        .backgroundColor(this.Color)
54        .onMouse((event?: MouseEvent) => { // 设置Button的onMouse回调
55          if (event) {
56            this.buttonText = 'Button onMouse:\n' + '' +
57              'button = ' + event.button + '\n' +
58              'action = ' + event.action + '\n' +
59              'x,y = (' + event.x + ',' + event.y + ')' + '\n' +
60              'windowXY=(' + event.windowX + ',' + event.windowY + ')';
61          }
62        })
63      Divider()
64      Text(this.buttonText).fontColor(Color.Green)
65      Divider()
66      Text(this.columnText).fontColor(Color.Red)
67    }
68    .width('100%')
69    .height('100%')
70    .justifyContent(FlexAlign.Center)
71    .borderWidth(2)
72    .borderColor(Color.Red)
73    .onMouse((event?: MouseEvent) => { // Set the onMouse callback for the column.
74      if (event) {
75        this.columnText = 'Column onMouse:\n' + '' +
76          'button = ' + event.button + '\n' +
77          'action = ' + event.action + '\n' +
78          'x,y = (' + event.x + ',' + event.y + ')' + '\n' +
79          'windowXY=(' + event.windowX + ',' + event.windowY + ')';
80      }
81    })
82  }
83}
84```
85
86上面的示例中给Button绑定onMouse接口。在回调中,打印出鼠标事件的button/action等回调参数值。同时,在外层的Column容器上,也做相同的设置。整个过程可以分为以下两个动作:
87
881. 移动鼠标:当鼠标从Button外部移入Button的过程中,仅触发了Column的onMouse回调;当鼠标移入到Button内部后,由于onMouse事件默认是冒泡的,所以此时会同时响应Column的onMouse回调和Button的onMouse回调。此过程中,由于鼠标仅有移动动作没有点击动作,因此打印信息中的button均为0(MouseButton.None的枚举值)、action均为3(MouseAction.Move的枚举值)。
89
902. 点击鼠标:鼠标进入Button后进行了2次点击,分别是左键点击和右键点击。
91   左键点击时:button = 1(MouseButton.Left的枚举值),按下时:action = 1(MouseAction.Press的枚举值),抬起时:action = 2(MouseAction.Release的枚举值)。
92
93   右键点击时:button = 2(MouseButton.Right的枚举值),按下时:action = 1(MouseAction.Press的枚举值),抬起时:action = 2(MouseAction.Release的枚举值)。
94
95
96![onMouse1](figures/onMouse1.gif)
97
98如果需要阻止鼠标事件冒泡,可以通过调用stopPropagation方法进行设置。
99
100```ts
101class ish{
102  isHovered:boolean = false
103  set(val:boolean){
104    this.isHovered = val;
105  }
106}
107class butf{
108  buttonText:string = ''
109  set(val:string){
110    this.buttonText = val
111  }
112}
113@Entry
114@Component
115struct MouseExample {
116  @State isHovered:ish = new ish()
117  build(){
118    Column(){
119      Button(this.isHovered ? 'Hovered!' : 'Not Hover')
120        .width(200)
121        .height(100)
122        .backgroundColor(this.isHovered ? Color.Green : Color.Gray)
123        .onHover((isHover?: boolean) => {
124          if(isHover) {
125            let ishset = new ish()
126            ishset.set(isHover)
127          }
128        })
129        .onMouse((event?: MouseEvent) => {
130          if (event) {
131            if (event.stopPropagation) {
132              event.stopPropagation(); // 在Button的onMouse事件中设置阻止冒泡
133            }
134            let butset = new butf()
135            butset.set('Button onMouse:\n' + '' +
136              'button = ' + event.button + '\n' +
137              'action = ' + event.action + '\n' +
138              'x,y = (' + event.x + ',' + event.y + ')' + '\n' +
139              'windowXY=(' + event.windowX + ',' + event.windowY + ')');
140          }
141        })
142    }
143  }
144}
145```
146
147在子组件(Button)的onMouse中,通过回调参数event调用stopPropagation回调方法(如下)即可阻止Button子组件的鼠标事件冒泡到父组件Column上。
148
149### onHover
150
151如果需要感知鼠标移入或移出控件范围,建议直接使用高级事件[onHover](../reference/apis-arkui/arkui-ts/ts-universal-events-hover.md#onhover),建议避免直接处理鼠标move事件,以保持代码简洁。
152
153```ts
154onHover(event: (isHover: boolean) => void)
155```
156
157悬浮事件回调。参数isHover类型为boolean,表示鼠标进入组件或离开组件。该事件不支持自定义冒泡设置,默认父子冒泡。
158
159
160若组件绑定了该接口,当鼠标指针从组件外部进入到该组件的瞬间会触发事件回调,参数isHover等于true;鼠标指针离开组件的瞬间也会触发该事件回调,参数isHover等于false。
161
162
163```ts
164// xxx.ets
165@Entry
166@Component
167struct MouseExample {
168  @State hoverText: string = 'Not Hover';
169  @State Color: Color = Color.Gray;
170
171  build() {
172    Column() {
173      Button(this.hoverText)
174        .width(200).height(100)
175        .backgroundColor(this.Color)
176        .onHover((isHover?: boolean) => { // 使用onHover接口监听鼠标是否悬浮在Button组件上
177          if (isHover) {
178            this.hoverText = 'Hovered!';
179            this.Color = Color.Green;
180          }
181          else {
182            this.hoverText = 'Not Hover';
183            this.Color = Color.Gray;
184          }
185        })
186    }.width('100%').height('100%').justifyContent(FlexAlign.Center)
187  }
188}
189```
190
191该示例创建了一个Button组件,初始背景色为灰色,内容为“Not Hover”。示例中的Button组件绑定了onHover回调,在该回调中将this.isHovered变量置为回调参数:isHover。
192
193当鼠标从Button外移动到Button内的瞬间,回调响应,isHover值等于true,isHovered的值变为true,将组件的背景色改成Color.Green,内容变为“Hovered!”。
194
195当鼠标从Button内移动到Button外的瞬间,回调响应,isHover值等于false,又将组件变成了初始的样式。
196
197![onHover](figures/onHover.gif)
198
199
200## 处理鼠标按键
201
202当用户按下鼠标上的按键时,会产生鼠标按下事件,可以通过MouseEvent访问事件的一些重要信息,如发生时间,鼠标按键(MouseButton: 左键/右键等),也可以通过**getModifierKeyState**接口获取到用户在使用鼠标时,物理键盘上的**ctrl/alt/shift**这几个修饰键的按下状态,可以通过组合判断它们的状态来实现一些便捷操作。
203
204以下是一个通过处理鼠标按键实现快速多选的示例:
205
206```typescript
207class ListDataSource implements IDataSource {
208  private list: number[] = [];
209  private listeners: DataChangeListener[] = [];
210
211  constructor(list: number[]) {
212    this.list = list;
213  }
214
215  totalCount(): number {
216    return this.list.length;
217  }
218
219  getData(index: number): number {
220    return this.list[index];
221  }
222
223  registerDataChangeListener(listener: DataChangeListener): void {
224    if (this.listeners.indexOf(listener) < 0) {
225      this.listeners.push(listener);
226    }
227  }
228
229  unregisterDataChangeListener(listener: DataChangeListener): void {
230    const pos = this.listeners.indexOf(listener);
231    if (pos >= 0) {
232      this.listeners.splice(pos, 1);
233    }
234  }
235
236  // 通知控制器数据删除
237  notifyDataDelete(index: number): void {
238    this.listeners.forEach(listener => {
239      listener.onDataDelete(index);
240    });
241  }
242
243  // 在指定索引位置删除一个元素
244  public deleteItem(index: number): void {
245    this.list.splice(index, 1);
246    this.notifyDataDelete(index);
247  }
248}
249
250@Entry
251@Component
252struct ListExample {
253  private arr: ListDataSource = new ListDataSource([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
254  private allSelectedItems: Array<number> = []
255  @State isSelected: boolean[] = []
256
257  @Styles
258  selectedStyle(): void {
259    .backgroundColor(Color.Green)
260  }
261
262  isItemSelected(item: number): boolean {
263    for (let i = 0; i < this.allSelectedItems.length; i++) {
264      if (this.allSelectedItems[i] == item) {
265        this.isSelected[item] = true;
266        return true;
267      }
268    }
269    this.isSelected[item] = false;
270    return false;
271  }
272
273  build() {
274    Column() {
275      List({ space: 10, initialIndex: 0 }) {
276        LazyForEach(this.arr, (index: number) => {
277          ListItem() {
278            Text('' + index)
279              .width('100%')
280              .height(100)
281              .fontSize(16)
282              .fontColor(this.isSelected[index] ? Color.White : Color.Black)
283              .textAlign(TextAlign.Center)
284          }
285          .backgroundColor(Color.White)
286          .selectable(true)
287          .selected(this.isSelected[index])
288          .stateStyles({
289            selected: this.selectedStyle
290          })
291          .onMouse((event: MouseEvent) => {
292            // 判断是否按下鼠标左键
293            if (event.button == MouseButton.Left && event.action == MouseAction.Press) {
294              // 判断之前是否已经时选中状态
295              let isSelected: boolean = this.isItemSelected(index)
296              // 判断修饰键状态
297              let isCtrlPressing: boolean = false
298              if (event.getModifierKeyState) {
299                isCtrlPressing = event.getModifierKeyState(['Ctrl'])
300              }
301              // 如果没有按着ctrl键点鼠标,则强制清理掉其他选中的条目并只让当前条目选中
302              if (!isCtrlPressing) {
303                this.allSelectedItems = []
304                for (let i = 0; i < this.isSelected.length; i++) {
305                  this.isSelected[i] = false
306                }
307              }
308              if (isSelected) {
309                this.allSelectedItems.filter(item => item != index)
310                this.isSelected[index] = false
311              } else {
312                this.allSelectedItems.push(index)
313                this.isSelected[index] = true
314              }
315            }
316          })
317        }, (item: string) => item)
318      }
319      .listDirection(Axis.Vertical)
320      .scrollBar(BarState.Off)
321      .friction(0.6)
322      .edgeEffect(EdgeEffect.Spring)
323      .width('90%')
324    }
325    .width('100%')
326    .height('100%')
327    .backgroundColor(0xDCDCDC)
328    .padding({ top: 5 })
329  }
330}
331```
332
333
334## 处理滚轮
335
336鼠标的滚轮是一种可以产生纵向滚动量的输入设备,当用户滚动鼠标滚轮时,系统会产生纵向轴事件上报,应用可在组件上通过`onAxisEvent(event: (event: AxisEvent) => void): T`接口接收轴事件,轴事件中上报的坐标,为鼠标光标所在的位置,而滚轮上报的角度变化可从axisVertical中获得。
337鼠标滚轮轴事件的上报,每次都以Begin类型开始,当停止滚动时以End结束,慢速滚动时,会产生多段的Begin,End上报。当你处理axisVertical时,应确保理解它的数值含义与单位,其有以下特点:
338- 上报的数值单位为角度,为单次变化量,非总量。
339- 上报数值大小受系统设置中对滚轮放大倍数设置的影响。
340- 系统设置中的放大倍数通过AxisEvent中的scrollStep告知。
341- 向前滚动,上报数值为负,向后滚动,上报数值为正。
342
343如果使用滚动类组件,对于滚轮的响应,系统内部已实现,不需要额外处理。
344如果使用[PanGesture](../reference/apis-arkui/arkui-ts/ts-basic-gestures-pangesture.md),对于滚轮的响应,此时向前滚动,offsetY的上报数值为正,向后滚动,offsetY的上报数值为负。
345
346> **说明:**
347>
348> 1. 滚轮产生的纵向轴值,一般情况下只能触发纵向滚动手势,无法触发横向滚动。
349> 2. 系统会在发现鼠标指针下只有能够响应横向滚动的组件时,也可以触发横向滚动。
350> 3. 但只要指针下有一个可以响应纵向滚动,则会优先处理纵向,不再处理横向。
351
352