新特性:多模态交互 —— 让应用支持「手势」「语音」与「触感」反馈

文章目录
前言
移动端交互早已不局限于「看屏幕 + 点按钮」。用户期望:
- 手势:左滑删除、长按预览、拖拽排序——操作应自然、跟手;
- 语音:支付确认、取消订单——解放双手,尤其在驾驶、厨电等场景;
- 触感:点击、成功、失败各有不同的震动反馈——操作「落地」有确认感。
三者组合,称为 多模态交互(Multimodal Interaction):同一操作在视觉、触觉、听觉(或语音)通道同时给出反馈,降低误操作焦虑,提升可用性。
HarmonyOS 在 ArkUI 手势体系之上,提供了 @kit.SensorServiceKit 的 vibrator 模块,通过 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.json5 的 requestPermissions 中添加:
{
"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_SHARP、EFFECT_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)
}
好处:
- UI 层只关心「这次是什么语义」,不关心 effectId;
- 全局开关
hapticEnabled一处控制; - 错误统一 catch,写日志而不 crash。
buildHapticEffect 映射关系:
TAP → EFFECT_SOFT
SUCCESS → EFFECT_NOTICE_SUCCESS
WARNING → EFFECT_NOTICE_WARNING
ERROR → EFFECT_NOTICE_FAILURE
LONG_PRESS → EFFECT_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 实时更新 offsetX;onActionEnd 判断阈值:
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 用户旅程
- 点击商品行 → 选中/取消,轻触震 + 蓝色边框;
- 左滑某行 → 删除,警告震 + 行消失;
- 右滑某行 → 收藏,成功震 + 绿色底层;
- 长按某行 → 预览价格,强震 + 状态文案;
- 点击 「语音支付」 或说「确认支付」→ 成功震 + 支付文案;
- 未选中商品就支付 → 失败震 + 错误提示。
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,code801表示能力不支持,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 与手势事件结合的价值。
更多推荐
所有评论(0)