手势分类

手势是一种通过手部的动作来控制设备交互的方式, 是人机交互中重要的组成方式,例如我们对一个手机屏幕的点击, 双击, 捏合,这类动作,都是手势。

手势具体可以分为

  • 单一手势, 指的是由一个简单独立的动作构成的手势,通常会涉及到一个明确的操作, 是手势事件中最为基础的形式,如单指单指多指的,单击,双击, 滑动,长按等等。
  • 组合手势: 将多个单一的手势,按照特定的顺序(同时发生, 同时只有一个发生,必须按照顺序发生)组合起来,形成给为复杂的手势操作,可以完成就更加丰富的交互方式。例如微信朋友圈编辑动态,九宫格照片拖拽的逻辑,实现照片位置的对调。
  • 多层级手势,常见于父子组件嵌套时,比较复杂的交互逻辑。比如地图应用的地图查看场景

单一手势

单一手势分类

手势名称

API

动作

点击手势

TapGesture

手指轻触屏幕

长按手势

LongPressGesture

手指按住屏幕一段时间

滑动手势

SwiperGesture

手指在屏幕上快速滑动

旋转手势

RotationGesture

两根手指在屏幕上做旋转动作

捏合手势

PinchGesture

两根手指在屏幕上向内靠拢或者向外分开

拖动手势

PanGesture

单指按住屏幕上的某个元素并拖动手指

API整体分析

如果想监听上述的手势类型,需要在控件上加一个gesture属性。 我们以对双击手势的监听为例,代码如下:

Text('测试一下双点击手势')
    .id(`tapGestureAccess`)
    .fontSize(45)
    .alignRules({
      start: { anchor: '__container__', align: HorizontalAlign.Start },
      top: { anchor: '__container__', align: VerticalAlign.Top },
    })
    .gesture(TapGesture({ count: 2, fingers: 2 }) // 核心api。也就是调用gesture方法
      .onAction((event) => {
        if (event) {
          this.resetAllData()
          this.tapGestureText = `点击事件onAction, event=${JSON.stringify(event)}`
        }
      }), GestureMask.Normal)

所有的单一属性都是靠gesture来设置的。gesture方法声明于CommonMethod文件中,也就意味着,所有的控件都具备这个属性,都具备监听手势的能力。我们首先大致的了解下这个API的结构。

上图可知:

  • gesture指定监听什么类型的手势,靠GestureType传入的是谁。而GestureType有7类,代表各类我们上述讲的手势。这些手势配置接口,全部继承自GestureInteface类。
  • gesture拦截模式,靠GestureMask来指定。 Normal代表按照系统顺序,优先分发给可以消费该手势的子控件。和Android保持一致。 而另一个值IgnoreInternal则会屏蔽子组件手势,父组件和子组件都具备响应能力的场景,优先处理父组件。

各类单一手势如何使用API

以点击手势-TapGestureInterface为例分析

点击手势支持单击和双击, 单手指和多手指。配置是十分灵活的。具体API拆析如图所示:

上图可以看出,我们如果监听点击手势,可以调用UI组件的gesture方法, 并将第一个入参指定为TapGestureInterface, 第二个参数随您的需求自己定义。

而TapGestureInterface具备两个能力:

  • 指定要监听的手势的细节, 例如TapGestureInterface的构造参数中,就有count(您想监听连续几次点击), finger(这个点击是几个手指动作), distance(手指最大的移动距离,多出不算点击, 在范围内才算点击)三个参数。
  • 回调,即系统识别出您指定要求的手势后立马给与回调。

理解到这里的话, 我们基本不用研究文档了, 甚至可以写出比文档中还要细致的案例出来。因为所有接口参数心里都有数了。

接下来我们就对比着图示写一下代码,在写的过程中发现,报错了! 那么是怎么回事呢?

但是源码里明明这样声明的:

而关于TapGestureInterface的信息, 源码中还有一个细节:

为什么没有办法直接按照TapGestureInterface来进行参数构造呢?这里面涉及到ArkTs中的语法问题:

第一张图:在ArkTS中, interface不能直接初始化为对象, 也就是不能new。 这个和Java是一致的。所以第一张图报错正常。如果不想报错, 您也可以自己写一个TapGestureInterface的实现类。然后初始化这个实例作为参数传进去。

第二张图:GestureType是一种联合类型,它另一方面也列出了,手势动作所有的类型。

第三张图:TapGesture 是一个常量,既然为常量,则意味着在编译的时候就已经清清楚楚的掌握了大小,并约定了所占的空间。 TapGesture作为常量在声明的时候必须要初始化的, 所以这个TapGesture一定是一个有效的实例类, 这个类是实现了TapGestureInterface!这个比较有意思, 里面包含了一种不向外部公布,而又能实现具体功能,又能让外部使用的类,用户不用关心这个类具体实现方式,只管拿过来系统根据此类初始化的实例使用就可以了。

PS:这种设计方式,有个比较专业的名称,叫 接口类型化工厂常量 模式。好处就是为开发者提供简洁统一的调用入口,并隐藏框架的实现细节,并可以支撑运行时动态实例化。 就像您永远可以不知道TapGestureInterfaceImpl的存在那样。

好,那么这样的话,我们就可以清晰的知道代码应当怎么调用了, 即您在gesture方法中,第一个参数可以直接传入TapGesture常量,您可以通过修改这个常量,而获取对应的监听。

代码案例
 Text('测试一下双点击手势')
    .id(`tapGestureAccess`)
    .fontSize(45)
    .alignRules({
      start: { anchor: '__container__', align: HorizontalAlign.Start },
      top: { anchor: '__container__', align: VerticalAlign.Top },
    })
    .gesture(TapGesture({ count: 2, fingers: 2 }) //直接用系统给出的TapGesture常量,并说明是两个手指双击
      .onAction((event) => {
        if (event) {
          this.resetAllData()
          this.tapGestureText = `点击事件onAction, event=${JSON.stringify(event)}`
        }
      }), GestureMask.Normal)
  
}
关于回调用的GestureEvent里面都有什么内容
{
  "repeat": false,
  "offsetX": 0,
  "offsetY": 0,
  "scale": 1,
  "angle": 0,
  "speed": 0,
  "timestamp": 43491339572000,
  "pinchCenterX": 0,
  "pinchCenterY": 0,
  "source": 2,
  "pressure": 2.8299999237060547,
  "tiltX": 0,
  "tiltY": 0,
  "rollAngle": 0,
  "sourceTool": 1,
  "velocityX": 0,
  "velocityY": 0,
  "velocity": 0,
  "fingerList": [
    {
      "id": 0,
      "hand": 0,
      "globalX": 238.22222222222223,
      "globalY": 167.7037037037037,
      "localX": 237.03703703703704,
      "localY": 26.666666666666668,
      "displayX": 238.22222222222223,
      "displayY": 167.7037037037037
    },
    {
      "id": 1,
      "hand": 0,
      "globalX": 151.40740740740742,
      "globalY": 168.2962962962963,
      "localX": 150.22222222222223,
      "localY": 27.25925925925926,
      "displayX": 151.40740740740742,
      "displayY": 168.2962962962963
    }
  ],
  "deviceId": 5,
  "target": {
    "area": {
      "position": {
        "x": 1.1851851851851851,
        "y": 97.25925925925925
      },
      "globalPosition": {
        "x": 1.1851851851851851,
        "y": 141.11111111111111
      },
      "width": 360.2962962962963,
      "height": 105.48148148148148
    },
    "id": "tapGestureAccess"
  },
  "axisVertical": 0,
  "axisHorizontal": 0,
  "targetDisplayId": 0
}

可以看出和我们图中分析的API是保持一致的。在特殊的情况下, 或者是完全自定义的高难度组件中, 设计交互,会用到这些数据。对于单一手势交互,就不用在意里面的内容了。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐