当前位置: 首页 > news >正文

HarmonyOS 5.1手势事件详解

大家好,我是 V 哥。
手势事件由绑定手势方法和绑定的手势组成,绑定的手势可以分为单一手势和组合手势两种类型,根据手势的复杂程度进行区分。本文跟着 V 哥一起来探讨手势事件处理。

想要考取鸿蒙认证的小伙伴,请加入V 哥班级获取辅导:

https://developer.huawei.com/consumer/cn/training/classDetail/042cb1cc4d7d44ecbdbd902fd1275dcc?type=1

V哥写的鸿蒙全系例图书,助你从入门到一名成熟的开发者:
![](https://files.mdnice.com/user/57732/ea8a430a-5ef1-4b41-9946-f90867c21963.png)


## 一、绑定手势方法
通过给各个组件绑定不同的手势事件,并设计事件的响应方式,当手势识别成功时,ArkUI框架将通过事件回调通知组件手势识别的结果。

1. gesture(常规手势绑定方法),gesture为通用的一种手势绑定方法,可以将手势绑定到对应的组件上,方法格式如下所示:
```
.gesture(gesture: GestureType, mask?: GestureMask)
```
2. priorityGesture(带优先级的手势绑定方法),priorityGesture是带优先级的手势绑定方法,可以在组件上绑定优先识别的手势,方法格式如下所示:
```
.priorityGesture(gesture: GestureType, mask?: GestureMask)
```
在默认情况下,当父组件和子组件使用gesture绑定同类型的手势时,子组件优先识别通过gesture绑定的手势。当父组件使用priorityGesture绑定与子组件同类型的手势时,父组件优先识别通过priorityGesture绑定的手势。长按手势时,设置触发长按的最短时间小的组件会优先响应,会忽略priorityGesture设置。比如当父组件Column和子组件Text同时绑定TapGesture手势时,父组件以带优先级手势priorityGesture的形式进行绑定时,优先响应父组件绑定的TapGesture。示例代码如下所示:

**Demo0701.ets 示例**
```typescript
@Entry
@Component
struct Demo0701 {
build() {
Column() {
Text('绑定手势事件-演示').fontSize(35)
// 通过gesture绑定TapGesture手势
.gesture(
TapGesture()
.onAction(() => {
console.info('文本组件的手势-触发中');
}))
}.width("100%").margin(10)
// 设置为priorityGesture时
// 点击文本区域会忽略Text组件的TapGesture手势事件
// 优先响应父组件Column的TapGesture手势事件
.priorityGesture(
TapGesture()
.onAction(() => {
console.info('容器的手势-触发中');
}),
// 忽略内部,仅响应当前组件的手势事件
GestureMask.IgnoreInternal
)
}
}
```
效果如下图所示:

![](https://files.mdnice.com/user/57732/81e74433-629e-46ca-bbe4-2ad5533459be.png)


3. parallelGesture(并行手势绑定方法),是并行的手势绑定方法,可以在父子组件上绑定可以同时响应的相同手势。
```
.parallelGesture(gesture: GestureType, mask?: GestureMask)
```
在默认情况下,手势事件为非冒泡事件,当父子组件绑定相同的手势时,父子组件绑定的手势事件会发生竞争,最多只有一个组件的手势事件能够获得响应。而当父组件绑定了并行手势parallelGesture时,父子组件相同的手势事件都可以触发,实现类似冒泡效果。

## 二、单一手势
1. 点击手势(TapGesture),点击手势支持单次点击和多次点击,拥有两个可选参数:count:声明该点击手势识别的连续点击次数。默认值为1,若设置小于1的非法值会被转化为默认值。如果配置多次点击,上一次抬起和下一次按下的超时时间为300毫秒。fingers:用于声明触发点击的手指数量,最小值为1,最大值为10,默认值为1。当配置多指时,若第一根手指按下300毫秒内未有足够的手指数按下则手势识别失败。方法格式如下所示:
```
TapGesture(value?:{count?:number, fingers?:number})
```
2. 长按手势(LongPressGesture),长按手势用于触发长按手势事件,拥有三个可选参数:fingers:用于声明触发长按手势所需要的最少手指数量,最小值为1,最大值为10,默认值为1。repeat:用于声明是否连续触发事件回调,默认值为false。duration:用于声明触发长按所需的最短时间,单位为毫秒,默认值为500。方法格式如下所示:
```
LongPressGesture(value?:{fingers?:number, repeat?:boolean, duration?:number})
```
以在Text组件上绑定可以重复触发的长按手势为例,演示长按手势,代码如下所示:

**Demo0702.ets 示例**
```typescript
@Entry
@Component
struct Demo0702 {
//记录 手势的触发次数
@State count: number = 0;
build() {
Column() {
Text('长按手势-触发次数=' + this.count).fontSize(28)
.gesture(
// 绑定可以重复触发的LongPressGesture
LongPressGesture({ repeat: true })
.onAction((event: GestureEvent | undefined) => {
if (event) {
// 如果是可重复的长按手势,则count值一直递增
if (event.repeat) {
this.count++;
console.log("长按手势,触发次数=",this.count)
}
}
})
.onActionEnd(() => {
// 当长按手势结束的时候将count重置为0
console.log("长按手势,触发结束!")
})
).width("90%")
}
.padding(10)
.border({ width: 3 })
.width("100%")
}
}
```

3. 拖动手势(PanGesture),拖动手势用于触发拖动手势事件,滑动达到最小滑动距离(默认值为5vp)时拖动手势识别成功,拥有三个可选参数:fingers:用于声明触发拖动手势所需要的最少手指数量,最小值为1,最大值为10,默认值为1。direction:用于声明触发拖动的手势方向,此枚举值支持逻辑与(&)和逻辑或(|)运算。默认值为Pandirection.All。distance:用于声明触发拖动的最小拖动识别距离,单位为vp,默认值为5。方法格式如下所示:
```
PanGesture(value?:{ fingers?:number, direction?:PanDirection, distance?:number})
```
以在Text组件上绑定拖动手势为例,可以通过在拖动手势的回调函数中修改组件的布局位置信息来实现组件的拖动,实现代码如下所示:

**Demo0703.ets 示例**
```typescript
@Entry
@Component
struct Demo0703 {
// 记录当前x轴偏移量
@State offsetX: number = 0;
// 记录当前y轴偏移量
@State offsetY: number = 0;
// 记录上一次x轴偏移量
@State positionX: number = 0;
// 记录上一次y轴偏移量
@State positionY: number = 0;
build() {
Column() {
Text('拖动手势,坐标:\nX: ' + this.offsetX + '\n' + 'Y: ' + this.offsetY)
.height(100).padding(10)
.fontColor(Color.White).backgroundColor(Color.Red)
.border({ width: 3 })// 在组件上绑定布局位置信息
.translate({ x: this.offsetX, y: this.offsetY, z: 0 })
.gesture(// 绑定拖动手势
PanGesture()
.onActionStart((event: GestureEvent | undefined) => {
console.info('开始拖动手势');
})// 当触发拖动手势时,根据回调函数修改组件的布局位置信息
.onActionUpdate((event: GestureEvent | undefined) => {
if (event) {
// 当前位置累加当前事件记录的偏移量得到横轴总偏移量
this.offsetX = this.positionX + event.offsetX;
// 当前位置累加当前事件记录的偏移量得到纵轴总偏移量
this.offsetY = this.positionY + event.offsetY;
}
}).onActionEnd(() => {
// 当手势结束的时候记录当前偏移量
this.positionX = this.offsetX;
// 当手势结束的时候记录当前偏移量
this.positionY = this.offsetY;
})
)
}.width("100%")
}
}
```
效果如下所示:

![](https://files.mdnice.com/user/57732/38420b64-ba58-42d9-9c98-b391b0fa754e.png)

大部分可滑动组件,如List、Grid、Scroll、Tab等组件是通过PanGesture实现滑动,在组件内部的子组件绑定拖动手势(PanGesture)或者滑动手势(SwipeGesture)会导致手势竞争。

当在子组件绑定PanGesture时,在子组件区域进行滑动仅触发子组件的PanGesture。如果需要父组件响应,需要通过修改手势绑定方法或者子组件向父组件传递消息进行实现,或者通过修改父子组件的PanGesture参数distance使得拖动更灵敏。当子组件绑定SwipeGesture时,由于PanGesture和SwipeGesture触发条件不同,需要修改PanGesture和SwipeGesture的参数以达到所需效果。不合理的阈值设置会导致滑动不跟手(响应时延慢)的问题。
4.捏合手势(PinchGesture),捏合手势用于触发捏合手势事件,拥有两个可选参数:fingers:用于声明触发捏合手势所需要的最少手指数量,最小值为2,最大值为5,默认值为2。distance:用于声明触发捏合手势的最小距离,单位为vp,默认值为5,方法格式如下所示:
```
PinchGesture(value?:{fingers?:number, distance?:number})
```
我们可以在Column组件上绑定三指捏合手势,可以通过在捏合手势的函数回调中获取缩放比例,实现对组件的缩小或放大,代码如下所示:

**Demo0704.ets 示例**
```
@Entry
@Component
struct Index {
 @State scaleValue: number = 1;
 @State pinchValue: number = 1;
 @State pinchX: number = 0;
 @State pinchY: number = 0;
 build() {  
   Column() {    
     Column() {  
       Text('PinchGesture scale:\n' + this.scaleValue)    
       Text('PinchGesture center:\n(' + this.pinchX + ',' + this.pinchY + ')')  
    }  
    .height(200)  
      .width(300)    
      .border({ width: 3 })  
      .margin({ top: 100 })    
     // 在组件上绑定缩放比例,可以通过修改缩放比例来实现组件的缩小或者放大
      .scale({ x: this.scaleValue, y: this.scaleValue, z: 1 })    
      .gesture(      
       // 在组件上绑定三指触发的捏合手势    
       PinchGesture({ fingers: 3 })        
      .onActionStart((event: GestureEvent|undefined) => {  
         console.info('Pinch start');      
      })        
       // 当捏合手势触发时,可以通过回调函数获取缩放比例,从而修改组件的缩放比例    
      .onActionUpdate((event: GestureEvent|undefined) => {      
         if(event){        
           this.scaleValue = this.pinchValue * event.scale;  
           this.pinchX = event.pinchCenterX;    
           this.pinchY = event.pinchCenterY;  
        }      
      })  
      .onActionEnd(() => {  
         this.pinchValue = this.scaleValue;      
         console.info('Pinch end');    
      })
    )
  }
}
}
```
5. 旋转手势(RotationGesture),旋转手势用于触发旋转手势事件,拥有两个可选参数:fingers:用于声明触发旋转手势所需要的最少手指数量,最小值为2,最大值为5,默认值为2。angle:用于声明触发旋转手势的最小改变度数,单位为deg,默认值为1。方法格式如下所示:
```
RotationGesture(value?:{fingers?:number, angle?:number})
```
我们可以在Text组件上绑定旋转手势实现组件的旋转为例,可以通过在旋转手势的回调函数中获取旋转角度,从而实现组件的旋转,代码示例如下图所示:

**Demo0705.ets 示例**
```typescript
@Entry
@Component
struct Demo0705 {
//记录组件需要旋转的角度
@State angle: number = 0;
//记录旋转手势的角度值
@State rotateValue: number = 0;
build() {
Column() {
Text('旋转手势演示-旋转角度-' + this.angle).
fontSize(20).width("80%").margin(20).
borderWidth(1).padding(10)
// 在组件上绑定旋转布局,可以通过修改旋转角度来实现组件的旋转
.rotate({ angle: this.angle })
.gesture(
RotationGesture()
.onActionStart((event: GestureEvent|undefined) => {
console.info('开始旋转');
})
// 当旋转手势生效时,通过旋转手势的回调函数获取旋转角度,从而修改组件的旋转角度
.onActionUpdate((event: GestureEvent|undefined) => {
if(event){
this.angle = this.rotateValue + event.angle;
}
console.info('结束旋转');
})
// 当旋转结束抬手时,固定组件在旋转结束时的角度
.onActionEnd(() => {
this.rotateValue = this.angle;
console.info('获取旋转角度');
})
.onActionCancel(() => {
console.info('旋转取消');
})
)
}.width("100%")
}
}
```
效果如下图所示:

![](https://files.mdnice.com/user/57732/891d5390-c6a4-4249-81c9-23f874d4e21d.png)


6. 滑动手势(SwipeGesture),滑动手势用于触发滑动事件,当滑动速度大于100vp/s时可以识别成功,拥有三个可选参数:fingers:用于声明触发滑动手势所需要的最少手指数量,最小值为1,最大值为10,默认值为1。direction:用于声明触发滑动手势的方向,此枚举值支持逻辑与(&)和逻辑或(|)运算。默认值为SwipeDirection.All。speed:用于声明触发滑动的最小滑动识别速度,单位为vp/s,默认值为100。方法格式如下所示:

```
SwipeGesture(value?:{fingers?:number, direction?:SwipeDirection, speed?:number})
```
我们可以在Column组件上绑定滑动手势实现组件的旋转为例,代码如下所示:

**Demo0706.ets 示例**
```typescript
@Entry
@Component
struct Demo0706 {
//记录 旋转角度
@State rotateAngle: number = 0;
// 记录 滑动速度
@State speed: number = 1;
build() {
Column() {
Column() {
Text("滑动手势的速度:" + this.speed)
Text("滑动手势的角度:" + this.rotateAngle)
}
.border({ width: 3 })
.width("70%").padding(10).margin(20)
// 在Column组件上绑定旋转,通过滑动手势的滑动速度和角度修改旋转的角度
.rotate({ angle: this.rotateAngle })
.gesture(
// 绑定滑动手势且限制仅在竖直方向滑动时触发
SwipeGesture({ direction: SwipeDirection.Vertical })
// 当滑动手势触发时,获取滑动的速度和角度,实现对组件的布局参数的修改
.onAction((event: GestureEvent|undefined) => {
if(event){
this.speed = event.speed;
this.rotateAngle = event.angle;
}
})
)
}.width("100%")
}
}
```
效果如下所示:

![](https://files.mdnice.com/user/57732/6d001511-3ea0-4bdf-98b7-61032c52b477.png)

当SwipeGesture和PanGesture同时绑定时,若二者是以默认方式或者互斥方式进行绑定时,会发生竞争。SwipeGesture的触发条件为滑动速度达到100vp/s,PanGesture的触发条件为滑动距离达到5vp,先达到触发条件的手势触发。可以通过修改SwipeGesture和PanGesture的参数以达到不同的效果。

## 三、组合手势
组合手势由多种单一手势组合而成,通过在GestureGroup中使用不同的GestureMode来声明该组合手势的类型。方法主要包括2个参数分别为:mode:为GestureMode枚举类。用于声明该组合手势的类型,取值顺序识别、并行识别和互斥识别三种类型。gesture:由多个手势组合而成的数组。用于声明组合成该组合手势的各个手势。方法具体格式如下所示:
```
GestureGroup(mode:GestureMode, gesture:GestureType[])
```
1. 顺序识别

顺序识别组合手势对应的GestureMode为Sequence。顺序识别组合手势将按照手势的注册顺序识别手势,直到所有的手势识别成功。当顺序识别组合手势中有一个手势识别失败时,后续手势识别均失败。顺序识别手势组仅有最后一个手势可以响应onActionEnd。
以一个由长按手势和拖动手势组合而成的连续手势为例,在一个Column组件上绑定了translate属性,通过修改该属性可以设置组件的位置移动。然后在该组件上绑定LongPressGesture和PanGesture组合而成的Sequence组合手势。当触发LongPressGesture时,更新显示的数字。当长按后进行拖动时,根据拖动手势的回调函数,实现组件的拖动。实现代码示例如下所示:

**Demo0707.ets 示例**
```typescript
@Entry
@Component
struct Demo0707 {
//x坐标 偏移量
@State offsetX: number = 0;
//y坐标 偏移量
@State offsetY: number = 0;
//次数
@State count: number = 0;
//当前点的x坐标
@State positionX: number = 0;
//当前点的y坐标
@State positionY: number = 0;
//边框样式
@State borderStyles: BorderStyle = BorderStyle.Dotted

build() {
Column() {
Text('顺序手势识别\n' + '长按行为:' + this.count +
'\n拖拽手势:\nX: ' + this.offsetX + '\n' + 'Y: ' +
this.offsetY).fontSize(20).width("80%")
.margin(10).padding(10).borderWidth(2)
}
// 绑定translate属性可以实现组件的位置移动
.translate({ x: this.offsetX, y: this.offsetY, z: 0 })
.width("100%")
//以下组合手势为顺序识别,当长按手势事件未正常触发时不会触发拖动手势事件
.gesture(
// 声明该组合手势的类型为Sequence类型
GestureGroup(GestureMode.Sequence,
// 该组合手势第一个触发的手势为长按手势,且长按手势可多次响应  
LongPressGesture({ repeat: true })
// 当长按手势识别成功,增加Text组件上显示的count次数  
.onAction((event: GestureEvent|undefined) => {
if(event){
if (event.repeat) {
this.count++;
}
}
console.info('长按手势开始');
})
.onActionEnd(() => {
console.info('长按手势结束');
}),
// 当长按之后进行拖动,PanGesture手势被触发
PanGesture()
.onActionStart(() => {
this.borderStyles = BorderStyle.Dashed;
console.info('开始拖动');
})
//当该手势被触发时根据回调获得拖动的距离修改该组件的位移距离从而实现组件的移动        
.onActionUpdate((event: GestureEvent|undefined) => {
if(event){
this.offsetX = (this.positionX + event.offsetX);
this.offsetY = this.positionY + event.offsetY;
}
console.info('拖动已更新');
})
.onActionEnd(() => {
this.positionX = this.offsetX;
this.positionY = this.offsetY;
this.borderStyles = BorderStyle.Solid;
})
).onCancel(() => {
console.log("顺序手势取消")
})
)
}
}
```
效果如下图所示:

![](https://files.mdnice.com/user/57732/4e3df965-da73-47d4-834d-4c2b4563d64d.png)

 

拖拽事件是一种典型的顺序识别组合手势事件,由长按手势事件和滑动手势事件组合而成。只有先长按达到长按手势事件预设置的时间后进行滑动才会触发拖拽事件。如果长按事件未达到或者长按后未进行滑动,拖拽事件均识别失败。

2. 并行识别
并行识别组合手势对应的GestureMode为Parallel。并行识别组合手势中注册的手势将同时进行识别,直到所有手势识别结束。并行识别手势组合中的手势进行识别时互不影响。
以在一个Column组件上绑定点击手势和双击手势组成的并行识别手势为例,由于单击手势和双击手势是并行识别,因此两个手势可以同时进行识别,二者互不干涉。实现代码如下所示:

**Demo0708.ets 示例**
```typescript
@Entry
@Component
struct Demo0708 {
//单击手势 次数
@State count1: number = 0;
//双击手势 次数
@State count2: number = 0;
build() {
Column() {
Text('并行识别手势\n' + '单击手势次数:' + this.count1 +
'\n双击手势次数:' + this.count2 )
.fontSize(20).padding(10).width("90%").borderWidth(2)
}.width('100%')
// 以下组合手势为并行并别,单击手势识别成功后
// 若在规定时间内再次点击,双击手势也会识别成功
.gesture(
GestureGroup(GestureMode.Parallel,
TapGesture({ count: 1 }).onAction(() => {this.count1++;}),
TapGesture({ count: 2 }).onAction(() => {this.count2++;})
)
)
}
}
```
效果如下图所示:

![](https://files.mdnice.com/user/57732/e3f84eae-516a-4965-9185-0513dab323b6.png)

![](https://files.mdnice.com/user/57732/8189ca32-5145-49a0-a1ab-32b658dd51de.png)


当由单击手势和双击手势组成一个并行识别组合手势后,在区域内进行点击时,单击手势和双击手势将同时进行识别。当只有单次点击时,单击手势识别成功,双击手势识别失败。

当有两次点击时,若两次点击相距时间在规定时间内(默认规定时间为300毫秒),触发两次单击事件和一次双击事件。当有两次点击时,若两次点击相距时间超出规定时间,触发两次单击事件不触发双击事件。

3. 互斥识别

互斥识别组合手势对应的GestureMode为Exclusive。互斥识别组合手势中注册的手势将同时进行识别,若有一个手势识别成功,则结束手势识别,其他所有手势识别失败。

以在一个Column组件上绑定单击手势和双击手势组合而成的互斥识别组合手势为例。若先绑定单击手势后绑定双击手势,由于单击手势只需要一次点击即可触发而双击手势需要两次,每次的点击事件均被单击手势消费而不能积累成双击手势,所以双击手势无法触发。若先绑定双击手势后绑定单击手势,则触发双击手势不触发单击手势。代码实现如下所示:

**Demo0709.ets示例**
```typescript
@Entry
@Component
struct Index {
//记录 单击手势 的次数
@State count1: number = 0;
//记录 双击手势 的次数
@State count2: number = 0;
build() {
Column() {
Text('互斥识别手势\n' + '单击手势识别次数:' + this.count1 +
'\n双击手势识别次数:' + this.count2 + '\n')
.fontSize(20).borderWidth(2).padding(10).width("90%")
}
.width('100%')
//组合手势为互斥并别,单击手势识别成功后,双击手势会识别失败
.gesture(
GestureGroup(
GestureMode.Exclusive,
TapGesture({ count: 1 }).onAction(() => { this.count1++;}),
TapGesture({ count: 2 }).onAction(() => { this.count2++;})
)
)
}
}
```
效果如下图所示:

![](https://files.mdnice.com/user/57732/b4bd5529-a693-48ca-b490-8cbc66d2ce5b.png)

![](https://files.mdnice.com/user/57732/8ccbe7e4-bde3-4e29-a566-6a97fa3e38a9.png)


当由单击手势和双击手势组成一个互斥识别组合手势后,在区域内进行点击时,单击手势和双击手势将同时进行识别。当只有单次点击时,单击手势识别成功,双击手势识别失败。

当有两次点击时,手势响应取决于绑定手势的顺序。若先绑定单击手势后绑定双击手势,单击手势在第一次点击时即宣告识别成功,此时双击手势已经失败。即使在规定时间内进行了第二次点击,双击手势事件也不会进行响应,此时会触发单击手势事件的第二次识别成功。若先绑定双击手势后绑定单击手势,则会响应双击手势不响应单击手势。

## 四、多层级手势事件
多层级手势事件指父子组件嵌套时,父子组件均绑定了手势或事件。在该场景下,手势或者事件的响应受到多个因素的影响,相互之间发生传递和竞争,容易出现预期外的响应。

1. 触摸事件

触摸事件(onTouch事件)是所有手势组成的基础,有Down,Move,Up,Cancel四种。手势均由触摸事件组成,例如,点击为Down+Up,滑动为Down+一系列Move+Up。触摸事件具有最特殊性:
- 监听了onTouch事件的组件。若在手指落下时被触摸则均会收到onTouch事件的回调,被触摸受到触摸热区和触摸控制影响。
- onTouch事件的回调是闭环的。若一个组件收到了手指Id为0的Down事件,后续也会收到手指Id为0的Move事件和Up事件。
- onTouch事件的回调是一致的。若一个组件收到了手指Id为0的Down事件未收到手指Id为1的Down事件,则后续只会收到手指Id为0的touch事件,不会收到手指Id为1的后续touch事件。

对于一般的容器组件(例如:Column),父子组件之间onTouch事件能够同时触发,兄弟组件之间onTouch事件根据布局进行触发。
```
ComponentA() {  
 ComponentB().onTouch(() => {})  
 ComponentC().onTouch(() => {})
}.onTouch(() => {})
```
组件B和组件C作为组件A的子组件,当触摸到组件B或者组件C时,组件A也会被触摸到。onTouch事件允许多个组件同时触发。因此,当触摸组件B时,会触发组件A和组件B的onTouch回调,不会触发组件C的onTouch回调。

当触摸组件C时,会触发组件A和组件C的onTouch回调,不触发组件B的回调。特殊的容器组件,如Stack等组件,由于子组件之间存在着堆叠关系,子组件的布局也互相存在遮盖关系。所以,父子组件之间onTouch事件能够同时触发,兄弟组件之间onTouch事件会存在遮盖关系。

2. 手势与事件

除了触摸事件(onTouch事件)外的所有手势与事件,均是通过基础手势或者组合手势实现的。比如拖拽事件是由长按手势和滑动手势组成的一个顺序手势。在未显式声明的情况下,同一时间,一根手指对应的手势组中只会有一个手势获得成功从而触发所设置的回调。因此,除非显式声明允许多个手势同时成功,同一时间只会有一个手势响应。响应优先级遵循以下条件:
- 当父子组件均绑定同一类手势时,子组件优先于父组件触发。
- 当一个组件绑定多个手势时,先达到手势触发条件的手势优先触发。
```
ComponentA() {  
 ComponentB()
  .gesture(TapGesture({count: 1}))
}.gesture(TapGesture({count: 1}))    
```
当父组件和子组件均绑定点击手势时,子组件的优先级高于父组件。因此,当在B组件上进行点击时,组件B所绑定的TapGesture的回调会被触发,而组件A所绑定的TapGesture的回调不会被触发。
```
ComponentA().gesture(  
 GestureGroup(  
   GestureMode.Exclusive,  
   TapGesture({count: 1}),    
   PanGesture({distance: 5})
))
```
当组件A上绑定了由点击和滑动手势组成的互斥手势组时,先达到手势触发条件的手势触发对应的回调。若使用者做了一次点击操作,则响应点击对应的回调。若使用者进行了一次滑动操作并且滑动距离达到了阈值,则响应滑动对应的回调。

可以通过设置属性,控制默认的多层级手势事件竞争流程,更好的实现手势事件。目前,responseRegion属性和hitTestBehavior属性可以控制Touch事件的分发,从而可以影响到onTouch事件和手势的响应。而绑定手势方法属性可以控制手势的竞争从而影响手势的响应,但不能影响到onTouch事件。

3. responseRegion对手势和事件的控制

responseRegion属性可以实现组件的响应区域范围的变化。响应区域范围可以超出或者小于组件的布局范围。
```
ComponentA() {  
 ComponentB()
  .onTouch(() => {})
  .gesture(TapGesture({count: 1}))  
  .responseRegion({Rect1, Rect2, Rect3})}.onTouch(() => {})
  .gesture(TapGesture({count: 1}))
  .responseRegion({Rect4})
```
当组件A绑定了.responseRegion({Rect4})的属性后,所有落在Rect4区域范围的触摸事件和手势可被组件A对应的回调响应。

当组件B绑定了.responseRegion({Rect1, Rect2, Rect3})的属性后,所有落在Rect1,Rect2和Rect3区域范围的触摸事件和手势可被组件B对应的回调响应。

当绑定了responseRegion后,手势与事件的响应区域范围将以所绑定的区域范围为准,而不是以布局区域为准,可能出现布局相关区域不响应手势与事件的情况。此外,responseRegion属性支持由多个Rect组成的数组作为入参,以支持更多开发需求。

4. hitTestBehavior对手势和事件的控制

hitTestBehavior属性可以实现在复杂的多层级场景下,一些组件能够响应手势和事件,而一些组件不能响应手势和事件。
```
ComponentA() {  
 ComponentB()  
  .onTouch(() => {})
  .gesture(TapGesture({count: 1}))
 
   ComponentC() {    
     ComponentD()  
      .onTouch(() => {})  
      .gesture(TapGesture({count: 1}))  
  }
.onTouch(() => {})  
  .gesture(TapGesture({count: 1}))  
  .hitTestBehavior(HitTestMode.Block)}
.onTouch(() => {})
  .gesture(TapGesture({count: 1}))
```
HitTestMode.Block自身会响应触摸测试,阻塞子节点和兄弟节点的触摸测试,从而导致子节点和兄弟节点的onTouch事件和手势均无法触发。

当组件C未设置hitTestBehavior时,点击组件D区域,组件A、组件C和组件D的onTouch事件会触发,组件D的点击手势会触发。

当组件C设置了hitTestBehavior为HitTestMode.Block时,点击组件D区域,组件A和组件C的onTouch事件会触发,组件D的onTouch事件未触发。同时,由于组件D的点击手势因为被阻塞而无法触发,组件C的点击手势会触发。
```
Stack A() {  
 ComponentB()  
  .onTouch(() => {})
  .gesture(TapGesture({count: 1}))
 
   ComponentC()
    .onTouch(() => {})
    .gesture(TapGesture({count: 1}))  
    .hitTestBehavior(HitTestMode.Transparent)
}.onTouch(() => {})
.gesture(TapGesture({count: 1}))
```
HitTestMode.Transparent自身响应触摸测试,不会阻塞兄弟节点的触摸测试。当组件C未设置hitTestBehavior时,点击组件B和组件C的重叠区域时,Stack A和组件C的onTouch事件会触发,组件C的点击事件会触发,组件B的onTouch事件和点击手势均不触发。

而当组件C设置hitTestBehavior为HitTestMode.Transparent时,点击组件B和组件C的重叠区域,组件A和组件C不受到影响与之前一致,组件A和组件C的onTouch事件会触发,组件C的点击手势会触发。而组件B因为组件C设置了HitTestMode.Transparent,组件B也收到了Touch事件,从而组件B的onTouch事件和点击手势触发。
```
ComponentA() {  
 ComponentB()  
  .onTouch(() => {})  
  .gesture(TapGesture({count: 1}))
}.onTouch(() => {})
.gesture(TapGesture({count: 1}))
.hitTestBehavior(HitTestMode.None)
```
HitTestMode.None自身不响应触摸测试,不会阻塞子节点和兄弟节点的触摸控制。
当组件A未设置hitTestBehavior时,点击组件B区域时,组件A和组件B的onTouch事件均会触发,组件B的点击手势会触发。

当组件A设置hitTestBehavior为HitTestMode.None时,点击组件B区域时,组件B的onTouch事件触发,而组件A的onTouch事件无法触发,组件B的点击手势触发。
针对简单的场景,建议在单个组件上绑定hitTestBehavior。
针对复杂场景,建议在多个组件上绑定不同的hitTestBehavior来控制Touch事件的分发。

5. 绑定手势方法对手势的控制

设置绑定手势的方法可以实现在多层级场景下,当父组件与子组件绑定了相同的手势时,设置不同的绑定手势方法有不同的响应优先级。当父组件使用.gesture绑定手势,父子组件所绑定手势类型相同时,子组件优先于父组件响应。
```
ComponentA() {  
 ComponentB()  
  .gesture(TapGesture({count: 1}))
}.gesture(TapGesture({count: 1}))
```
当父子组件均正常绑定点击手势时,子组件优先于父组件响应。此时,单击组件B区域范围,组件B的点击手势会触发,组件A的点击手势不会触发。如果以带优先级的方式绑定手势,则可使得父组件所绑定手势的响应优先级高于子组件。
```
ComponentA() {
 ComponentB()
  .gesture(TapGesture({count: 1}))
}.priorityGesture(TapGesture({count: 1}))
```
当父组件以.priorityGesture的形式绑定手势时,父组件所绑定的手势优先级高于子组件。此时,单击组件B区域范围,组件A的点击手势会触发,组件B的点击手势不会触发。如果需要父子组件所绑定的手势不发生冲突,均可响应,则可以使用并行的方式在父组件绑定手势。
```
ComponentA() {
 ComponentB()  
  .gesture(TapGesture({count: 1}))
}.parallelGesture(TapGesture({count: 1}))
```
当父组件以.parallelGesture的形式绑定手势时,父组件和子组件所绑定的手势均可触发。此时,单击组件B区域范围,组件A和组件B的点击手势均会触发。

## 小结
本文详细介绍了Harmony OS中的交互事件处理机制,涵盖了触屏事件、键鼠事件、焦点事件和拖拽事件等多种类型。首先,对Harmony OS中的事件分类进行了详细阐述,包括触屏事件、键鼠事件、焦点事件和拖拽事件,并解释了事件分发机制和手势事件的构成。接着,深入探讨了事件分发的具体过程,包括触摸测试和事件响应链的收集,以及如何通过触摸测试控制和自定义事件拦截来优化事件处理。

在触屏事件部分,详细讲解了点击事件、触摸事件等的触发条件和回调函数的使用方法,并通过示例代码展示了如何在实际应用中实现这些事件的处理。键鼠事件部分则介绍了鼠标事件和键盘事件的分类及其触发机制,包括鼠标悬浮、点击等事件的处理方法,以及如何通过键盘事件实现快捷键功能。

焦点事件的介绍包括焦点的基本概念、焦点链的形成和走焦规范,以及如何通过监听获焦和失焦事件来实现焦点的动态管理。此外,还介绍了如何设置组件的可获焦属性和焦点样式,以及如何使用FocusController实现主动获焦和失焦。

拖拽事件部分则详细介绍了拖拽操作的基本概念、触发方式和回调事件的使用,包括手势拖拽和鼠标拖拽的不同实现方式,以及如何通过设置拖拽背板图和使用UDMF实现数据的传递。

最后,手势事件的讲解涵盖了单一手势和组合手势的分类及其绑定方法,包括点击手势、长按手势、拖动手势、捏合手势、旋转手势和滑动手势等的使用场景和实现方式,并通过示例代码展示了如何在实际应用中实现复杂的手势交互。

通过本文的学习,读者可以全面掌握Harmony OS中各种交互事件的处理方法,提升应用的交互体验和用户满意度。

相关文章:

HarmonyOS 5.1手势事件详解

大家好,我是 V 哥。手势事件由绑定手势方法和绑定的手势组成,绑定的手势可以分为单一手势和组合手势两种类型,根据手势的复杂程度进行区分。本文跟着 V 哥一起来探讨手势事件处理。 想要考取鸿蒙认证的小伙伴,请加入V 哥班级获取辅导: https://developer.huawei.com/consu…...

Vue3项目中集成AI对话功能的实战经验分享

ai-suspended-ball-chat组件使用体验摘要 本文分享了Vue3项目中使用ai-suspended-ball-chat组件集成AI对话功能的实践经验。该组件提供悬浮球和独立面板两种模式,支持流式响应、图片上传、语音交互等功能,显著提升了用户体验。通过实际案例展示了在客服系统和代码助手场景中的…...

gulimall出现服务间调用org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.choose 问题

java.lang.AbstractMethodError: org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.choose(Ljava/lang/String;Lorg/springframework/cloud/client/loadbalancer/Request;)Lorg/springframework/cloud/client/ServiceInstance;A调用B模块出现上面这个问题,…...

Java02课前问题列表

Java02课前问题列表1.方法相关问题 public class Main {static void changeStr(String x) {x = "xyz";}static void changeArr(String[] strs) {for (int i = 0; i < strs.length; i++) {strs[i] = strs[i]+""+i;}}public static void main(String[] ar…...

达梦数据库安装和使用

1、达梦数据库安装地址 https://eco.dameng.com/document/dm/zh-cn/start/install-dm-windows-prepare.html 2、 点击下载3、4、 现在版本只需要点击exe文件56 点击【下一步】如图所示7、 接受授权协议8 如果没有key文件可跳过 如果有点击浏览找到key文件系统自动校验9建议典型…...

CSP 赛前周记

初赛前 - 第一周(末) 这学期的第一周,据说是本学期第二长的假期,故开始摸摸。 Day1 - 周五 晚上回来开了把信奥大联赛,发现比你谷月赛还烂,IOI 赛制,风格跟 CSP 三不沾,每周有时间打打玩玩吧(结果被打爆了,只有 230pts)。总结Day2 - 周六 由于作业多得一批,白天在疯…...

Day16对数组的基本认识

数组的定义package array;public class ArrayDemo1 {//变量类型 变量名称 = 变量的值//数组类型 同上public static void main(String[] args) {int [] nums;//声明一个数组nums = new int [10];//创建一个数组int [] nums1 =new int [10];//两种写法都可,初学建议拆分避免…...

Ubuntu 界面变为 Mac

sudo apt install gnome-tweaks...

今日随笔

今天完成了社会实践调查作业...

Day16

数组的定义package array;public class ArrayDemo1 {//变量类型 变量名称 = 变量的值//数组类型 同上public static void main(String[] args) {int [] nums;//声明一个数组nums = new int [10];//创建一个数组int [] nums1 =new int [10];//两种写法都可,初学建议拆分避免…...

PVE9环境下飞牛OS安装vGPU驱动

1.安装过程# 切换root权限 sudo su -# 屏蔽nouveau echo "blacklist nouveau" >> /etc/modprobe.d/blacklist-nouveau.conf# 更新initramfs update-initramfs -u# 安装组件 apt update && apt install build-essential dkms linux-headers-generic lib…...

02020304 .NET Core核心基础组件04-配置系统、Json文件配置、选项方式读取、扁平化环境变量其它配置源

02020304 .NET Core核心基础组件04-配置系统、Json文件配置、选项方式读取、扁平化&环境变量&其它配置源 1. 配置系统入门(视频2-32)传统Web.config配置的缺点,之前DI讲过。 为了兼容,仍然可以使用web.config和ConfigurationManage类,但不推荐。 .NET中的配置系统…...

md格式

markdown # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题*斜体* **粗体** ***粗斜体*** ~~删除线~~ `print("hello world)` ==高亮== 一级标题 二级标题 三级标题 四级标题 斜体 粗体 粗斜体 删除线 print("hello world) 高亮>一层嵌套引用 >>二层嵌套…...

CSP-S模拟20

前言: 一场通过乱搞获得如下成绩的比赛。\(T1:\) 思路: 一场情况超级复杂(bushi)的大模拟(应该可以这么叫吧?)直接先这样这样,在那样那样,最后在叽里呱啦就好了。 嘿嘿,开个玩笑嘛。首先,我们知道一定至少从\(S\)跳到\(S\)到\(T\)的那条链上,这样我们可以就\(S\)跳…...

第7篇、Kafka Streams 与 Connect:企业级实时数据处理架构实践指南

Kafka Streams 与 Kafka Connect:企业级实时数据处理架构实践指南 技术背景与适用场景 在现代数据架构中,实时数据处理已成为企业数字化转型的核心能力。Apache Kafka作为分布式流处理平台,提供了两个关键组件:Kafka Streams:轻量级流处理库,支持有状态实时计算 Kafka Co…...

Day16编写一个计算机程序

package method; import java.util.Scanner; public class Demo6 {/**作业:*1 写四个方法,加减乘除*2 利用循环+switch进行用户交互*3 传递需要操作的两个数*4 输出结果*/public static void main(String[] args) {Scanner scanner = new Scanner(System.in);boolean sco…...

迷宫最短路径

2025.9.11 曹立 题目内容 给定一个迷官的地图,地图是一个二维矩阵,其中0表示通道,1表示墙壁,S表示起点,E表示终点。你需要从起点S出发,通过最路径到达终点E,返回最短路径的步数,如果无法到达终点,则返回-1,迷宫中会有虫洞,用数字2表示,成对出现,你走入虫洞可以穿越…...

千靶日记-0003

day-3 今天事情不多,继续打靶,这个靶机关键点不多 Hommie靶机复盘 https://t.bilibili.com/1111299672388927491?share_source=pc_native...

COMSOL 6.3 下载+安装教程+激活教程:一站式下载安装激活操作说明

COMSOL 6.3 作为主流多物理场仿真软件,是工程设计与科研的重要工具。不少用户在下载安装时会遇权限不足、许可证无效等问题。本教程围绕安全下载渠道、 step-by-step 安装步骤、常见问题解决展开,还附入门实操,助你高效完成安装,快速上手软件。目录一、先搞懂:COMSOL 6.3 …...

20231427-田泽航-Linux命令实践

1...

202207_BUGKU_二维码GIF

GIF分离,QRCODE,ZXING库Tags:GIF分离,QRCODE,ZXING库 0x00. 题目0x01. WP 01 分离GIF工具路径:https://pan.baidu.com/s/1GyH7kitkMYywGC9YJeQLJA?pwd=Zmxh#list/path=/CTF附件/Tools 工具名称:风二西_GIF图片分离工具.zip 02 使用脚本批量扫描 exp.py #将指定文件夹下的文件…...

20250910NOIP模拟赛

20250910NOIP模拟赛 A 题意: 有 \(n\) 个小球分为红、蓝两种颜色排成一排,现在你可以进行若干次操作,每次操作选择任意 \(R+B\) 个小球,使其有 \(R\) 个红球,\(B\) 个蓝球,把这些小球全部染为白色,且在该选择序列中,最左侧的球到最右侧的球之间不得存在已经被染为白色的…...

分治 NTT 一则

le0n 太强大!1...

U604938 你不准卡 O(n sqrt n log L) 其中 L log L = sqrt n

U604938 你不准卡 O(n sqrt n log L) 其中 L log L = sqrt n 如题目所言,这道题的出现就是为此,所以不要说什么 wyy。 首先是空间上卡掉了 \(n\sqrt n\) 空间的做法,然后因为值域限制卡掉了回滚莫队(也许只是我菜才不会写?)。总之再有什么我也没法了,就这样。 如果你要卡…...

20250906

20250906T1 推倒骨牌 多维护几个东西就能直接倍增了。不要开 long long。代码 #include <iostream> #include <algorithm> #include <string.h> #define lowbit(x) ((x) & (-(x))) using namespace std; int n, q; struct BIT {pair<int, int> bi…...

【2025最新推荐】AI大模型API中转站 | 国内直连ChatGPT/Claude/Gemini全系API接口服务

作为一名开发者,你是否曾为了使用ChatGPT、Claude等AI模型而苦恼?网络问题、支付困难、成本昂贵...这些痛点让很多国内开发者望而却步。今天给大家推荐简易API中转站,解决这些问题。 1.什么是API中转站? API中转站是专为国内开发者打造的AI模型API中转服务平台。简单来说,…...

在用灵魂去感受另一个灵魂的震颤

【用灵魂去感受另一个灵魂的震颤】...

html怎么写

html 1. 基本结构 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title></title> <bod…...

谁拿了谁的伞?

我:要去上课了,哎,不想去上课,我想在工位带着。算了,还是去吧。我的伞放在工位门口左边,光线很黑,拿错了雨伞,拿成了学长的雨伞去上课。一到教室,刚坐下,老师就开始说,不让坐最后一排啊,你们几个快做前面去,然后我就拿着我的伞坐到了前面。这课是真无聊,我准备上…...

NSSCTF-misc

签到 用emoji-aes解码,key为GAME ☀☺⏩☺⌨☂☃✅ 得到flag{10ve_4nd_Peace} GIF有点大GETwbStego4open 隐写 首先,wbStego4open会把插入数据中的每一个ASCII码转换为二进制形式 然后,把每一个二进制数字再替换为十六进制的20或09,20代表0,09代表1 最后,将这些被转换后的…...

百粉粉福

应机房某人要求,说要搞一个百粉粉福,决定先做一个 \(Q&A\) ,其它的请各位想想可以做什么别的粉福。 \(Q&A\) 可以直接回复这篇帖子或直接私信我,到时候会发到你谷主页,文章跟博客园,支持一下呗QwQ \(Q&A\)Q:瑞平我 @Misty_PostA:感觉是非常可爱的学弟,平时…...

lc1024-视频拼接

难度:中等(中期)题目描述给定一些区间和一个数字 time,找到能覆盖 [0, time] 的最少区间数示例 输入:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], time = 10 输出:3 解释:选 [0,2], [8,10], [1,9]输入:clips = [[0,1],[1,2]], time = 5 输出:-1 解释:找不到返回…...

多元统计分析1

多元统计分析1 大三开始学习多元统计分析,首先使一些预备知识,基本是就是一些高代,数分,概率论的有些难度的知识。我个人觉得这些知识还是有难度的,尤其是距离高代已经过去了一年时间了。 概率论这里就是一些随机变量的均值,方差的性质。由于涉及到了多变量的协方差矩阵一…...

OI界的梗

%%% 在C++“%”是取模的意思,简称模,是膜拜大佬的意思(%越多,语气越强) 蒟蒻 他本是一种可以吃的植物(就是魔芋),可是因为他谐音“巨弱”,所以,他被用作自嘲,但没人会说别人是蒟蒻的,这是一种基础礼仪 神犇(读ben(第一声))、巨佬 神犇是大牛的升级版 巨佬是大佬…...

202404_QQ_ZIP嵌套

ZIP,嵌套Tags:ZIP,嵌套 0x00. 题目 附件路径:https://pan.baidu.com/s/1GyH7kitkMYywGC9YJeQLJA?pwd=Zmxh#list/path=/CTF附件 附件名称:202404_QQ_ZIP嵌套.zip 0x01. WP打开txt文件发现文件头为504b0304 导入到010Editor另存为tmp.zip 打开tmp.zip发现里面是另一个txt 打开…...

无重复字符的最长子串-leetcode

题目描述给定一个字符串 s ,请你找出其中不含有重复字符的 最长 的长度。 示例 1: 输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。示例 2: 输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子…...

两个常见的 计数问题 trick

两个非常有用的计数 trick,虽然感觉比较典。 计数转 01 有一件事情:\(v=\sum_{i=1}^V [v \geq i]\),\(V\) 为值域。看着好像没有什么突破性的转变,但是当我们对很多东西进行统计的时候,如果对于 有多少个大于等于 某个值 \(v\) 是好做的话,那么我们就可以做这个转化:\(\…...

搜维尔科技:Xsens人形机器人拟人动作AI训练,提升机器人工作精度与效率

随着人工智能与机器人技术的深度融合,人形机器人正从实验室走向工业制造、医疗护理、公共服务等真实场景。然而,要让机器人真正"像人类一样工作",其动作的流畅性、精准度与环境适应性仍是技术突破的关键。Xsens动作捕捉系统通过创新的拟人化动作AI训练方案,为机器…...

文件轮转机制

文件轮转机制 基于文件的持久化队列(File-based Persistent Queue),利用 双文件切换(Double Buffering / File Rotation) 来保证批处理、高效写入、并发安全。 方法主要实现的机制双文件切换(Double Buffering / File Rotation) • 通过 inputFile(正在写的新数据) 和…...

202110_绿盟杯_隐藏的数据

ZIP,伪加密,密码爆破,DOCX文件Tags:ZIP,伪加密,密码爆破,DOCX文件 0x00. 题目 附件路径:https://pan.baidu.com/s/1GyH7kitkMYywGC9YJeQLJA?pwd=Zmxh#list/path=/CTF附件 附件名称:202110_绿盟杯_隐藏的数据.zip 0x01. WP 01 打开压缩包,发现有word文件和zip文件,得到flag1…...

【初赛】图 - Slayer

欧拉图 在图论中,欧拉路径是经过图中每条边恰好一次的路径,欧拉回路是经过图中每条边恰好一次的回路。 如果一个图中存在欧拉回路,则这个图被称为欧拉图;如果一个图中不存在欧拉回路但是存在欧拉路径,则这个图被称为半欧拉图。 对于连通图 G,以下三个性质是互相等价的: …...

线上课

反射反射就是在程序运行时 获取到类的信息(成员变量 成员方法 构造方法) 并操作对象的属性和方法 获取class对象就可以拿到类的信息 获取class对象Class.forName(全类名) 类名.class 对象.getClass字节码是唯一的 无论哪种方式获取 都是同一个class对象当通过反射拿到的构造…...

弹窗、抽屉、当前页和新开页,到底怎么选? - 智慧园区

弹窗、抽屉、当前页、新开页,看似只是交互容器的选择,实则关乎信息密度、操作路径与用户心智的精准匹配。本文从B端产品的真实场景出发,拆解四种容器的使用逻辑与适配原则,帮助产品经理构建更清晰的设计判断框架。在B端产品的设计实践中,你是否曾面临过以下的灵魂拷问?你…...

POJ 2566 Bound Found

题面描述: 美国航空航天局(该机构似乎正经历叛逆期:"我就要用英尺,才不用米呢!")接收并数字化了极可能来自地外文明的信号。每个信号包含两部分:一个由n个整数值组成的序列和一个非负整数t。虽然细节不便透露,但研究人员发现信号编码了两个整数值。这两个值可…...

搜维尔科技:Haption触觉力反馈系统,沉浸式远程呈现、数字孪生、混合现实和移动远程机器人

为您的项目提供技术和经验 自2001年以来,HAPTION力反馈设备已被用于广泛的研究应用。 您是否希望在您的项目中使用HAPTION力反馈设备 您是否正在寻找国内厂商来申请新项目 增强人机交互-远程操作,实现人机交互 图片 图片 通过最先进的社交、视觉、触觉、音频和嗅觉技术的出色…...

飞书免费企业邮箱推荐

1、品牌背书:成长技术最快的企业,稳定性拉满 对于中小企业而言,一款稳定、安全且免费的企业邮箱,不仅能降低运营成本,更能提升团队沟通效率。飞书作为字节跳动旗下的协同办公平台,其推出的免费企业邮箱服务,凭借 “零成本、强协同、高安全” 的特点,成为越来越多企业的…...

CF1140F Extending Set of Points

还是比较好想的一个题。 首先你这个 \((x, y)\) 看着就很像连边关系,这是很重要的一步,一般这种二元关系都可以想着上图。 然后你发现,所谓的扩展不过就是在能加上边的地方都加上边,就是一个连通块都连满了。 这个时候注意到原图是一个二分图,能加的边不过就是所有左部点向…...

Lark免费企业邮箱推荐

Lark是飞书的国际版 Lark(飞书)的免费企业邮箱是中小型团队高效协作的理想选择,尤其适合追求低成本、高集成度的企业。以下是基于最新功能和用户反馈的深度推荐:一、核心功能与优势免费版基础能力全面无广告纯净体验:不同于部分免费邮箱,Lark 免费版全程无广告干扰,专注…...

CMP 40HX在PVE9.0配置vGPU

在PVE9.0下CMP 40HX使用NVIDIA vGPU19.0显卡虚拟化拆分技术 本文参考文章:https://yangwenqing.com/archives/2729/最近看了很多vGPU的文章,心里面痒痒,就想搞一块矿卡来玩玩。在选择方面考虑了P106-100、CMP 30HX 、CMP 40HX,最终选则了CMP 40HX。 如果你需要玩vGPU,百元…...

耶日奈曼:置信区间与假设检验的奠基者

img { display: block; margin-left: auto; margin-right: auto } table { margin-left: auto; margin-right: auto } 在20世纪统计学的发展历程中,耶日奈曼(Jerzy Neyman, 1894–1981)无疑是一位具有里程碑意义的人物。他不仅在理论层面上为数理统计学奠定了严格的推断体系…...