鸿蒙简单了解手势使用

5 阅读5分钟

学习原因

    在学习鸿蒙的时候,突然想到在 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')
  }