1# 支持鼠标输入事件 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @jiangtao92--> 5<!--Designer: @piggyguy--> 6<!--Tester: @songyanhong--> 7<!--Adviser: @HelloCrease--> 8 9 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 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 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