接上篇
单一手势
捏合手势(PinchGesture)
PinchGesture(value?:{fingers?:number, distance?:number})
捏合手势用于触发捏合手势事件
参数
- fingers:用于声明触发捏合手势所需要的最少手指数量,最小值为2,最大值为5,默认值为2。
- distance:用于声明触发捏合手势的最小距离,单位为vp,默认值为5。
事件
示例
ts
@Entry
@Component
struct PinchGesturePage {
@State scaleValue: number = 1
@State pinchValue: number = 1
@State pinchX: number = 0
@State pinchY: number = 0
build() {
Column() {
Column() {
Text('倍数scale: ' + this.scaleValue)
}
.height(200)
.width(300)
.padding(20)
.border({ width: 3 })
.justifyContent(FlexAlign.Center)
.scale({ x: this.scaleValue, y: this.scaleValue, z: 1 })
//两指捏合触发该手势事件
.gesture(
PinchGesture({ fingers: 2 })
.onActionStart((event: GestureEvent) => {
console.info('Pinch start')
})
.onActionUpdate((event: GestureEvent) => {
if (event) {
this.scaleValue = this.pinchValue * event.scale
this.pinchX = event.pinchCenterX
this.pinchY = event.pinchCenterY
}
})
.onActionEnd((event: GestureEvent) => {
this.pinchValue = this.scaleValue
console.info('Pinch end')
})
)
}.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
效果
PinchGesture与PinchGesture两种手势结合
我们将PinchGesture和PanGesture两种手势结合,实现在一定的空间中放大后,滑动放大画面。(最近我在项目遇到需要实现对直播流画面放大并滑动)
代码
ts
@Entry
@Component
struct PinchGesturePage {
@State scaleValue: number = 1
@State pinchValue: number = 1
@State pinchX: number = 0
@State pinchY: number = 0
@State zWidth: number = 300
@State zHeight: number = 200
//边界问题
@State maxOffsetX: number = 0
@State minOffsetX: number = 0
@State maxOffsetY: number = 0
@State minOffsetY: number = 0
//滑动偏移量
@State offsetX: number = 0
@State offsetY: number = 0
@State positionY: number = 0
@State positionX: number = 0
/**
*触发边界检查
*/
checkBoundary = () => {
if (this.offsetX > this.maxOffsetX) {
this.offsetX = this.maxOffsetX
}
if (this.offsetX < this.minOffsetX) {
this.offsetX = this.minOffsetX
}
if (this.offsetY > this.maxOffsetY) {
this.offsetY = this.maxOffsetY
}
if (this.offsetY < this.minOffsetY) {
this.offsetY = this.minOffsetY
}
}
build() {
Column() {
Column() {
//这里需要自己选择一张图片
Image($r('app.media.Q'))
.height(this.zHeight)
.width(this.zWidth)
.objectFit(ImageFit.Fill)
.scale({ x: this.scaleValue, y: this.scaleValue, z: 1 })
.translate({ x: this.offsetX, y: this.offsetY, z: 0 })
.animation({ duration: 100, curve: Curve.Linear })//为了变化的过程更加流畅,我们选择增加一个动画效果
//增加手势扩大子组件的画面,两指捏合触发该手势事件
.gesture(
PinchGesture({ fingers: 2 })
.onActionStart((event: GestureEvent) => {
console.info('Pinch start')
})
.onActionUpdate((event: GestureEvent) => {
if (event) {
this.scaleValue = this.pinchValue * event.scale
//为了更好效果展示我们选择扩大
if (this.scaleValue <= 1) {
this.scaleValue = 1
}
this.pinchX = event.pinchCenterX
this.pinchY = event.pinchCenterY
}
})
.onActionEnd((event: GestureEvent) => {
this.pinchValue = this.scaleValue
//在滑动过程中,我们需要处理画面边界问题,使用我们需要存储最大和最小偏移量
this.maxOffsetX = Math.abs(Math.min(((1 - this.scaleValue) * this.zWidth) / 2, 0))
this.minOffsetX = -Math.abs(Math.max(((this.scaleValue - 1) * this.zWidth) / 2, 0))
this.maxOffsetY = Math.abs(Math.min(((1 - this.scaleValue) * this.zHeight) / 2, 0))
this.minOffsetY = -Math.abs(Math.max(((this.scaleValue - 1) * this.zHeight) / 2, 0))
//触发边界检查
this.checkBoundary()
console.info('Pinch end')
})
)
}
.height(200)
.width(300)
//clip属性防止子组件扩大之后画面超过父组件的范围
.clip(true)
.border({ width: 3 })
.gesture(
PanGesture({ fingers: 1 })
.onActionStart((event) => {
})
.onActionUpdate((event: GestureEvent) => {
if (event) {
this.offsetX = this.positionX + event.offsetX
this.offsetY = this.positionY + event.offsetY
this.checkBoundary()
}
})
.onActionEnd((event) => {
if (event) {
this.positionX = this.offsetX
this.positionY = this.offsetY
}
})
)
}.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
效果
旋转手势(RotationGesture)
RotationGesture(value?:{fingers?:number, angle?:number})
旋转手势用于触发旋转手势事件
参数
- fingers:用于声明触发旋转手势所需要的最少手指数量,最小值为2,最大值为5,默认值为2。
- angle:用于声明触发旋转手势的最小改变度数,单位为deg,默认值为1。
事件
属性
示例
以在Text组件上绑定旋转手势实现组件的旋转为例,可以通过在旋转手势的回调函数中获取旋转角度,从而实现组件的旋转:
ts
@Entry
@Component
struct RotationGesturePage {
@State angle: number = 0;
@State rotateValue: number = 0;
build() {
Column() {
Text('旋转角度' + this.angle)
.fontSize(28)// 在组件上绑定旋转布局,可以通过修改旋转角度来实现组件的旋转
.rotate({ angle: this.angle })
.gesture(
RotationGesture()
.onActionStart((event: GestureEvent | undefined) => {
console.info('RotationGesture is onActionStart');
})// 当旋转手势生效时,通过旋转手势的回调函数获取旋转角度,从而修改组件的旋转角度
.onActionUpdate((event: GestureEvent | undefined) => {
if (event) {
this.angle = this.rotateValue + event.angle;
}
console.info('RotationGesture is onActionEnd');
})// 当旋转结束抬手时,固定组件在旋转结束时的角度
.onActionEnd(() => {
this.rotateValue = this.angle;
console.info('RotationGesture is onActionEnd');
})
.onActionCancel(() => {
console.info('RotationGesture is onActionCancel');
})
)
.height(200)
.width(300)
.border({ width: 3 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
效果
滑动手势(SwipeGesture)
SwipeGesture(value?:{fingers?:number, direction?:SwipeDirection, speed?:number})
滑动手势用于触发滑动事件,当滑动速度大于100vp/s时可以识别成功。
注意:拖动手势(PanGesture)是滑动达到最小滑动距离(默认值为5vp)时拖动手势识别成功
冲突
当SwipeGesture和PanGesture同时绑定时,若二者是以默认方式或者互斥方式进行绑定时,会发生竞争。SwipeGesture的触发条件为滑动速度达到100vp/s,PanGesture的触发条件为滑动距离达到5vp,先达到触发条件的手势触发。可以通过修改SwipeGesture和PanGesture的参数以达到不同的效果。
参数
- fingers:用于声明触发滑动手势所需要的最少手指数量,最小值为1,最大值为10,默认值为1。
- direction:用于声明触发滑动手势的方向,此枚举值支持逻辑与(&)和逻辑或(|)运算。默认值为SwipeDirection.All。
- speed:用于声明触发滑动的最小滑动识别速度,单位为vp/s,默认值为100。
事件
示例
ts
@Entry
@Component
struct SwipeGesturePage {
@State rotateAngle: number = 0;
@State speed: number = 1;
build() {
Column() {
Column() {
Text("SwipeGesture speed\n" + this.speed)
Text("SwipeGesture angle\n" + this.rotateAngle)
}
.border({ width: 3 })
.width(300)
.height(200)
.margin(100)
// 在Column组件上绑定旋转,通过滑动手势的滑动速度和角度修改旋转的角度
.rotate({ angle: this.rotateAngle })
.animation({ duration: 100, curve: Curve.Linear })
.gesture(
// 绑定滑动手势且限制仅在竖直方向滑动时触发
SwipeGesture({ direction: SwipeDirection.Vertical })// 当滑动手势触发时,获取滑动的速度和角度,实现对组件的布局参数的修改
.onAction((event: GestureEvent | undefined) => {
if (event) {
this.speed = event.speed;
this.rotateAngle = event.angle;
}
})
)
}
}
}
效果
组合手势
当我们需要对同一个组件增加多种手势,我们需要使用组合手势。组合手势由多种单一手势组合而成,通过在GestureGroup中使用不同的GestureMode来声明该组合手势的类型,支持顺序识别、并行识别和互斥识别三种类型
GestureGroup(mode:GestureMode, gesture:GestureType[])
参数
- mode:为GestureMode枚举类。用于声明该组合手势的类型。
- gesture:由多个手势组合而成的数组。用于声明组合成该组合手势的各个手势。
顺序识别
顺序识别组合手势对应的GestureMode为Sequence。顺序识别组合手势将按照手势的注册顺序识别手势,直到所有的手势识别成功。当顺序识别组合手势中有一个手势识别失败时,后续手势识别均失败。顺序识别手势组仅有最后一个手势可以响应onActionEnd。
示例
以一个由长按手势和拖动手势组合而成的连续手势为例:
在一个Column组件上绑定了translate属性,通过修改该属性可以设置组件的位置移动。然后在该组件上绑定LongPressGesture和PanGesture组合而成的Sequence组合手势。当触发LongPressGesture时,更新显示的数字。当长按后进行拖动时,根据拖动手势的回调函数,实现组件的拖动。
ts
@Entry
@Component
struct SequenceRecognition {
@State offsetX: number = 0;
@State offsetY: number = 0;
@State count: number = 0;
@State positionX: number = 0;
@State positionY: number = 0;
@State borderStyles: BorderStyle = BorderStyle.Solid
build() {
Column() {
Text('顺序识别\n' + '长按触发次数:' + this.count + '\n拖拽触发偏移量:\nX: ' + this.offsetX +
'\n' + 'Y: ' + this.offsetY)
.fontSize(28)
}
.margin(10)
.borderWidth(1)
// 绑定translate属性可以实现组件的位置移动
.translate({ x: this.offsetX, y: this.offsetY, z: 0 })
.height(250)
.width(300)
//以下组合手势为顺序识别,当长按手势事件未正常触发时不会触发拖动手势事件
.gesture(
// 声明该组合手势的类型为Sequence类型
GestureGroup(GestureMode.Sequence,
// 该组合手势第一个触发的手势为长按手势,且长按手势可多次响应
LongPressGesture({ repeat: true })// 当长按手势识别成功,增加Text组件上显示的count次数
.onAction((event: GestureEvent | undefined) => {
if (event) {
if (event.repeat) {
this.count++;
}
}
console.info('LongPress onAction');
})
.onActionEnd(() => {
console.info('LongPress end');
}),
// 当长按之后进行拖动,PanGesture手势被触发
PanGesture()
.onActionStart(() => {
this.borderStyles = BorderStyle.Dashed;
console.info('pan start');
})// 当该手势被触发时,根据回调获得拖动的距离,修改该组件的位移距离从而实现组件的移动
.onActionUpdate((event: GestureEvent | undefined) => {
if (event) {
this.offsetX = (this.positionX + event.offsetX);
this.offsetY = this.positionY + event.offsetY;
}
console.info('pan update');
})
.onActionEnd(() => {
this.positionX = this.offsetX;
this.positionY = this.offsetY;
this.borderStyles = BorderStyle.Solid;
})
)
.onCancel(() => {
console.log("sequence gesture canceled")
})
)
}
}
效果
并行识别
并行识别组合手势对应的GestureMode为Parallel。并行识别组合手势中注册的手势将同时进行识别,直到所有手势识别结束。并行识别手势组合中的手势进行识别时互不影响。
示例
以在一个Column组件上绑定点击手势和双击手势组成的并行识别手势为例,由于单击手势和双击手势是并行识别,因此两个手势可以同时进行识别,二者互不干涉。
说明
- 当由单击手势和双击手势组成一个并行识别组合手势后,在区域内进行点击时,单击手势和双击手势将同时进行识别。
- 当只有单次点击时,单击手势识别成功,双击手势识别失败。
- 当有两次点击时 ,若两次点击相距时间在规定时间内 (默认规定时间为300毫秒),触发两次单击事件和一次双击事件。
- 当有两次点击时 ,若两次点击相距时间超出规定时间 ,触发两次单击事件不触发双击事件。
ts
@Entry
@Component
struct parallelRecognition {
@State count1: number = 0;
@State count2: number = 0;
build() {
Column() {
Column() {
Text('并行识别\n' + '单击次数:' + this.count1 + '\n双击次数:' + this.count2 + '\n')
.fontSize(28)
}
.border({ width: 3 })
.justifyContent(FlexAlign.Center)
.height(200)
.width(300)
// 以下组合手势为并行并别,单击手势识别成功后,若在规定时间内再次点击,双击手势也会识别成功
.gesture(
GestureGroup(GestureMode.Parallel,
TapGesture({ count: 1 })
.onAction(() => {
this.count1++;
}),
TapGesture({ count: 2 })
.onAction(() => {
this.count2++;
})
)
)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
效果
互斥识别
互斥识别组合手势对应的GestureMode为Exclusive 。互斥识别组合手势中注册的手势将同时进行识别,若有一个手势识别成功,则结束手势识别,其他所有手势识别失败。
示例
以在一个Column组件上绑定单击手势和双击手势组合而成的互斥识别组合手势 为例。若先绑定单击手势后绑定双击手势,由于单击手势只需要一次点击即可触发而双击手势需要两次,每次的点击事件均被单击手势消费而不能积累成双击手势,所以双击手势无法触发。若先绑定双击手势后绑定单击手势,则触发双击手势不触发单击手势。(以下例子是先绑单击再绑双击)
说明
- 当由单击手势和双击手势组成一个互斥识别组合手势后,在区域内进行点击时,单击手势和双击手势将同时进行识别。
- 当只有单次点击时,单击手势识别成功,双击手势识别失败。
- 当有两次点击时,手势响应取决于绑定手势的顺序。若先绑定单击手势后绑定双击手势,单击手势在第一次点击时即宣告识别成功,此时双击手势已经失败。即使在规定时间内进行了第二次点击,双击手势事件也不会进行响应,此时会触发单击手势事件的第二次识别成功。若先绑定双击手势后绑定单击手势,则会响应双击手势不响应单击手势。
ts
@Entry
@Component
struct exclusiveRecognition {
@State count1: number = 0;
@State count2: number = 0;
build() {
Column() {
Column() {
Text('并行识别\n' + '单击次数:' + this.count1 + '\n双击次数:' + this.count2 + '\n')
.fontSize(28)
}
.height(200)
.width('100%')
.border({ width: 3 })
.justifyContent(FlexAlign.Center)
//以下组合手势为互斥并别,单击手势识别成功后,双击手势会识别失败
.gesture(
GestureGroup(GestureMode.Exclusive,
TapGesture({ count: 1 })
.onAction(() => {
this.count1++;
}),
TapGesture({ count: 2 })
.onAction(() => {
this.count2++;
})
)
)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
效果
结语
随着鸿蒙系统的持续迭代与优化,手势识别技术必将迎来更广阔的发展空间。未来,它或许能打破设备间的界限,在智能家居、智能车载等多元场景实现无缝衔接,为用户打造全场景、沉浸式的交互体验。而这,也激励着开发者不断探索,深挖手势识别技术的潜力,推动人机交互迈向新的台阶。如果您觉得这篇关于鸿蒙手势识别的分析对您有帮助,不妨点个赞,关注我。后续我会持续分享更多前沿技术动态,陪您一同探索科技的无限可能 。