请添加图片描述

前言

移动端交互早已不局限于「看屏幕 + 点按钮」。用户期望:

  • 手势:左滑删除、长按预览、拖拽排序——操作应自然、跟手;
  • 语音:支付确认、取消订单——解放双手,尤其在驾驶、厨电等场景;
  • 触感:点击、成功、失败各有不同的震动反馈——操作「落地」有确认感。

三者组合,称为 多模态交互(Multimodal Interaction):同一操作在视觉、触觉、听觉(或语音)通道同时给出反馈,降低误操作焦虑,提升可用性。

HarmonyOS 在 ArkUI 手势体系之上,提供了 @kit.SensorServiceKitvibrator 模块,通过 startVibration 触发马达震动。本文结合项目 Demo,从权限配置、震动 API、手势绑定、语音指令流程到综合购物车场景,做一次完整实战解析。


一、为什么需要触感反馈?

1.1 纯视觉反馈的局限

只有颜色变化或 Toast 提示时:

  • 用户快速连点时,不确定「哪一次点到了」;
  • 滑动删除过阈值时,缺少「已触发」的物理确认;
  • 支付成功若只有文字,缺乏「完成感」。

苹果 Human Interface Guidelines 将 Haptic Feedback 列为重要交互层;Android 也有 Vibrator / HapticFeedbackConstants。HarmonyOS 通过 vibrator.startVibration 提供系统级能力。

1.2 多模态反馈矩阵

Demo 中的设计原则:

操作类型 视觉 触感 语义
点击选中 边框变蓝 + ✓ EFFECT_SOFT 轻触确认
长按预览 状态文案 EFFECT_HARD 强反馈 · 二级操作
左滑删除 行移除 EFFECT_NOTICE_WARNING 警告 · 不可逆
右滑收藏 背景变绿 EFFECT_NOTICE_SUCCESS 成功 · 正向
支付成功 金额文案 EFFECT_NOTICE_SUCCESS 完成
支付失败 错误提示 EFFECT_NOTICE_FAILURE 失败

核心思想:不同语义用不同震动预设,用户形成肌肉记忆后,甚至不必看屏幕也能感知操作结果。


二、vibrator API 入门

2.1 权限声明

震动需要 ohos.permission.VIBRATE。在 module.json5requestPermissions 中添加:

{
  "name": "ohos.permission.VIBRATE"
}

该权限为 system_grant 类,安装时自动授予,无需用户弹窗(与普通危险权限不同)。

2.2 导入与基本调用

import { vibrator } from '@kit.SensorServiceKit'
import { BusinessError } from '@kit.BasicServicesKit'

const touchAttr: vibrator.VibrateAttribute = { usage: 'touch' }
const feedbackAttr: vibrator.VibrateAttribute = { usage: 'physicalFeedback' }

async function vibrateSoft() {
  const effect: vibrator.VibratePreset = {
    type: 'preset',
    effectId: vibrator.HapticFeedback.EFFECT_SOFT,
    count: 1
  }
  await vibrator.startVibration(effect, touchAttr)
}

2.3 VibrateEffect 三种类型

type 结构 用途
'time' { type: 'time', duration: number } 固定毫秒震动
'preset' { type: 'preset', effectId, count } 系统预设效果
'pattern' { type: 'pattern', pattern } 自定义节奏(Transient / Continuous)

2.4 HapticFeedback 预设枚举

Demo 用到的预设:

vibrator.HapticFeedback.EFFECT_SOFT           // 轻柔 · 点击
vibrator.HapticFeedback.EFFECT_HARD           // 硬感 · 长按
vibrator.HapticFeedback.EFFECT_NOTICE_SUCCESS // 成功
vibrator.HapticFeedback.EFFECT_NOTICE_WARNING // 警告
vibrator.HapticFeedback.EFFECT_NOTICE_FAILURE // 失败

还可使用 EFFECT_SHARPEFFECT_CLOCK_TIMER 等,可用 isSupportEffectSync(effectId) 检测设备是否支持。

2.5 VibrateAttribute.usage

usage 含义 Demo 使用场景
'touch' 触摸反馈 点击、滑动短脉冲
'physicalFeedback' 物理/通知反馈 成功、失败、警告

区分 usage 有助于系统调度马达资源,避免与来电震动等冲突。

2.6 停止震动

await vibrator.stopVibration()

长按自定义 pattern 或循环震动时需要主动停止;预设单次震动一般自动结束。


三、封装 triggerHaptic:语义化触感层

Demo 将震动逻辑收敛为 HapticKind 枚举 + triggerHaptic(kind)

enum HapticKind {
  TAP, SUCCESS, WARNING, ERROR, SWIPE, LONG_PRESS
}

private async triggerHaptic(kind: HapticKind): Promise<void> {
  if (!this.hapticEnabled) return  // 用户可关闭触感

  const effect = this.buildHapticEffect(kind)
  const attr = (kind === HapticKind.TAP || kind === HapticKind.SWIPE)
    ? TAP_ATTR : FEEDBACK_ATTR

  await vibrator.startVibration(effect, attr)
}

好处

  1. UI 层只关心「这次是什么语义」,不关心 effectId;
  2. 全局开关 hapticEnabled 一处控制;
  3. 错误统一 catch,写日志而不 crash。

buildHapticEffect 映射关系:

TAPEFFECT_SOFT
SUCCESSEFFECT_NOTICE_SUCCESS
WARNINGEFFECT_NOTICE_WARNING
ERROREFFECT_NOTICE_FAILURE
LONG_PRESSEFFECT_HARD
SWIPE      → VibrateTime 40ms

四、手势体系:Tap / LongPress / Pan

4.1 三种基础手势

手势 典型场景 Demo 绑定
TapGesture 单击选中 购物车行 toggle selected
LongPressGesture 长按预览/菜单 500ms 触发详情
PanGesture 拖拽/滑动 水平 offsetX 左滑删/右滑收藏

4.2 GestureGroup 互斥模式

同一组件上挂多种手势时,用 GestureGroup(GestureMode.Exclusive, ...) 避免冲突:

.gesture(
  GestureGroup(GestureMode.Exclusive,
    PanGesture({ direction: PanDirection.Horizontal, distance: 5 })
      .onActionUpdate((event) => {
        this.updateCartItem(item.id, { offsetX: event.offsetX })
      })
      .onActionEnd(() => {
        this.onCartPanEnd(this.findCartItem(item.id))
      }),
    LongPressGesture({ repeat: false, duration: 500 })
      .onAction(() => { this.onCartLongPress(item) }),
    TapGesture({ count: 1 })
      .onAction(() => { this.onCartTap(item) })
  )
)

Exclusive 表示同一时刻只识别一种手势:轻点不会误触发 Pan,长按不会和 Tap 打架。

4.3 左滑删除:Stack + offset 模式

购物车每一行是经典 「底层操作 + 上层卡片」 结构:

┌─────────────────────────────────┐
│  [删除]              [收藏]      │  ← 底层 Row(红/绿背景)
│  ┌───────────────────────────┐  │
│  │  鸿蒙开发指南    ¥89   ✓  │  │  ← 上层卡片 .offset({ x: offsetX })
│  └───────────────────────────┘  │
└─────────────────────────────────┘

onActionUpdate 实时更新 offsetXonActionEnd 判断阈值:

const SWIPE_THRESHOLD = 72

onCartPanEnd(item: CartItem) {
  if (item.offsetX <= -SWIPE_THRESHOLD) {
    this.triggerHaptic(HapticKind.WARNING)
    this.removeCartItem(item.id)      // 左滑删除
    return
  }
  if (item.offsetX >= SWIPE_THRESHOLD) {
    this.triggerHaptic(HapticKind.SUCCESS)
    this.updateCartItem(item.id, { favorited: true, offsetX: 0 })
    return
  }
  this.updateCartItem(item.id, { offsetX: 0 })  // 未过阈值,回弹
}

4.4 触感触发时机:过阈值再震

反模式:在 onActionUpdate 里每次 offset 变化都 startVibration——拖动过程中马达狂震,体验极差。

正模式:仅在 onActionEnd 判定「操作已 commit」时震一次。点击、长按同理,在 onAction 回调里触发。


五、语音交互:从 ASR 到指令执行

5.1 生产环境路径

完整语音链路:

麦克风 → ASR 识别 → NLU 意图解析 → 业务 Handler → TTS 播报 + 触感

HarmonyOS 可接入 Core Speech Kit(语音识别 / 语音合成)。Demo 为降低依赖,用 模拟 ASR 演示流程。

5.2 指令表设计

const VOICE_COMMANDS = [
  { id: 'pay',     phrase: '确认支付',   hint: '成功反馈 · 下单' },
  { id: 'cancel',  phrase: '取消订单',   hint: '警告反馈 · 撤销' },
  { id: 'refresh', phrase: '刷新列表',   hint: '轻触反馈 · 重载' },
  { id: 'help',    phrase: '我需要帮助', hint: '语音播报 · 提示' }
]

每条指令映射:识别文案 → 业务动作 → 触感类型

5.3 executeVoiceCommand 核心逻辑

executeVoiceCommand(command: VoiceCommand) {
  this.voiceTranscript = `已识别:「${command.phrase}`

  if (command.id === 'pay') {
    if (this.totalPrice <= 0) {
      this.triggerHaptic(HapticKind.ERROR)   // 失败震
      return
    }
    this.triggerHaptic(HapticKind.SUCCESS)  // 成功震
    this.statusText = `支付成功 · ¥${this.totalPrice}`
    return
  }
  // cancel → WARNING, refresh → TAP ...
}

多模态闭环:语音识别结果展示(视觉)+ 状态文案(视觉)+ 对应预设震动(触觉)。后续可再加 TTS 播报「支付成功」(听觉)。

5.4 「按住说话」交互

语音实验室场景:

LongPressGesture({ repeat: false, duration: 300 })
  .onAction(() => {
    this.isListening = true
    this.voiceTranscript = '正在聆听…'
    this.triggerHaptic(HapticKind.TAP)  // 开始录音轻触
  })
  .onActionEnd(() => {
    this.isListening = false
    this.simulateVoiceInput()           // 模拟 ASR 返回
  })

UI 上麦克风圆形按钮变红表示聆听中,松手后随机/匹配一条指令并执行——完整走通 按住说话 → 识别 → 反馈 的产品形态。


六、综合场景:多模态购物车

Demo 默认「综合场景」将三者合一:

6.1 用户旅程

  1. 点击商品行 → 选中/取消,轻触震 + 蓝色边框;
  2. 左滑某行 → 删除,警告震 + 行消失;
  3. 右滑某行 → 收藏,成功震 + 绿色底层;
  4. 长按某行 → 预览价格,强震 + 状态文案;
  5. 点击 「语音支付」 或说「确认支付」→ 成功震 + 支付文案;
  6. 未选中商品就支付 → 失败震 + 错误提示。

6.2 触感开关

顶部 Toggle 控制 hapticEnabled

  • 开启:所有操作带震动;
  • 关闭:仅视觉 + 日志,方便对比和 accessibility 场景(部分用户禁用震动)。

6.3 交互日志

feedbackLogs 记录每条操作的通道(手势/语音/触感)与时间,便于调试:

11:05:32  [手势]  Tap · 鸿蒙开发指南
11:05:35  [触感]  EFFECT_SOFT · touch
11:05:40  [手势]  SwipeLeft 删除 · ArkTS 实战手册
11:05:40  [触感]  EFFECT_NOTICE_WARNING
11:05:45  [语音]  确认支付 ¥89
11:05:45  [触感]  EFFECT_NOTICE_SUCCESS

七、四个实验室场景对照

场景 重点 适合学习
综合场景 手势+语音+触感联动 产品级交互设计
触感实验室 六种预设 + 时长滑块 vibrator API
手势实验室 Exclusive 手势组 Tap/LongPress/Pan
语音实验室 按住说话 + 指令列表 ASR 流程占位

八、踩坑清单

8.1 震动无反应

  • 检查 module.json5 是否声明 VIBRATE 权限;
  • 模拟器可能不支持马达,需真机验证;
  • catch BusinessError,code 801 表示能力不支持,201 表示权限拒绝。

8.2 拖动时震动过于频繁

  • 不要在 onActionUpdate 里触发震动;
  • 仅在 onActionEnd 或操作 commit 时震一次。

8.3 手势冲突:点击变成滑动

  • 使用 GestureGroup(Exclusive)
  • Pan 设置合理 distance(如 5vp),避免轻微抖动识别为 Pan。

8.4 所有操作同一种震动

  • 用户无法区分成功/失败;
  • 按语义映射不同 HapticFeedback 预设。

8.5 usage 混用

  • 点击类用 touch,通知/结果类用 physicalFeedback
  • 与系统场景保持一致,减少被系统降权或打断的概率。

8.6 语音只做了 UI 没有闭环

  • 识别结果要有业务副作用(改状态、跳转、提交);
  • 必须配合触感或 TTS,否则用户不确定是否生效。

8.7 Partial 更新 @State 数组

ArkTS 中更新 cartItems 某一项时,需 immutable 替换整个数组(Demo 的 updateCartItem 模式),否则 UI 可能不刷新。


九、进阶:Pattern 自定义节奏

除 preset 外,可用 VibratorPatternBuilder 构造复杂节奏(项目 HapticEngine.ets 中有完整示例):

const builder = new vibrator.VibratorPatternBuilder()
builder.addTransientEvent(0, { intensity: 90, frequency: 120 })
builder.addTransientEvent(90, { intensity: 70, frequency: 100 })
const pattern = builder.build()

const effect: vibrator.VibrateFromPattern = {
  type: 'pattern',
  pattern: pattern
}
await vibrator.startVibration(effect, feedbackAttr)

适用:心跳提醒、双击确认、游戏技能反馈等需要 节奏感 的场景。


十、无障碍与产品策略

  • 系统设置:用户可在系统层关闭触感,App 内 Toggle 是补充而非替代;
  • 可配置:设置页提供「触感强度/开关」是成熟 App 标配;
  • 不要滥用:列表滚动、hover 不需要震;关键 commit 点才震;
  • 与 Sound 配合:静音模式下触感更重要;响铃模式下可减弱。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

十一、总结

模态 核心 API Demo 要点
触感 vibrator.startVibration 语义化 HapticKind + usage 区分
手势 Tap / LongPress / Pan + GestureGroup 左滑删/右滑收藏,过阈值再震
语音 指令表 + executeVoiceCommand 模拟 ASR,成功/失败不同震

一句话:多模态不是把三种能力堆在一起,而是 同一操作、多通道一致反馈——眼看到状态变化,手感到确认震动,耳(或屏)听到/看到语义结果。

把 Demo 跑在真机上:选中商品时轻震一下,左滑删除时警告震,语音支付成功时 success 震——这种「操作落地感」,就是 vibrator 与手势事件结合的价值。

Logo

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

更多推荐