ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Flutter 触觉反馈插件 Gaimon三方库鸿蒙端教程

Flutter 触觉反馈插件 Gaimon三方库鸿蒙端教程 AI工具 码道 推荐 https://developer.huaweicloud.com/codeartsco.html?sourcedmzntgwatomgit1sourceaddmzntgwatomgiths欢迎加入 CPF-Flutter 鸿蒙社区 https://atomgit.com/CPF-Flutter本文配套仓库 https://atomgit.com/oh-flutter/holding-harmony摘要本文介绍 Flutter 触觉反馈插件 Gaimon 在 OpenHarmony 平台上的适配与使用。文章从 Gaimon 的核心功能出发讲解如何使用 FVM 管理鸿蒙 Flutter SDK、创建 OpenHarmony 平台目录、添加 Gaimon 依赖并配置震动权限最后通过 DevEco Studio 在真实设备上运行验证帮助开发者快速将触觉反馈能力接入鸿蒙应用。关键词Flutter、Gaimon、OpenHarmony、触觉反馈、鸿蒙适配前言在 Flutter 应用开发中按钮点击、表单提交、操作成功、风险提醒和错误提示除了通过文字、颜色或弹窗向用户传递信息之外还可以通过触觉反馈让用户更快感知当前状态。合理的触觉反馈能够让操作结果更加明确也能提升移动应用的交互体验。尤其是在用户没有持续注视屏幕或者应用需要快速反馈时震动是一种直接而有效的提示方式。Gaimon 是一个用于实现触觉反馈的 Flutter 三方库。它将不同平台上的触觉能力封装成统一的 Dart API开发者只需要在 Flutter 代码中调用 Gaimon.selection()、Gaimon.success() 或 Gaimon.error() 等方法就可以为应用增加对应的反馈效果不需要分别编写 Android、iOS 和 OpenHarmony 的原生代码。对于需要同时支持多个移动平台的项目来说这种统一的调用方式可以减少平台判断和重复开发让业务代码更加清晰。除了常见的预置反馈之外Gaimon 还支持不同强度的触觉效果、成功/警告/错误等状态反馈以及基于 AHAP 文件和自定义波形的复杂触觉模式。开发者可以根据具体业务选择合适的效果列表选择可以使用轻微的 Selection 反馈提交成功可以使用 Success 反馈危险操作可以使用 Warning 反馈提交失败可以使用 Error 反馈如果预置效果不能满足需求还可以通过 .ahap 文件或 timings、amplitudes 参数构造更有节奏感的自定义震动。不过原始 Gaimon 插件主要面向 Android 和 iOS 平台OpenHarmony 项目无法直接使用。为了让 Flutter 应用能够在鸿蒙设备上复用这套触觉反馈能力本文对 Gaimon 进行了 OpenHarmony 平台适配在尽量保持原有 Dart 公共 API 不变的前提下补充鸿蒙侧的插件实现并完成设备能力检测、预置反馈、自定义模式、波形播放和停止播放等功能的验证。本文将从一个实际的 Flutter 示例项目开始介绍如何使用 FVM 管理适配鸿蒙的 Flutter SDK创建 OpenHarmony 平台目录添加 Gaimon 三方库依赖配置鸿蒙震动权限并通过 DevEco Studio 在真实设备上运行验证。读者可以按照文章步骤完成环境配置也可以直接参考完整演示页面将 Gaimon 接入到自己的按钮、表单、列表和业务状态处理中。一、Gaimon 鸿蒙适配效果展示1.1 能力概览Gaimon 是一个 Flutter 触觉反馈插件原始版本主要支持 Android 和 iOS。经过 OpenHarmony 适配后Flutter 应用可以继续使用原有 Dart API在鸿蒙设备上完成触觉反馈。本文示例按 OpenHarmony API 12 及以上环境进行验证API 12 以下版本不纳入本文讨论范围。支持以下功能设备触觉能力检测通过 Gaimon.canSupportsHaptic 判断当前设备是否支持触觉反馈。预置触觉反馈提供 Selection、Light、Medium、Heavy、Rigid 和 Soft 六种基础效果。状态反馈提供 Success、Warning 和 Error 三种业务状态效果。AHAP 自定义触觉模式通过 .ahap 文件描述有节奏的触觉效果。自定义震动波形通过 timings、amplitudes 和 repeat 组合播放波形。停止当前震动通过 Gaimon.stop() 取消正在播放的反馈。本文介绍如何使用 Flutter 插件 Gaimon 。1.2 三方平台支持矩阵对比下表横向对比 Gaimon 在 Android、iOS 和 OpenHarmony 三个平台上的功能支持情况便于开发者评估适配后的能力边界功能AndroidiOSOpenHarmony差异说明设备能力检测支持支持支持三个平台均可通过Gaimon.canSupportsHaptic检测设备是否支持触觉反馈。预置反馈支持支持支持Android 与 iOS 原生支持selection、light、medium、heavy、rigid、soft六种反馈OpenHarmony 端经适配后同样支持。状态反馈支持支持支持三个平台均支持success、warning、error三种业务状态反馈。AHAP 自定义模式支持支持部分支持Android 与 iOS 可完整解析并播放 AHAP 文件OpenHarmony 端通过Gaimon.patternFromData播放复杂节奏的还原程度以当前适配实现为准。自定义波形支持支持部分支持三个平台均支持timings、amplitudes参数构造波形OpenHarmony 端按适配后的分段逻辑播放repeat的具体播放次数以当前适配实现为准。停止播放支持支持支持三个平台均可通过Gaimon.stop()取消正在播放的触觉反馈。二、使用FVM创建项目2.1 用FVM切换SDK使用fvm flutter create gaimon_demo新建一个名为gaimon_demo的文件夹。如果使用的是 OHOS Flutter SDK项目还应包含或能够生成ohos/目录但是我这里没有因为没使用适配了鸿蒙的 Flutter SDK。cd /d E:\gaimon_demo进入 gaimon_demo 这个目录 fvm use 3.41.10-ohos-1.0.1 --force使用 fvm 切换 3.41.10 版本的 Flutter SDK flutter version查看 Flutter SDK 的版本 fvm flutter create --platformsohos .创建 ohos 鸿蒙平台执行完看到已经创建了鸿蒙的目录。三、添加gaimon的依赖3.1 配置pubspec.yaml依赖我这里使用的是 VSCode使用 Android Studio 也是可以的。在pubspec.yaml添加下方依赖gaimon: git: url: https://atomgit.com/Deng666/fluttertpc_gaimon.git ref: 1.5.0-ohos-1.0.0使用fvm flutter pub get同步依赖3.2 完整演示页面在 lib/main.dart 中添加这一行代码import package:gaimon/gaimon.dart;把main.dart的计数器案例删除了代码如下import package:flutter/material.dart; import package:gaimon/gaimon.dart; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); override Widget build(BuildContext context) { return const MaterialApp( home: Scaffold(), ); } }代码讲解设备触觉能力检测。应用启动后可以通过 Gaimon.canSupportsHaptic 检测当前设备是否支持触觉反馈并根据检测结果决定是否启用页面中的播放按钮。这样可以避免在不支持触觉反馈的设备上直接调用接口也能让使用者清楚地看到当前插件是否已经完成注册。预置触觉反馈。插件提供 selection、light、medium、heavy、rigid 和 soft 六种基础反馈分别对应选择操作、轻度反馈、中度反馈、重度反馈、刚性反馈和柔和反馈。开发者可以根据交互的重要程度选择合适的反馈例如列表选择使用 selection普通操作使用 light 或 medium重要确认使用 heavy 或 rigid。业务状态反馈。插件还提供 success、warning 和 error 三种状态反馈适合放在表单提交、保存数据、删除确认、风险提醒和操作失败等业务流程中。这样用户不仅能通过页面文字看到操作结果还能通过触觉获得即时反馈尤其适合移动端和鸿蒙设备上的高频交互场景。AHAP 自定义触觉模式。应用可以把 .ahap 文件作为 Flutter 资源加载再通过 Gaimon.patternFromData 播放自定义触觉效果。本示例准备了 Heartbeat、Rumble、Gravel 和 Inflate 四种模式用来展示心跳、持续震动、颗粒感震动和逐渐增强等不同效果。相较于单次预置反馈AHAP 更适合表达有节奏、有变化的复杂触觉图案。手动构造震动波形。开发者也可以直接传入 timings、amplitudes 和 repeat 参数调用 Gaimon.patternFromWaveForm自行组合等待、震动、停顿和再次震动等阶段。本示例包含单次成功波形、双脉冲波形和重复波形便于理解震动时间与强度之间的关系。OpenHarmony 端会按照适配后的分段逻辑播放波形repeat 的具体播放次数以当前适配实现为准。停止当前反馈。通过 Gaimon.stop() 可以取消正在播放的触觉反馈适合页面退出、任务取消或用户主动停止播放等场景。这个接口能够和自定义波形、重复播放配合使用让业务层拥有更完整的播放控制能力。import package:flutter/material.dart; import package:flutter/services.dart; import package:gaimon/gaimon.dart; void main() { runApp(const GaimonDemoApp()); } class GaimonDemoApp extends StatelessWidget { const GaimonDemoApp({super.key}); override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: Gaimon Demo, theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.teal, ), useMaterial3: true, ), home: const GaimonPage(), ); } } class GaimonPage extends StatefulWidget { const GaimonPage({super.key}); override StateGaimonPage createState() _GaimonPageState(); } class _GaimonPageState extends StateGaimonPage { bool? _supported; bool _loading false; String _lastAction 暂无操作; bool get _enabled _supported true !_loading; override void initState() { super.initState(); _checkSupport(); } Futurevoid _checkSupport() async { setState(() { _supported null; _lastAction 正在检测设备是否支持触觉反馈...; }); try { final supported await Gaimon.canSupportsHaptic; if (!mounted) return; setState(() { _supported supported; _lastAction supported ? 设备支持触觉反馈 : 设备不支持触觉反馈; }); } catch (_) { if (!mounted) return; setState(() { _supported false; _lastAction 检测失败请确认插件是否注册; }); } } void _run(String name, VoidCallback action) { if (!_enabled) return; setState(() { _loading true; _lastAction 正在执行$name; }); try { // Gaimon 的这些方法返回 void不需要 await。 action(); if (!mounted) return; setState(() { _loading false; _lastAction $name 执行完成; }); } catch (_) { if (!mounted) return; setState(() { _loading false; _lastAction $name 执行失败; }); } } Futurevoid _playAhap(String name, String asset) async { if (!_enabled) return; setState(() { _loading true; _lastAction 正在播放$name; }); try { final data await rootBundle.loadString(asset); Gaimon.patternFromData(data); if (!mounted) return; setState(() { _loading false; _lastAction $name 播放完成; }); } catch (_) { if (!mounted) return; setState(() { _loading false; _lastAction $name 播放失败; }); } } void _playWaveform( String name, Listint timings, Listint amplitudes, bool repeat, ) { _run( name, () Gaimon.patternFromWaveForm( timings, amplitudes, repeat, ), ); } void _stop() { Gaimon.stop(); setState(() { _loading false; _lastAction 已停止当前震动; }); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text(Gaimon 触觉反馈演示), actions: [ IconButton( tooltip: 重新检测设备能力, onPressed: _loading ? null : _checkSupport, icon: const Icon(Icons.refresh), ), ], ), body: ListView( padding: const EdgeInsets.all(16), children: [ _buildStatusCard(), const SizedBox(height: 20), _buildSectionTitle(一、设备能力检测), _buildInfoCard( Gaimon.canSupportsHaptic, 检测当前设备是否支持触觉反馈。, ), const SizedBox(height: 20), _buildSectionTitle(二、预置触觉反馈), _buildPreset( Selection, 轻微的选择反馈适合列表选择、按钮点击。, Gaimon.selection(), Icons.touch_app, Gaimon.selection, ), _buildPreset( Light, 轻度震动适合轻量级操作提示。, Gaimon.light(), Icons.blur_on, Gaimon.light, ), _buildPreset( Medium, 中等强度震动适合普通操作反馈。, Gaimon.medium(), Icons.radio_button_checked, Gaimon.medium, ), _buildPreset( Heavy, 较强震动适合重要操作或强提醒。, Gaimon.heavy(), Icons.vibration, Gaimon.heavy, ), _buildPreset( Rigid, 刚性感较强的反馈触感更加明确。, Gaimon.rigid(), Icons.crop_square, Gaimon.rigid, ), _buildPreset( Soft, 柔和的反馈适合不希望过于明显的提示。, Gaimon.soft(), Icons.circle_outlined, Gaimon.soft, ), _buildPreset( Success, 成功反馈例如提交成功、保存成功。, Gaimon.success(), Icons.check_circle_outline, Gaimon.success, ), _buildPreset( Warning, 警告反馈例如删除前提醒、风险提示。, Gaimon.warning(), Icons.warning_amber, Gaimon.warning, ), _buildPreset( Error, 错误反馈例如提交失败、操作失败。, Gaimon.error(), Icons.error_outline, Gaimon.error, ), const SizedBox(height: 20), _buildSectionTitle(三、AHAP 自定义模式), _buildAhap( Heartbeat, 心跳效果读取 heartbeats.ahap 播放。, assets/haptics/heartbeats.ahap, ), _buildAhap( Rumble, 持续震动效果读取 rumble.ahap 播放。, assets/haptics/rumble.ahap, ), _buildAhap( Gravel, 颗粒感震动效果读取 gravel.ahap 播放。, assets/haptics/gravel.ahap, ), _buildAhap( Inflate, 逐渐增强的震动效果读取 inflate.ahap 播放。, assets/haptics/inflate.ahap, ), const SizedBox(height: 20), _buildSectionTitle(四、手动构造波形), _buildWaveform( 单次成功波形, 先等待再轻微震动最后强震动。, [0, 55, 55, 53], [0, 178, 0, 255], false, ), _buildWaveform( 双脉冲波形, 连续产生两次震动。, [0, 40, 80, 40, 80], [0, 255, 0, 180, 0], false, ), _buildWaveform( 重复波形, 重复播放当前波形。鸿蒙适配中会播放有限次数。, [0, 35, 45, 35], [0, 200, 0, 160], true, ), const SizedBox(height: 20), Card( child: ListTile( leading: const Icon(Icons.stop_circle_outlined), title: const Text(停止当前震动), subtitle: const Text(调用 Gaimon.stop() 停止正在播放的反馈。), trailing: FilledButton( onPressed: _supported true ? _stop : null, child: const Text(停止), ), ), ), ], ), ); } Widget _buildStatusCard() { final checking _supported null; final supported _supported true; final title checking ? 正在检测设备能力 : supported ? 设备支持触觉反馈 : 设备不支持触觉反馈; return Card( color: supported ? Colors.green.shade50 : null, child: ListTile( leading: Icon( checking ? Icons.sync : supported ? Icons.check_circle : Icons.info_outline, color: supported ? Colors.green : null, ), title: Text(title), subtitle: Text(最近操作$_lastAction), ), ); } Widget _buildSectionTitle(String title) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Text( title, style: const TextStyle( fontSize: 19, fontWeight: FontWeight.bold, ), ), ); } Widget _buildInfoCard(String api, String description) { return Card( child: ListTile( leading: const Icon(Icons.info_outline), title: Text(api), subtitle: Text(description), ), ); } Widget _buildPreset( String name, String description, String api, IconData icon, VoidCallback action, ) { return Card( child: ListTile( leading: Icon(icon), title: Text(name), subtitle: Text($description\n$api), isThreeLine: true, trailing: FilledButton( onPressed: _enabled ? () _run(name, action) : null, child: const Text(播放), ), ), ); } Widget _buildAhap( String name, String description, String asset, ) { return Card( child: ListTile( leading: const Icon(Icons.audio_file), title: Text(name), subtitle: Text($description\npatternFromData()), isThreeLine: true, trailing: FilledButton( onPressed: _enabled ? () _playAhap(name, asset) : null, child: const Text(播放), ), ), ); } Widget _buildWaveform( String name, String description, Listint timings, Listint amplitudes, bool repeat, ) { return Card( child: ListTile( leading: const Icon(Icons.waves), title: Text(name), subtitle: Text( $description\n timings: $timings\n amplitudes: $amplitudes\n repeat: $repeat, ), isThreeLine: true, trailing: FilledButton( onPressed: _enabled ? () _playWaveform( name, timings, amplitudes, repeat, ) : null, child: const Text(播放), ), ), ); } }本文的示例页面会把以上能力集中到一个界面中每个按钮都对应一个实际的 Gaimon API。用户可以先查看设备能力检测结果再依次体验预置反馈、AHAP 模式和手动波形最后使用停止按钮结束当前反馈。这样既能直观看到适配后的运行效果也能作为后续接入真实业务的参考页面。四、运行项目4.1 配置权限使用 DevEco Studio 打开我们的gaimon_demo这个项目选择 ohos 目录并且在ohos/entry/src/main/module.json5加上震动权限点击右上角的运行。Gaimon 的触觉反馈核心权限是 ohos.permission.VIBRATE网络权限不是触觉反馈本身必需的权限。requestPermissions: [ {name: ohos.permission.INTERNET}, { name: ohos.permission.VIBRATE, reason: $string:vibrate_reason, usedScene: { abilities: [EntryAbility], when: inuse } } ]4.2 真机运行与效果验证运行出来的效果如下五、适配原理与踩坑记录本次适配的目标不是重新设计一套鸿蒙专用 API而是在尽量保持 Gaimon 原有 Dart 调用方式不变的前提下补充 OpenHarmony 原生实现。业务层仍然只需要调用 Gaimon.selection()、Gaimon.success()、Gaimon.patternFromData() 等方法平台差异由插件内部处理。适配原理。Dart 层通过名为 gaimon 的 MethodChannel 与原生侧通信。在 lib/gaimon.dart 中OpenHarmony 会走原生触觉实现避免把不同强度的反馈都降级成 Flutter 通用的 HapticFeedback。鸿蒙侧的实现位于 ohos/src/main/ets/components/plugin/GaimonPlugin.ets通过 kit.SensorServiceKit 的 vibrator 调用系统震动能力并对预置反馈、状态反馈、波形播放和停止操作进行分发。API 12 的实现方式。** API 12 不使用 API 18 才提供的 VibratorPatternBuilder否则项目在最低 API 12 的兼容配置下会产生编译或运行兼容问题。因此适配代码采用基础 preset/time 接口将波形拆成多个时间片通过定时器依次开始和停止每一段震动。AHAP 文件也不是在鸿蒙端直接交给 iOS 的 Core Haptics 播放而是先由 Dart 层的 ahapToWaveform 转换为 timings 和 amplitudes再交给鸿蒙侧分段执行。这次适配中实际遇到的注意点- API 12 不能依赖 API 18 的高级波形构造接口所以最终采用基础接口加定时分段的兼容方案。这会让复杂 AHAP 图案变成近似效果锐度、攻击、衰减和音频事件不能与 iOS 完全一致。Dart 层的波形强度沿用 Android 常见的 0–255 范围鸿蒙侧需要换算成 0–100。强度为 0 的片段只表示停顿不能当作一次有效震动处理。repeattrue 在当前鸿蒙实现中固定循环 3 次并不是无限循环。这样可以避免 API 12 环境下出现无法停止的持续震动需要更复杂的循环策略时应由业务层控制播放次数。- 新的播放请求或调用 Gaimon.stop() 时不仅要调用系统的 stopVibration()还要取消之前尚未执行的定时任务。否则旧波形的定时回调可能在页面切换后继续触发。当前实现通过定时器列表和播放代数标记处理了这个问题。预置效果在部分设备上可能不支持。实现中优先尝试 preset调用失败后回退到按时间震动设备关闭系统触感或硬件不支持时仍可能没有明显效果。- ohos.permission.VIBRATE 必须配置在宿主应用的 ohos/entry/src/main/module.json5 中并在资源文件中提供 vibrate_reason。插件自身的 HAR 模块不会替宿主应用申请权限。-插件注册器应由适配鸿蒙的 Flutter SDK 在执行 fvm flutter pub get 后生成不要手动修改 GeneratedPluginRegistrant.ets。如果生成文件中没有 GaimonPlugin优先检查是否误用了普通 Flutter SDK版本说明。 仓库中的 1.5.0-ohos-1.0.0 是较早的 OpenHarmony 适配标签不包含后续 API 12 最低版本适配本文使用的 main 分支包含 API 12 适配提交。若后续为 API 12 适配创建新的稳定标签应将依赖中的 ref 替换为对应标签。本次适配最终保留了 Gaimon 原有的 Dart API业务代码不需要针对鸿蒙再写一套调用逻辑。实际使用时基础反馈和状态反馈可以直接接入按钮、列表和表单AHAP 与自定义波形则应在目标鸿蒙设备上进行真机验证因为它们在 API 12 上属于基于基础震动接口的近似实现。六、总结本文围绕 Flutter 触觉反馈插件 Gaimon 的 OpenHarmony 适配展开完整走通了从环境准备到真机验证的流程。关键步骤可以概括为使用 FVM 管理适配鸿蒙的 Flutter SDK通过fvm flutter create --platformsohos .生成 OpenHarmony 平台目录在 pubspec.yaml 中引入 Gaimon 的鸿蒙适配版本依赖并在ohos/entry/src/main/module.json5中配置震动权限最后使用 DevEco Studio 在真实设备上运行验证。从功能支持情况来看设备能力检测、预置反馈、状态反馈和停止播放等核心能力在 OpenHarmony 上均已完整支持AHAP 自定义模式和自定义波形目前为部分支持复杂节奏的还原程度以及repeat的具体播放次数仍以当前适配实现为准。整体上Gaimon 的鸿蒙适配已经能够覆盖大多数常见触觉反馈场景开发者可以直接将其接入按钮、表单、列表和业务状态处理中。使用过程中需要注意以下几点一是务必使用适配了鸿蒙的 Flutter SDK否则项目无法生成ohos/目录二是不要遗漏震动权限配置否则真机运行时无法触发触觉反馈三是对于 AHAP 文件和自定义波形建议在目标设备上提前验证实际效果避免复杂节奏或重复播放与预期存在差异。后续可以从两个方向继续优化一方面完善 AHAP 解析能力提升复杂触觉图案在 OpenHarmony 上的还原度另一方面支持更多自定义参数例如更精细的强度控制、更灵活的重复策略以及更丰富的波形组合方式让鸿蒙端的触觉反馈能力与 Android、iOS 进一步对齐。
返回列表