学习原因
在学习鸿蒙的时候,突然想到在 Android 中可以 view 中设置 手势以及 touch事件,那么鸿蒙如何设置呢?是不是跟 android 一样的传递方式?鸿蒙如何进行互动冲突处理呢?
有了想法就先从手势入手,鸿蒙的手势还是比较多的;简单逻辑一下常见的手势
例如:点击手势(TapGesture),长按手势(LongPressGesture),滑动手势(PanGesture),捏合手势(PinchGesture),旋转手势(RotationGesture),快滑手势(SwipeGesture);
鸿蒙手势的相关链接文档如下:
1.点击手势(TapGesture):
该点击手势支持单点,双点,多次点击;同时也可以指定手指个数等;针对 Android 中双击响应,就可以直接设置count =2 来实现;代码如下:
Text('点击')
.fontSize(20)
.fontColor(Color.Black)
.gesture(
TapGesture({
count: 1, //识别的连续点击次数.默认是300ms,如果设置双点击就是改成2
fingers: 1 //一个手指头
}).onAction((event: GestureEvent) => {
//event 可以获取到手指点击的位置信息
console.log('--------- > TapGesture ' + JSON.stringify(event))
})
)
//获取对应 event 的值;针对后续手势的 event 的 json 也是如下。就不再一一粘贴日志;
--------- > TapGesture {"repeat":false,"offsetX":0,"offsetY":0,"scale":1,"angle":0,"speed":0,"timestamp":375778151000,"pinchCenterX":0,
"pinchCenterY":0,"source":2,"pressure":0,"tiltX":0,"tiltY":0,
"rollAngle":0,"sourceTool":1,"velocityX":0,"velocityY":0,"velocity":0,
"fingerList":[{"id":0,"hand":0,"globalX":195.85117885044642,
"globalY":92.82436697823661,"localX":27.851178850446427,
"localY":7.110081263950893,"displayX":195.85117885044642,
"displayY":92.82436697823661,"globalDisplayX":195.85117691723616,
"globalDisplayY":92.82436589178558}],
"fingerInfos":[{"id":0,"hand":0,"globalX":195.85117885044642,
"globalY":92.82436697823661,"localX":27.851178850446427,
"localY":7.110081263950893,"displayX":195.85117885044642,
"displayY":92.82436697823661,"globalDisplayX":195.85117691723616,
"globalDisplayY":92.82436589178558}],
"deviceId":1,
"target":{"area":{"position":{"x":168,"y":46.857142857142854},
"globalPosition":{"x":168,"y":285.7142857142857},
"width":40,"height":23.428571428571427}},
"axisVertical":0,"axisHorizontal":0,"targetDisplayId":0,
"tapLocation":{"windowX":195.85117885044642,"windowY":92.82436697823661,
"x":27.851178850446427,"y":7.110081263950893,
"displayX":195.85117885044642,"displayY":92.82436697823661}}
2.长按手势(LongPressGesture):
在 Android 中也有长点击事件;在鸿蒙中同样可以依赖手势实现;代码如下:
Text('点击')
.fontSize(20)
.fontColor(Color.Black)
.gesture(
LongPressGesture({
fingers:1,//手指个数
duration:500,//长按多久触发事件
repeat:true//长按是否重复触发;如果想触发一次就改成 false
})
.onAction((event: GestureEvent) => {
console.log('--------- > LongPressGesture onAction ')
})
.onActionEnd((event: GestureEvent) => {
console.log('--------- > LongPressGesture onActionEnd ')
})
.onActionCancel((event: GestureEvent) => {
console.log('--------- > LongPressGesture onActionCancel ')
})
对应的日志:
28045-28045 I --------- > LongPressGesture onAction
28045-28045 I --------- > LongPressGesture onAction
28045-28045 I --------- > LongPressGesture onActionEnd
3.滑动手势(PanGesture):
这个手势和 Android 中的拖拽手势很像,但是鸿蒙的滑动手势功能更加丰富;包含手指设置,距离设置,以及滑动方向的设置;例如想要仅仅监听垂直方向的移动,就可以直接设置direction: PanDirection.Vertical;具体代码如下:
Text('点击')
.fontSize(20)
.fontColor(Color.Black)
.gesture(
PanGesture({
fingers: 1, //手指
direction: PanDirection.Vertical, //相应的方向,默认是 All
distance: 8, //最小滑动距离
})
.onActionStart((event: GestureEvent) => {
console.log('--------- > PanGesture onActionStart ')
})
.onActionUpdate((event: GestureEvent) => {
console.log('--------- > PanGesture onActionUpdate ')
})
.onActionEnd((event: GestureEvent) => {
console.log('--------- > PanGesture onActionEnd ')
})
.onActionCancel((event: GestureEvent) => {
console.log('--------- > PanGesture onActionCancel ')
})
)
.width('100%')
.height(100)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Grey)
在设置针对属性direction可以设置的值如下:Horizontal(水平滑动),Left(向左滑动),right(向右滑动),Vertical(垂直滑动),Up(向上滑动),Down(向下滑动),All(全部支持);
在PanGesture 手势的 onActionUpdate()中可以通过 event 获取到滑动的偏移量等值,同时也可以通过 offsetY/X 获取到滚动方向,offsetY<0是手指先上滚动,offsetY>0是手指向下滚动;offsetX<0 手指向左滑动,offsetX>0手指向右滑动;
通过上述就可以借助滑动的中的移动距离实现对应的移动效果;例如实现view 的跟随手指移动效果(常见的例如应用首页的悬浮窗的上下移动,具体代码放最后)
4.捏合手势(PinchGesture):
这个手势有点像 Android 中的缩放手势;常用用于进行大图预览时的放大和缩小;pinchCenterX,pinchCenterY,event.scale;这三个值是捏合手势常用的参数;实例代码:
PinchGesture({
fingers: 2, //手指最少2个,这个地方注意;范围是【2-5】
distance: 5//距离默认5vp
})
.onActionStart((event: GestureEvent) => {
console.log('--------- > PinchGesture onActionStart ')
})
.onActionUpdate((event: GestureEvent) => {
console.log('--------- > PinchGesture onActionUpdate ')
})
.onActionEnd((event: GestureEvent) => {
console.log('--------- > PinchGesture onActionEnd ')
})
.onActionCancel((event: GestureEvent) => {
console.log('--------- > PinchGesture onActionCancel ')
})
下面是鸿蒙的代码示例链接:文档中心
实现跟随手指的移动效果;
想要实现手指的跟随滚动;首先想到的是通过手势的滑动手势(PanGesture)+设置组件的位置(position)来实现组件的跟随移动效果;
由于为了避免滑动时,组件移除屏幕,因此需要设置 view 滑动的边界值;就需要获取父容器的宽高,以及自身的宽高;获取宽高方式如下:
try{
//根据 id获取容器的宽高
let rect = this.getUIContext().getComponentUtils().getRectangleById('parentId')
this.parentWidth = this.getUIContext().px2vp(rect.size.width)
this.parentHeight = this.getUIContext().px2vp(rect.size.height)
let rectChild = this.getUIContext().getComponentUtils().getRectangleById('stack')
this.moveXMax = this.parentWidth - this.getUIContext().px2vp(rectChild.size.width)
this.moveYMax = this.parentHeight - this.getUIContext().px2vp(rectChild.size.height)
} catch (error) {
console.log('-----------> gesture ' + JSON.stringify(error))
}
上述方式是通过 id 获取的宽高值;需要在 onPageShow()方法中添加;如果你没有设置外部父组件,是按照屏幕的宽高来作为滑动区域的话;可以这样获取屏幕的宽度:
//方式1
try {
let screenSize = display.getDefaultDisplaySync();
this.screenWidth = this.getUIContext().px2vp(screenSize.width)
this.screenHeight = this.getUIContext().px2vp(screenSize.height)
} catch (error) {
}
//方式2
window.getLastWindow(this.getUIContext().getHostContext())
.then((windowClass) => {
try {
//获取的可以说是屏幕的大小,这个地方获取的是 px
let rect = windowClass.getGlobalRect()
this.screenWidth = this.getUIContext().px2vp(rect.width)
this.screenHeight = this.getUIContext().px2vp(rect.height)
}catch(error){}
}
然后在组件的的onSizeChange()方法中,通过获取子组件的宽高,来获取组件移动的最大区域值;
跟随手指移动组件整体代码如下:
(因为页面使用了 HMRouter 跳转,onPageShow()无法相应,因此使用了对应的生命周期监听进行处理,如果onPageShow()方法可以监听,在该方法中也可以)
//进行事件的监听
@Local module: ARouterLifecycleModule | null =
(HMRouterMgr.getCurrentLifecycleOwner()?.getLifecycle() as PageDurationLifecycle).module
//首先获取到生命监听
@Monitor('module.status')
pageLifecycle(): void {
//在 onShow方法中可以获取view 的宽度
if ('onShown' === this.module?.status) {
try {
//根据 id获取容器的宽高
let rect = this.getUIContext().getComponentUtils().getRectangleById('parentId')
this.parentWidth = this.getUIContext().px2vp(rect.size.width)
this.parentHeight = this.getUIContext().px2vp(rect.size.height)
let rectChild = this.getUIContext().getComponentUtils().getRectangleById('stack')
this.moveXMax = this.parentWidth - this.getUIContext().px2vp(rectChild.size.width)
this.moveYMax = this.parentHeight - this.getUIContext().px2vp(rectChild.size.height)
} catch (error) {
console.log('-----------> gesture ' + JSON.stringify(error))
}
}
}
build() {
this.fingerMove()
}
@Builder
fingerMove() {
Stack() {
//验证手势的跟随
Stack() {}
.width('50%')
.height('25%')
.backgroundColor(Color.Gray)
.id('stack')
//验证发现位置点需要的是 vp 值,并不是 px 的值,在默认设置数值的时候;
// 如果使用后面添加后缀的名字也可以方式 `${px 值}px`
.position({ x: this.xPosition, y: this.yPosition })
.gesture(
PanGesture()
.onActionStart((event: GestureEvent) => {
//点击的时候获取点击的位置点
this.startX = event.offsetX
this.startY = event.offsetY
})
.onActionUpdate((event: GestureEvent) => {
// event.offsetX 是偏移量,属于一次滑动过程中的偏移量。并不是相对于上次移动的偏移量
//获取到新的位置点,并同开始点进行计算获取到移动的距离
this.xPosition += event.offsetX - this.startX
this.yPosition += event.offsetY - this.startY
//设置边界值
this.xPosition = this.xPosition < 0 ? 0 : this.xPosition > this.moveXMax ? this.moveXMax : this.xPosition
this.yPosition = this.yPosition < 0 ? 0 : this.yPosition > this.moveYMax ? this.moveYMax : this.yPosition
//更新值
this.startX = event.offsetX
this.startY = event.offsetY
})
);
}
.width('100%')
.height('100%')
.backgroundColor(Color.Green)
.id('parentId')
}