ArkUI 交互事件与手势:从触摸到组合手势

交互是界面和用户的对话

组件画好了、动画做好了,最终要响应"用户怎么操作"。ArkUI 的交互分两层:通用事件(触摸、点击、键盘、焦点)和手势事件(长按、拖动、捏合、旋转、滑动)。手势事件是基于通用事件进一步识别的结果。

用户操作
   ↓
通用事件:触摸、鼠标、键盘、焦点(底层原始事件)
   ↓ 框架识别
手势事件:单击、长按、拖动、捏合、旋转、滑动(识别后的语义)
   ↓ 组合
组合手势:多个手势组合(如双指缩放 + 拖动)

通用事件:Click 和 Touch

最基础的是 onClick,几乎每个组件都支持:

Button('点击我')
  .onClick(() => {
    console.info('按钮被点击');
  })

触摸事件 onTouch 提供更底层的触摸信息:

Text('触摸区域')
  .onTouch((event: TouchEvent) => {
    switch (event.type) {
      case TouchType.Down:
        console.info('手指按下');
        break;
      case TouchType.Move:
        console.info('手指移动');
        break;
      case TouchType.Up:
        console.info('手指抬起');
        break;
    }
  })

onTouch 能拿到触摸的坐标、类型(Down/Move/Up),适合做跟手操作。普通点击用 onClick 就够,需要精细控制才用 onTouch

单击手势 TapGesture

onClick 是简化的点击,TapGesture 是完整的手势点击,能设置点击次数:

Text('双击我')
  .gesture(
    TapGesture({ count: 2 })  // 双击
      .onAction((event: GestureEvent) => {
        console.info('双击触发');
      })
  )

count: 2 表示双击,count: 1 是单击。需要区分单击双击时用 TapGesture

长按手势 LongPressGesture

Text('长按我')
  .gesture(
    LongPressGesture({ repeat: false })
      .onAction((event: GestureEvent) => {
        console.info('长按触发');
      })
  )

长按常用于:弹出上下文菜单、删除确认、拖拽触发。repeat 控制是否重复触发(长按不动时是否连续回调)。

拖动手势 PanGesture

@Entry
@Component
struct PanDemo {
  @State offsetX: number = 0;
  @State offsetY: number = 0;

  build() {
    Text('拖动我')
      .width(80)
      .height(80)
      .backgroundColor('#FF6B35')
      .offset({ x: this.offsetX, y: this.offsetY })
      .gesture(
        PanGesture()
          .onActionUpdate((event: GestureEvent) => {
            // 拖动过程中持续回调,更新位置实现跟手
            this.offsetX += event.offsetX;
            this.offsetY += event.offsetY;
          })
      )
  }
}

onActionUpdate 在拖动过程中持续触发,event.offsetX/Y 是单次位移量,累加实现跟手效果。拖动是移动元素的基础手势。

捏合与旋转:双指手势

捏合(缩放)和旋转需要双指:

@Entry
@Component
struct PinchRotateDemo {
  @State scale: number = 1.0;
  @State angle: number = 0;

  build() {
    Text('双指缩放旋转')
      .scale({ x: this.scale, y: this.scale })
      .rotate({ angle: this.angle })
      .gesture(
        GestureGroup(GestureMode.Parallel,
          // 双指捏合缩放
          PinchGesture()
            .onActionUpdate((event: GestureEvent) => {
              this.scale *= event.scale;  // event.scale 是缩放比例
            }),
          // 双指旋转
          RotationGesture()
            .onActionUpdate((event: GestureEvent) => {
              this.angle += event.angle;  // event.angle 是旋转角度
            })
        )
      )
  }
}

GestureGroup 组合多个手势,GestureMode.Parallel 表示同时识别(缩放和旋转可同时进行)。

滑动手势 SwipeGesture

Text('滑动区域')
  .gesture(
    SwipeGesture()
      .onAction((event: GestureEvent) => {
        // 根据滑动方向处理
        if (event.angle >= 45 && event.angle < 135) {
          console.info('向上滑动');
        } else {
          console.info('其他方向滑动');
        }
      })
  )

滑动常用于:翻页、切换 tab、侧滑返回。event.angle 是滑动角度,用来判断方向。

手势组合:GestureGroup

多个手势组合是手势能力的核心。GestureGroup 有三种模式:

模式含义示例
GestureMode.Parallel并行识别,多个手势同时响应缩放 + 旋转同时进行
GestureMode.Sequence顺序识别,一个手势完成后另一个才开始长按后拖动(先长按再拖)
GestureMode.Exclusive互斥,只识别其中一个单击和双击二选一

顺序识别的典型场景——长按后拖动:

Text('长按后拖动')
  .gesture(
    GestureGroup(GestureMode.Sequence,
      LongPressGesture()  // 先长按
        .onAction(() => {
          console.info('长按激活');
        }),
      PanGesture()  // 再拖动
        .onActionUpdate((event: GestureEvent) => {
          console.info('拖动中');
        })
    )
  )

互斥识别——区分单击和双击:

Text('单击或双击')
  .gesture(
    GestureGroup(GestureMode.Exclusive,
      TapGesture({ count: 1 })
        .onAction(() => console.info('单击')),
      TapGesture({ count: 2 })
        .onAction(() => console.info('双击'))
    )
  )

焦点事件

焦点事件主要用于键盘/遥控器导航场景:

Text('可聚焦组件')
  .focusable(true)  // 允许获取焦点
  .onFocus(() => {
    console.info('获得焦点');
  })
  .onBlur(() => {
    console.info('失去焦点');
  })

手机 app 的触屏场景焦点事件用得少,但做 TV 应用、无障碍导航时是基础。

小总结

  1. 手势会拦截组件默认行为。给组件加了 gesture 可能影响其默认手势(如列表的滚动)。需要时用 priorityGesture 让手势优先,或用 parallelGesture 并行。

  2. 拖动注意坐标累积PanGestureevent.offsetX/Y 是单次增量,要累加到 @State 上。直接赋值会跳动不跟手。

  3. 组合手势用对模式。并行、顺序、互斥三种模式语义完全不同,用错模式手势会互相冲突,出现"怎么都不触发"或"同时触发"的奇怪现象。

  4. 触摸和手势别叠加混用。同一组件既用 onTouch 又用 gesture 容易冲突。底层细节用 onTouch,语义操作用手势,二选一。

  5. 长按手势的 repeat 默认 false。如果希望长按不放持续触发(如音量连调),需要显式设置 repeat: true

Logo

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

更多推荐