# [特殊字符] 零基础学 ArkUI 手势(专题五):从点击到多指触控,一网打尽 6 种手势

👆 零基础学 ArkUI 手势(专题五):从点击到多指触控,一网打尽 6 种手势

博主说: 一个好 App 和伟大 App 的差距,往往就在「交互细节」上------点击有波纹反馈、滑动跟手指走、缩放丝般顺滑、长按弹出快捷菜单......这些交互的核心就是 手势系统。ArkUI 的手势系统非常完整,支持从最简单的单击到复杂的多指旋转、从手势并行到互斥冲突处理。今天这篇专题将带你逐一攻克全部 6 种手势,并附带避坑指南、性能优化和综合实战。


📱 为什么要学手势?

为什么手势重要 实际例子
提升操作效率 左滑删除比长按→删除快 3 倍
增强沉浸感 双指缩放图片,自然得像在摸一张真实照片
节省屏幕空间 长按弹出菜单,不用额外放一排按钮
差异化体验 摇一摇找客服、画圈截图------你的 App 更有记忆点

根据 Nielsen Norman Group 的研究,手势驱动的交互比按钮点击快 20%~60%,而且用户满意度更高。


⚙️ 运行环境要求

项目 版本要求
DevEco Studio 5.0.3.800 及以上
HarmonyOS SDK API 12(HarmonyOS 5.0.0)及以上
应用模型 Stage 模型
开发语言 ArkTS
真机要求 多指手势(Pinch/Rotation)必须真机,模拟器不支持双指触控

环境配置截图示意

📄 👉 点此查看环境配置截图网页

图1:新建 Empty Ability 项目,选择 API 12 --- 浏览器打开查看完整环境配置截图


🛠️ 6 种手势逐一实战

ArkUI 的手势系统围绕一个核心 API 设计:

typescript 复制代码
.gesture(
  SomeGesture({ 参数 })
    .onAction((event) => { /* 触发 */ })
    .onActionUpdate((event) => { /* 进行中 */ })
    .onActionEnd((event) => { /* 结束 */ })
    .onActionCancel(() => { /* 中断 */ })
)

手势生命周期: Start → Update(N次) → End / Cancel

🎯 手势 1:TapGesture --- 点击(最常用但最容易被忽视的细节)

作用: 手指按下→抬起,检测一次「点击」动作。

typescript 复制代码
// ─── 基础用法 ───
Column()
  .width(200).height(100)
  .backgroundColor('#007AFF')
  .borderRadius(12)
  .gesture(
    TapGesture({ count: 1 })  // count=1 单击,count=2 双击
      .onAction((event: GestureEvent) => {
        console.log(`点击位置: (${event.fingerInfo[0]?.x}, ${event.fingerInfo[0]?.y})`);
        console.log(`手指数量: ${event.fingerInfo.length}`);
      })
  )

进阶:双击检测:

typescript 复制代码
@State tapCount: number = 0;
@State lastTapTime: number = 0;

Column()
  .width(200).height(100).backgroundColor('#007AFF').borderRadius(12)
  .gesture(
    TapGesture({ count: 2 })  // 检测双击
      .onAction(() => {
        AlertDialog.show({ message: '✅ 双击事件触发!' });
      })
  )
// 替代方案:不用 count=2,直接用时间判断
.gesture(
  TapGesture({ count: 1 })
    .onAction(() => {
      const now = Date.now();
      if (now - this.lastTapTime < 300) {
        AlertDialog.show({ message: '✅ 连续点击(双击)' });
      }
      this.lastTapTime = now;
    })
)

💡 点击位置有什么用? 可用于实现「点赞波纹效果」------在点击位置播放一个扩散圆环动画。


🎯 手势 2:LongPressGesture --- 长按触发菜单

作用: 手指按住超过指定时长(默认 500ms)触发,适合快捷菜单、拖拽开始、批量选择。

typescript 复制代码
// ─── 基础用法 ───
@Entry
@Component
struct LongPressDemo {
  @State progress: number = 0;       // 长按进度 0~100
  @State isLongPressed: boolean = false;
  private timerId: number = -1;

  build() {
    Column() {
      Column() {
        // 长按进度环
        Circle()
          .width(80).height(80)
          .fill('transparent')
          .stroke('#E0E0E0').strokeWidth(6)

        Circle()
          .width(80).height(80)
          .fill('transparent')
          .stroke('#34C759').strokeWidth(6)
          .strokeDashArray([this.progress * 2.5, 250])
          .rotate({ angle: -90 })

        Text(this.isLongPressed ? '✅ 已触发' : '长按我')
          .fontSize(16)
          .fontColor(this.isLongPressed ? '#34C759' : '#333')
          .position({ x: 0, y: 0 })
          .width(80).height(80)
          .textAlign(TextAlign.Center)
          .lineHeight(80)
      }
      .width(80).height(80)
      .gesture(
        LongPressGesture({ duration: 800 }) // 800ms 长按
          .onAction((event: GestureEvent) => {
            this.isLongPressed = true;
            // 震动反馈
            vibrator.startVibration({
              type: 'time',
              duration: 30
            }, (err) => {});
            AlertDialog.show({
              title: '快捷菜单',
              message: '复制 / 粘贴 / 删除',
              confirm: { value: '关闭' }
            });
          })
          .onActionEnd(() => {
            // 手指抬起
          })
          .onActionCancel(() => {
            this.isLongPressed = false;
            this.progress = 0;
          })
      )

      Text('长按 0.8 秒触发快捷菜单')
        .fontSize(13).fontColor('#999').margin({ top: 12 })

      Text('⚠️ 触发时会震动反馈')
        .fontSize(12).fontColor('#FF9500').margin({ top: 4 })
    }
    .width('100%').height(250).justifyContent(FlexAlign.Center)
  }
}

💡 应用场景: 微信聊天列表长按弹出菜单、桌面 App 长按图标弹出快捷操作、图片长按保存。


🎯 手势 3:PanGesture --- 拖拽(应用最广的手势)

作用: 单指或多指在屏幕上滑动,实时获取偏移量。这是 应用最广 的手势。

typescript 复制代码
@Entry
@Component
struct PanDemo {
  // 拖拽位置
  @State dragX: number = 0;
  @State dragY: number = 0;

  // 拖拽历史(用于惯性滑动)
  private velocityX: number = 0;
  private velocityY: number = 0;

  build() {
    Column() {
      // ─── 可拖拽的方块 ───
      Column() {
        Text('⇆ 拖拽我')
          .fontSize(16).fontWeight(FontWeight.Bold).fontColor('#fff')
      }
      .width(100).height(100)
      .backgroundColor('#FF9500')
      .borderRadius(16)
      .shadow({ radius: 12, color: '#40000000', offsetY: 6 })
      .offset({ x: this.dragX, y: this.dragY })
      .gesture(
        PanGesture({
          direction: PanDirection.All,     // 所有方向
          distance: 10,                     // 移动 10px 才识别为拖拽
          fingers: 1                        // 单指拖拽
        })
          .onActionStart((event: GestureEvent) => {
            // 拖拽开始:加大阴影
            // 实际项目中可以在这里改变状态
          })
          .onActionUpdate((event: GestureEvent) => {
            // 实时更新位置
            this.dragX = event.offsetX;
            this.dragY = event.offsetY;
            // 获取当前速度(用于惯性滑动)
            this.velocityX = event.velocityX;
            this.velocityY = event.velocityY;
          })
          .onActionEnd(() => {
            // 松手后弹簧回弹
            animateTo({
              duration: 500,
              curve: Curve.SpringMotion,
              delay: 0
            }, () => {
              this.dragX = 0;
              this.dragY = 0;
            });
          })
      )

      Text(`当前位置: (${this.dragX.toFixed(0)}, ${this.dragY.toFixed(0)})`)
        .fontSize(14).fontColor('#888').margin({ top: 16 })

      Text('拖拽方块 → 松手弹簧回弹')
        .fontSize(13).fontColor('#999').margin({ top: 4 })

      // ─── PanDirection 方向说明 ───
      Text('PanDirection 可选值:')
        .fontSize(14).fontWeight(FontWeight.Bold).margin({ top: 24 })

      Column() {
        ForEach([
          ['All', '所有方向'],
          ['Horizontal', '仅水平'],
          ['Vertical', '仅垂直'],
          ['Left', '仅向左'],
          ['Right', '仅向右'],
          ['Up', '仅向上'],
          ['Down', '仅向下'],
        ], (item: string[]) => {
          Row() {
            Text(`PanDirection.${item[0]}`)
              .fontSize(13).fontColor('#007AFF').fontFamily('Courier New')
            Text(`--- ${item[1]}`)
              .fontSize(13).fontColor('#666').margin({ left: 8 })
          }.padding({ top: 2, bottom: 2 })
        })
      }
      .margin({ top: 8 })
    }
    .width('100%').height(450).padding(16)
  }
}

💡 PanGesture 核心参数解析:

参数 类型 默认值 说明
fingers number 1 最少手指数
direction PanDirection All 滑动方向
distance number 5 最小滑动距离(px),小于此值视为点击

🎯 手势 4:PinchGesture --- 双指缩放

作用: 双指捏合/张开,常用于图片缩放、地图缩放、字体大小调整。

typescript 复制代码
@Entry
@Component
struct PinchDemo {
  @State scale: number = 1;
  @State savedScale: number = 1;

  build() {
    Column() {
      Text('双指缩放图片').fontSize(18).fontWeight(FontWeight.Bold)

      // ─── 可缩放的图片 ───
      Column() {
        Text('🖼️').fontSize(80)
      }
      .width(200 * this.scale).height(200 * this.scale)
      .backgroundColor('#F0F4FF')
      .borderRadius(16)
      .gesture(
        PinchGesture({ fingers: 2 })
          .onActionStart(() => {
            // 记住缩放前的值
            this.savedScale = this.scale;
          })
          .onActionUpdate((event: GestureEvent) => {
            // event.scale 是相对于起始状态的缩放比例
            // 当前缩放 = 保存的缩放 × 本次缩放因子
            this.scale = Math.max(0.3, Math.min(3.0,
              this.savedScale * event.scale
            ));
          })
          .onActionEnd(() => {
            // 如果缩放到边缘,回弹
            if (this.scale < 0.5) {
              animateTo({ duration: 200 }, () => { this.scale = 0.5; });
            } else if (this.scale > 2.5) {
              animateTo({ duration: 200 }, () => { this.scale = 2.5; });
            }
          })
      )

      Text(`当前缩放: ${(this.scale * 100).toFixed(0)}%`)
        .fontSize(16).fontColor('#007AFF').fontWeight(FontWeight.Bold)
        .margin({ top: 16 })

      Slider({ value: this.scale * 100, min: 30, max: 300, step: 1 })
        .width('80%').margin({ top: 8 })
        .onChange((val: number) => { this.scale = val / 100; })

      Text('用双指捏合缩放,或拖动滑块')
        .fontSize(13).fontColor('#999').margin({ top: 8 })

      Divider().margin(16)

      // ─── 极限值约束 ───
      Text('🔐 缩放范围约束:')
        .fontSize(14).fontWeight(FontWeight.Bold)
      Text('最小值 0.3x (30%) --- 防止缩太小找不到')
        .fontSize(13).fontColor('#555').margin({ top: 4 })
      Text('最大值 3.0x (300%) --- 防止放大到模糊')
        .fontSize(13).fontColor('#555')
    }
    .width('100%').padding(16)
  }
}

⚠️ 重要提示: PinchGesture 必须真机测试!模拟器不支持双指操作。event.scale 的值从 1.0 开始,捏合时 < 1,张开时 > 1。


🎯 手势 5:RotationGesture --- 双指旋转

作用: 双指旋转,用于图片旋转、地图方向调整、表盘校准。

typescript 复制代码
@Entry
@Component
struct RotationDemo {
  @State angle: number = 0;
  @State savedAngle: number = 0;

  build() {
    Column() {
      Text('双指旋转').fontSize(18).fontWeight(FontWeight.Bold)

      Column() {
        Text('🔄').fontSize(80)
      }
      .width(180).height(180)
      .backgroundColor('#FFF0E0')
      .borderRadius(16)
      .rotate({ angle: this.angle })
      .gesture(
        RotationGesture({ fingers: 2, angle: 10 })  // 10° 以上才触发
          .onActionStart(() => {
            this.savedAngle = this.angle;
          })
          .onActionUpdate((event: GestureEvent) => {
            // event.angle 是本次旋转的增量角度
            this.angle = this.savedAngle + event.angle;
          })
          .onActionEnd(() => {
            // 动画回正到最近的 90° 增量
            const nearest90 = Math.round(this.angle / 90) * 90;
            animateTo({ duration: 300, curve: Curve.FastOutSlowIn }, () => {
              this.angle = nearest90;
            });
          })
      )

      Text(`当前角度: ${Math.round(this.angle)}°`)
        .fontSize(16).fontColor('#FF9500').fontWeight(FontWeight.Bold)
        .margin({ top: 16 })

      Row() {
        Button('↺ 重置')
          .backgroundColor('#F0F0F0').fontColor('#333')
          .onClick(() => {
            animateTo({ duration: 300 }, () => { this.angle = 0; });
          })

        Button('+90°')
          .backgroundColor('#F0F0F0').fontColor('#333').margin({ left: 8 })
          .onClick(() => {
            animateTo({ duration: 300 }, () => { this.angle += 90; });
          })
      }
      .margin({ top: 12 })

      Text('双指旋转 → 松手吸附到最近 90° 位置')
        .fontSize(13).fontColor('#999').margin({ top: 8 })
    }
    .width('100%').height(400).padding(16).justifyContent(FlexAlign.Center)
  }
}

🎯 手势 6:SwipeGesture --- 快速滑动

作用: 检测手指的快速滑动(Fling),常用于「左滑删除」「右滑返回」。

typescript 复制代码
@Entry
@Component
struct SwipeDemo {
  @State items: string[] = [
    '📝 明天10点开会',
    '🛒 买牛奶和面包',
    '📞 回电话给张总',
    '✈️ 订下周三机票',
    '🎂 郭哥生日准备礼物'
  ];

  @State deletedItems: string[] = [];

  build() {
    Column() {
      Text('左滑删除 --- SwipeGesture 实战')
        .fontSize(18).fontWeight(FontWeight.Bold).margin({ bottom: 8 })

      Text('向左快速滑动条目可删除')
        .fontSize(13).fontColor('#999').margin({ bottom: 12 })

      List({ space: 8 }) {
        ForEach(this.items, (item: string, idx: number) => {
          ListItem() {
            Row() {
              Text(item).fontSize(15).layoutWeight(1)
              Text('← 左滑').fontSize(12).fontColor('#ccc')
            }
            .padding(16)
            .width('100%')
            .backgroundColor('#FFF')
            .borderRadius(10)
            .shadow({ radius: 2, color: '#10000000', offsetY: 1 })
          }
          .gesture(
            // 注意:SwipeGesture 的方向是从开始到结束的方向
            // SwipeDirection.Left = 手指从右向左滑动
            SwipeGesture({ direction: SwipeDirection.Left, speed: 100 })
              .onAction((event: GestureEvent) => {
                // 动画删除
                animateTo({ duration: 300 }, () => {
                  const deleted = this.items.splice(idx as number, 1);
                  this.deletedItems.push(deleted[0]);
                });
                // 震动反馈
                vibrator.startVibration({
                  type: 'time',
                  duration: 20
                }, (err) => {});
              })
          )
        }, (item: string) => item)
      }
      .layoutWeight(1)
      .width('100%')

      if (this.deletedItems.length > 0) {
        Divider().margin({ top: 8, bottom: 8 })
        Text('🗑️ 已删除:').fontSize(14).fontWeight(FontWeight.Bold)
        ForEach(this.deletedItems, (item: string) => {
          Text(item).fontSize(13).fontColor('#999').margin({ top: 4 })
        })
        Button('↺ 恢复所有')
          .fontSize(14).backgroundColor('#E5E5EA').fontColor('#333')
          .margin({ top: 8 })
          .onClick(() => {
            this.items = [...this.items, ...this.deletedItems];
            this.deletedItems = [];
          })
      }
    }
    .width('100%').height('100%').padding(16).backgroundColor('#F5F5F5')
  }
}

🎯 综合实战:手势解锁九宫格

将 TapGesture + PanGesture 结合,做一个完整的九宫格手势密码锁。

typescript 复制代码
@Entry
@Component
struct GestureLock {
  // 3×3 网格数据
  @State points: boolean[][] = Array.from(
    { length: 3 }, () => Array(3).fill(false)
  );
  @State selected: number[] = [];    // 选中的点序号
  @State result: string = '';
  @State resultColor: string = '#333';

  // 预设密码:第 0,4,8 个点(对角线)
  private password: number[] = [0, 4, 8];
  // 用于记录手指触摸位置
  private touchX: number = 0;
  private touchY: number = 0;

  // 获取点的中心坐标
  getPointCenter(index: number): { x: number; y: number } {
    const row = Math.floor(index / 3);
    const col = index % 3;
    return {
      x: 70 + col * 100,  // 间距 100px,起始偏移 70
      y: 70 + row * 100
    };
  }

  // 判断手指是否在某个点范围内
  hitTest(x: number, y: number): number {
    for (let i = 0; i < 9; i++) {
      const center = this.getPointCenter(i);
      const dx = x - center.x;
      const dy = y - center.y;
      if (dx * dx + dy * dy < 40 * 40) {  // 40px 半径
        return i;
      }
    }
    return -1;
  }

  // 重置所有状态
  resetAll() {
    this.selected = [];
    this.points = Array.from({ length: 3 }, () => Array(3).fill(false));
    this.result = '';
  }

  // 验证密码
  verify() {
    const correct = JSON.stringify(this.selected) === JSON.stringify(this.password);
    this.result = correct ? '✅ 解锁成功!' : '❌ 密码错误,请重试';
    this.resultColor = correct ? '#34C759' : '#FF3B30';

    if (correct) {
      // 成功:延迟后重置
      setTimeout(() => { this.resetAll(); }, 1500);
    } else {
      // 失败:显示错误动画后重置
      setTimeout(() => { this.resetAll(); }, 1000);
    }
  }

  @Builder
  renderPoint(index: number) {
    const row = Math.floor(index / 3);
    const col = index % 3;
    const isSelected = this.points[row][col];

    Circle()
      .width(56).height(56)
      .fill(isSelected ? '#007AFF' : '#E8E8ED')
      .fillOpacity(isSelected ? 1.0 : 0.7)
      .stroke(isSelected ? '#007AFF' : '#ccc')
      .strokeWidth(isSelected ? 3 : 1)
  }

  build() {
    Column() {
      Text('🔐 手势解锁')
        .fontSize(26).fontWeight(FontWeight.Bold).margin({ bottom: 8 })

      Text('请绘制解锁图案')
        .fontSize(15).fontColor('#888').margin({ bottom: 32 })

      // ─── 九宫格区域 ───
      Grid() {
        ForEach([0, 1, 2], (row: number) => {
          ForEach([0, 1, 2], (col: number) => {
            GridItem() {
              this.renderPoint(row * 3 + col)
            }
          })
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr')
      .width(280).height(280)
      .gesture(
        PanGesture({ distance: 5 })
          .onActionUpdate((event: GestureEvent) => {
            // 检测当前触摸位置落在哪个点上
            const idx = this.hitTest(event.fingerInfo[0]?.x || 0,
                                      event.fingerInfo[0]?.y || 0);
            if (idx >= 0 && !this.selected.includes(idx)) {
              const row = Math.floor(idx / 3);
              const col = idx % 3;
              this.points[row][col] = true;
              this.selected.push(idx);
              // 震动反馈
              vibrator.startVibration({
                type: 'time',
                duration: 15
              }, (err) => {});
            }
          })
          .onActionEnd(() => {
            if (this.selected.length > 0) {
              this.verify();
            }
          })
      )

      Text(this.result)
        .fontSize(20).fontWeight(FontWeight.Bold)
        .fontColor(this.resultColor)
        .margin({ top: 24 })

      Text(`已选择 ${this.selected.length} 个点`)
        .fontSize(14).fontColor('#999').margin({ top: 8 })

      if (this.selected.length > 0) {
        Button('↺ 重新绘制')
          .fontSize(14).backgroundColor('#F0F0F0').fontColor('#333')
          .margin({ top: 8 })
          .onClick(() => { this.resetAll(); })
      }

      Divider().margin(24)

      // ─── 密码设置提示 ───
      Text('💡 当前预设密码:对角线(左上→中→右下)')
        .fontSize(13).fontColor('#999').textAlign(TextAlign.Center)
      Text('在实际项目中,密码可让用户自由设置并持久化存储')
        .fontSize(12).fontColor('#bbb').textAlign(TextAlign.Center)
        .margin({ top: 4 })
    }
    .width('100%').height('100%').padding(20)
  }
}

📊 GestureEvent 事件对象解析

每个手势回调都会收到 GestureEvent 对象,包含丰富的触控信息:

属性 类型 说明 适用手势
fingerInfo FingerInfo\[\] 每根手指的位置、ID 全部
offsetX number 手势在 X 轴的偏移量 PanGesture
offsetY number 手势在 Y 轴的偏移量 PanGesture
velocityX number X 轴速度 (px/s) PanGesture, SwipeGesture
velocityY number Y 轴速度 (px/s) PanGesture, SwipeGesture
scale number 缩放比例(1.0 为基准) PinchGesture
angle number 旋转角度(度) RotationGesture
timestamp number 事件时间戳 (ms) 全部

🎯 手势冲突处理(关键难点)

当多个手势作用在同一组件时,必须明确「谁优先」。ArkUI 提供 GestureGroup 控制手势关系:

并行模式(Parallel)

typescript 复制代码
// 同时识别 Tap 和 Pan
// 场景:一个按钮同时支持「点击跳转」和「拖拽移动」
GestureGroup(GestureMode.Parallel,
  TapGesture({ count: 1 }),
  PanGesture({ distance: 10 })
)

// ⚠️ 注意:并行模式下 Tap 和 Pan 都可能触发
// 如果点击伴随轻微移动,可能 Pan 先触发,Tap 被吞掉

互斥模式(Exclusive)

typescript 复制代码
// 只识别其中一个手势
// 场景:左滑删除 vs 上下滑动列表
GestureGroup(GestureMode.Exclusive,
  SwipeGesture({ direction: SwipeDirection.Left }),
  PanGesture({ direction: PanDirection.Vertical })  // 垂直滑动
)
// 左滑触发删除手势,垂直滑动触发滚动 → 互不干扰

顺序模式(Sequence)

typescript 复制代码
// 先长按才能拖拽
// 场景:微信聊天列表「长按 → 开始拖拽排序」
GestureGroup(GestureMode.Sequence,
  LongPressGesture({ duration: 500 }),
  PanGesture({ distance: 10 })
)
// 只有长按成功后,拖拽手势才开始识别

优先级手势(priorityGesture)

typescript 复制代码
// 子组件的 priorityGesture 优先于父组件的普通 gesture
Column() {
  // 子组件:可拖拽
  Column()
    .width(60).height(60).backgroundColor('#FF9500').borderRadius(12)
    .priorityGesture(       // ← 优先级手势,优先于父组件
      PanGesture({ distance: 5 })
        .onActionUpdate((e) => { /* 拖拽逻辑 */ })
    )
}
.gesture(
  PanGesture()  // 父组件的 PanGesture
    .onActionUpdate((e) => { /* 父容器的滑动逻辑 */ })
)
// 拖拽方块时,子组件的 priorityGesture 优先响应
// 拖拽空白区域时,父组件的 gesture 响应

⚠️ 避坑指南(必看)

现象 原因 正确做法
TapGesture 不触发 点击没反应 和 PanGesture 在同级且 Pan 先抢占了 GestureMode.Exclusivedistance 参数 > 5
PanGesture 需要拖很远才触发 拖动手感"黏" distance 默认 10px,设太大 改为 distance: 3 提升灵敏度
PinchGesture 在模拟器无效 没反应 模拟器不支持多指 必须真机调试
手势穿透到父组件 拖动子组件时父容器也在动 子组件没用 priorityGesture priorityGesture 替代 gesture
SwipeGesture 和列表滑动冲突 左滑删除触发时列表也在滚动 方向没限制 SwipeDirection.Left + GestureMode.Exclusive
onActionEnd/onActionCancel 不执行 状态没复位 手势中断时没有清理 两个回调都绑定,onActionCancel 中也要重置状态
长按手势误触 用户只是想点击却触发了长按 duration 设太短 至少 500ms,推荐 800ms
旋转后缩放比例错误 图片既旋转又缩放时乱了 event.scale 是相对值不是绝对值 记住手势开始时的值 = savedValue * eventDelta
多次绑定的手势后绑定者覆盖前者 只有一个手势生效 同一个 .gesture() 多次调用会覆盖 GestureGroup 组合

🔥 最佳实践(10 条)

  1. 震动反馈加分 :手势触发时配合 @ohos.vibrator.startVibration() 增加触感,提升 App 品质感
  2. 视觉反馈先行:手势开始立即改变组件外观(放大/变色/阴影),让用户感知「系统已收到指令」
  3. 弹簧曲线收尾 :手势结束后用 Curve.SpringMotion 做回弹动画,比硬停止自然 10 倍
  4. **distance 设 510px**:太小容易误触、太大手感黏腻,510 是黄金区间
  5. 真机调试多指手势:Pinch/Rotation 必须真机,模拟器不支持
  6. 保存起始值 :Pinch/Rotation 回调中 event.scaleevent.angle 是增量,必须 + 起始值
  7. 手势互斥保平安 :同一个组件上绑多个手势时务必用 GestureGroup 明确关系
  8. onActionCancel 也要清理状态:手势可能被来电/通知中断,中断后要重置 UI
  9. 灵敏度可配置 :把 distance/duration 提取成常量,方便后续调优
  10. 下沉手势到子组件 :子组件需要手势时用 priorityGesture,避免被父组件吞掉

📚 手势 API 速查表

typescript 复制代码
// ─── 全部手势构造函数参数一览 ───

TapGesture({ count?: number })              // count: 点击次数 1|2
LongPressGesture({ duration?: number })     // duration: 长按时长(ms)
PanGesture({ fingers?: number, direction?: PanDirection, distance?: number })
PinchGesture({ fingers?: number })          // fingers: 最少手指数
RotationGesture({ fingers?: number, angle?: number })  // angle: 最小触发角度
SwipeGesture({ direction?: SwipeDirection, speed?: number })  // speed: 最小速度

🚀 扩展挑战

学有余力的同学可以挑战以下进阶场景:

  1. 拖拽排序列表:LongPress + Pan Gesture 实现类微信「长按拖动排序」
  2. 画板 App:PanGesture 记录轨迹 + Canvas 绘制,实现手指画画
  3. 三指截图:检测 3 指同时 Pan 触发截图(类似 MIUI)
  4. 手势自定义 :通过原始触控事件 onTouch 实现自定义手势识别(画圈、画 L)
  5. AR 交互:手势 + 传感器融合,实现 AR 场景中的物体拖拽缩放
  6. 多指快捷键:4 指上滑 = 返回桌面、3 指左滑 = 截屏

📄 👉 点此查看手势专题完整截图网页


官方文档: HarmonyOS 应用开发文档

相关推荐
Davina_yu2 小时前
网络请求基础:使用http模块发起GET/POST请求(12)
harmonyos·鸿蒙·鸿蒙系统
枫叶丹42 小时前
【HarmonyOS 6.0】MDM Kit 新增限制策略深度解析:短信、蜂窝数据、飞行模式、通知消息与 NFC 管控
开发语言·华为·harmonyos
辰海Coding2 小时前
MiniSpring框架学习笔记-JDBC 访问框架:如何抽取 JDBC 模板并隔离数据库?
java·数据库·笔记·学习·spring
十月的皮皮2 小时前
C语言学习笔记20260609-字符串反转两种实现方法
c语言·笔记·学习
G_dou_2 小时前
Flutter三方库适配OpenHarmony【mood_journal】心情日记项目完整实战
flutter·harmonyos
咸鱼翻身小阿橙2 小时前
C# WinForms 控件学习项目
开发语言·学习·c#
段一凡-华北理工大学2 小时前
工业领域的Hadoop架构学习~系列文章22:Hadoop生态展望 - 面向未来的技术演进
大数据·人工智能·hadoop·分布式·学习·架构·高炉炼铁
小雨下雨的雨2 小时前
数独算法与求解器鸿蒙PC Electron框架完成深度解析
javascript·人工智能·算法·游戏·华为·electron·鸿蒙系统
YangYang9YangYan2 小时前
学数据分析对学习编程的价值
学习·数据挖掘·数据分析