HarmonyOS 5.1手勢事件詳解
大家好,我是 V 哥。
手勢事件由綁定手勢方法和綁定的手勢組成,綁定的手勢可以分為單一手勢和組合手勢兩種類型,根據手勢的復雜程度進行區分。本文跟著 V 哥一起來探討手勢事件處理。
想要考取鴻蒙認證的小伙伴,請加入V 哥班級獲取輔導:
https://developer.huawei.com/consumer/cn/training/classDetail/042cb1cc4d7d44ecbdbd902fd1275dcc?type=1
V哥寫的鴻蒙全系例圖書,助你從入門到一名成熟的開發者:

## 一、綁定手勢方法
通過給各個組件綁定不同的手勢事件,并設計事件的響應方式,當手勢識別成功時,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
)
}
}
```
效果如下圖所示:

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%")
}
}
```
效果如下所示:

大部分可滑動組件,如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%")
}
}
```
效果如下圖所示:

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%")
}
}
```
效果如下所示:

當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("順序手勢取消")
})
)
}
}
```
效果如下圖所示:

拖拽事件是一種典型的順序識別組合手勢事件,由長按手勢事件和滑動手勢事件組合而成。只有先長按達到長按手勢事件預設置的時間后進行滑動才會觸發拖拽事件。如果長按事件未達到或者長按后未進行滑動,拖拽事件均識別失敗。
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++;})
)
)
}
}
```
效果如下圖所示:


當由單擊手勢和雙擊手勢組成一個并行識別組合手勢后,在區域內進行點擊時,單擊手勢和雙擊手勢將同時進行識別。當只有單次點擊時,單擊手勢識別成功,雙擊手勢識別失敗。
當有兩次點擊時,若兩次點擊相距時間在規定時間內(默認規定時間為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++;})
)
)
}
}
```
效果如下圖所示:


當由單擊手勢和雙擊手勢組成一個互斥識別組合手勢后,在區域內進行點擊時,單擊手勢和雙擊手勢將同時進行識別。當只有單次點擊時,單擊手勢識別成功,雙擊手勢識別失敗。
當有兩次點擊時,手勢響應取決于綁定手勢的順序。若先綁定單擊手勢后綁定雙擊手勢,單擊手勢在第一次點擊時即宣告識別成功,此時雙擊手勢已經失敗。即使在規定時間內進行了第二次點擊,雙擊手勢事件也不會進行響應,此時會觸發單擊手勢事件的第二次識別成功。若先綁定雙擊手勢后綁定單擊手勢,則會響應雙擊手勢不響應單擊手勢。
## 四、多層級手勢事件
多層級手勢事件指父子組件嵌套時,父子組件均綁定了手勢或事件。在該場景下,手勢或者事件的響應受到多個因素的影響,相互之間發生傳遞和競爭,容易出現預期外的響應。
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中各種交互事件的處理方法,提升應用的交互體驗和用戶滿意度。
本文來自博客園,作者:威哥愛編程,轉載請注明原文鏈接:http://www.rzrgm.cn/finally-vince/p/19086947

浙公網安備 33010602011771號