从 0 到 1 写一个 Flutter OHOS 插件 ohos_immersive_light(HDS TabBar 沉浸光感),把一路上踩过的坑一次性总结给你。
本文侧重 Windows 平台 特有的坑——macOS 用户可以略过第三章,但通用部分(PlatformView、Builder 限制、HashMap 解码、AbilityAware)请务必看。


一、为什么写 ohos_immersive_light

  • 目标:封装 OpenHarmony UIDesignKit 的 HdsTabs,给 Flutter 提供悬浮样式 + 沉浸光感效果的 TabBar。
  • 能力
    • OhosView 嵌入原生 HdsTabs 组件
    • 支持 phone / tablet / 2in1 设备的悬浮样式(API 23+),其他设备自动回退
    • 支持 selectedColor / unselectedColor / 自定义 Tab 列表
    • Tab 切换事件实时回传 Dart 侧
    • 通过 MethodChannel 暴露 isFloatingSupported() 给业务方做门控

底层机制就是 Dart 侧 OhosView + MethodChannel,OHOS 侧 FlutterPlugin + PlatformViewFactory + @Builder + 原生组件。把这一套打通,换个原生组件就能复刻出第二个、第三个插件。


二、通用开发流程(跨平台)

2.1 Dart 层

  1. 定义 ImmersiveLightOptions / ImmersiveLightTabItem 配置类,配套 toMap() 把字段转成原生可识别的基本类型(String / bool / num / List / Map)。
  2. 入口类 OhosImmersiveLight 提供 getPlatformVersion() / isFloatingSupported() 等方法,走 MethodChannel
  3. 视图类 ImmersiveLightView extends StatefulWidget,内部用 OhosViewviewType: 'immersive_light_view'creationParams: options.toMap(),并在 onPlatformViewCreated 里给独立 MethodChannel 设 setMethodCallHandler,接收 onTabChange 等原生回调。

2.2 OHOS 层

  1. OhosImmersiveLightPlugin implements FlutterPlugin, MethodCallHandler, AbilityAware
  2. onAttachedToEngine 里:注册主 MethodChannel + 注册 PlatformViewFactorygetPlatformViewRegistry().registerViewFactory(viewType, factory))。
  3. onAttachedToAbility 里:保存 AbilityPluginBinding,需要 UIAbilityContext 时从这里拿(本插件用不到,但写上不亏)。
  4. ImmersiveLightViewFactory extends PlatformViewFactory,构造里 super(StandardMessageCodec.INSTANCE)
  5. ImmersiveLightPlatformView extends PlatformViewgetView() 返回一个 WrappedBuilder,包住 @Builder 函数。
  6. @Builder 函数签名是 ($$: Params),里面只能写 UI 组件语法,不能 let、不能 const
  7. @Component 内部组件从 params.platformView 拿到业务对象(强转),把字段赋给原生组件;事件通过查 viewChannelMap 找到对应 MethodChannel,回调到 Dart。

跨平台通用 Bug 清单(与 Windows 无关,但每写一个新插件都会撞上)见文末「附录:通用 Bug 速查」。


三、Windows 平台专属踩坑(重点

如果你在 Windows 上开发 Flutter OHOS 插件,下面三件事是 必看 的,照着做能省掉至少 2 天的弯路。

3.1 自带的 example 工程跑不起来,要自己建一个

现象

  • flutter create 生成的 example/ 目录里,ohos/ 子工程跑不起来。
  • 在 DevEco Studio 里打开 example/ohos,hvigor 同步失败、签名缺失、依赖拉不到。
  • 即便能 build,运行时也找不到插件注册(Plugin OhosImmersiveLight not found)。

原因

  • 插件模板里 example/ohos 只搭了个空壳,没有完整的 build-profile.json5 / hvigorfile.ts / hvigor/hvigor-config.json5,也没有正确的 AppScope/app.json5
  • 它的 dependencies 指向本插件的 path: 引用没问题,但 entry 模块的 oh-package.json5 没把插件的 har 显式声明,hvigor 不会自动把插件原生代码加进构建。

解决自己建一个工程(参考仓库里的 ohos_immersive_light/exam_app/),步骤如下:

# 1. 在插件同级目录新建一个空 Flutter 工程
flutter create exam_app
cd exam_app
# 2. pubspec.yaml 用 path 引用本地插件
dependencies:
  ohos_immersive_light:
    path: ../
# 3. 拉依赖(注意:不要用 git 引用,见 3.2)
flutter pub get

# 4. 生成 ohos 工程
flutter create --platforms=ohos .

# 5. 用 DevEco Studio 打开 exam_app/ohos,配置签名后 Run

要点:

  • exam_app/ohos/hvigorconfig.tsinjectNativeModules 即可(见 3.3)。
  • exam_app/ohos/entry/oh-package.json5 不需要手写插件依赖,injectNativeModules 会自动注入。
  • exam_app/ 当作「调试宿主」,发布前所有调试都在这里做;插件的 example/ 可以删掉,也可以留作最小 demo,但不能拿来当联调工程。

结论:Windows 上别试图「一键跑通插件 example」,老老实实自己建一个 exam_app

3.2 不要用 git: 引入依赖

现象

  • pubspec.yaml 里写:
    dependencies:
      ohos_immersive_light:
        git:
          url: https://gitcode.com/zjx_jason/ohos_immersive_light
    
  • flutter pub get 报:路径含中文 / 含空格 / Unable to resolvegit checkout 失败、ohos/ 目录被识别为 Git 子模块 / 软链接异常。

原因(Windows 特有):

  • Windows 默认 core.longpaths 没开,路径长度超过 260 字符就 GG;Flutter OHOS 工程路径通常都很深。
  • git: 引用会让 pub 把仓库 clone 到 %LOCALAPPDATA%\Pub\Cache\git\...,再用软链引回工程;在 Windows 上软链 / Junction 经常被 DevEco Studio / hvigor 拒识。
  • 仓库里如果带 ohos/ 子目录且 .gitignore 不完善,pub 还会把 ohos/ 当成本地覆盖目录来链接,进一步炸。

解决

  • 开发阶段:统一用 path: 引用本地插件。
    dependencies:
      ohos_immersive_light:
        path: ../
    
  • 发布给用户:在 README 里同时给 path: / pub: / git: 三种安装方式,但你自己 Windows 联调时只用 path:
  • 如果必须用 git:,请在仓库根加好 .gitignore(至少忽略 ohos/build/ohos/.hvigor/ohos/.idea/example/build/),并确保 core.longpaths = true

结论:Windows 开发期,pubspec 永远用 path:,别用 git:

3.3 hvigorconfig.tssrcPath 必须是相对路径

现象

  • 自己用 flutter create --platforms=ohos . 生成的工程,build-profile.json5modules[0].srcPath 写成了类似 E:\\flutter_pub\\exam_app\\ohos\\entry绝对路径
  • 把整个 exam_app/ 拷给同事、或者 CI 上拉下来换盘符后,hvigor 直接报「module not found」「path not exist」。
  • flutter run 时偶发 hvigor assembleHar failed

原因

  • 早期某些 Flutter OHOS 模板在 Windows 上生成 build-profile.json5 时会写成绝对路径。
  • DevEco Studio 在某些版本上导入工程后,会主动把 srcPath 改写成绝对路径(IDE 的小聪明)。

解决

  • 打开 exam_app/ohos/build-profile.json5,手动把 modules[*].srcPath 改成 相对路径
    {
      "modules": [
        {
          "name": "entry",
          "srcPath": "./entry",          // ← 必须是相对路径
          "targets": [
            { "name": "default", "applyToProducts": ["default"] }
          ]
        }
      ]
    }
    
  • 同样地,宿主的 ohos/hvigor/hvigor-config.json5 不要有 dependencies: { "xxx": "E:\\...\\plugin\\ohos" } 这种绝对依赖;通过 injectNativeModules(__dirname, path.dirname(__dirname)) 让 hvigor 自动发现插件 har 即可。
// exam_app/ohos/hvigorconfig.ts
import path from 'path';
import { injectNativeModules } from 'flutter-hvigor-plugin';

injectNativeModules(__dirname, path.dirname(__dirname));

结论:每次新建 / 拷贝 OHOS 宿主工程后,先检查 build-profile.json5srcPath,不是相对路径就改掉。


四、ohos_immersive_light 实战要点(HDS TabBar)

4.1 在 @Builder 里使用原生组件

@Builder
function hdsTabsBuilder(params: Params) {
  ImmersiveLightInternalComponent({
    platformView: params.platformView as ImmersiveLightPlatformView,
    controller: (params.platformView as ImmersiveLightPlatformView).controller,
  });
}

这里 (params.platformView as ImmersiveLightPlatformView) 内联两次 是故意的——@Builder 里不能 let 中间变量。

4.2 设备类型判断(悬浮样式门控)

HdsTabs 的悬浮样式仅在 phone / tablet / 2in1 上有效,要在原生侧用 deviceInfo.deviceType 做门控,TV / Watch / Car 一律回退到普通 TabBar。

function isFloatingSupportedByDevice(): boolean {
  const t: string = deviceInfo.deviceType;
  return t === 'phone' || t === 'tablet' || t === '2in1';
}

Dart 侧也可以通过 MethodChannel 问原生「当前设备是否支持悬浮」,避免把门控逻辑写死:

final supported = await OhosImmersiveLight().isFloatingSupported();
if (!supported) {
  // 提示用户当前设备不支持悬浮样式
}

4.3 颜色 / 渐变字段

  • Dart 端用 int 传颜色(如 0xFF0A59F7),原生侧按 number 接收。
  • floatingStyle / barWidth / gradientMask / miniBar 这种结构化字段,原生侧用 HashMap<string, Object> 接收,配合 parseFloatingStyle(fsMap) 一类工具函数容错解析:
function parseFloatingStyle(fsMap: HashMap<string, Object>): HdsFloatingStyleConfig {
  const cfg: HdsFloatingStyleConfig = {};
  const rawBarWidth = fsMap.get('barWidth') as HashMap<string, Object> | undefined;
  if (rawBarWidth) {
    cfg.barWidth = {
      smallWidth: rawBarWidth.get('smallWidth') as number | undefined,
      mediumWidth: rawBarWidth.get('mediumWidth') as number | undefined,
      largeWidth: rawBarWidth.get('largeWidth') as number | undefined,
    };
  }
  // ... 其余字段同理
  return cfg;
}

关键原则:所有 HashMap 取值都要 as <期望类型> | undefined,再判空 + 赋值;永远不要 try { ... } catch { } 一次性吞掉,否则类型被推断成 any 直接被 ArkTS 拒。

4.4 Tab 切换事件

controller.onChange((index: number) => {
  const channel = viewChannelMap.get(this.viewId);
  channel?.invokeMethod('onTabChange', index);
});

Dart 端在 onTabChange 回调里 setState(() => _currentTabIndex = index) 即可联动 UI。

ImmersiveLightView(
  options: ImmersiveLightOptions(tabItems: [...]),
  onTabChange: (index) => setState(() => _currentTabIndex = index),
)

4.5 资源图标:路径 vs PixelMap

HdsTabs 的 Tab 图标支持两种形式:

  • 资源路径iconUrl: 'assets/icons/home.png'):Dart 端把资源打进 bundle,原生侧用 $r('app.media.xxx')$rawfile(...) 加载。
  • PixelMap:通过 MethodChannel 把 image.PixelMap 传过去,适合动态图标(未读消息数 badge 等)。

本插件默认走 iconUrl 资源路径,Dart 侧把 assets/icons/ 写到 pubspec.yamlflutter.assets 即可。


五、发布与维护

5.1 pubspec 必填项

name: ohos_immersive_light
description: "OpenHarmony HDS TabBar 插件,支持悬浮样式和沉浸光感效果"
version: 0.1.1
homepage: https://gitcode.com/zjx_jason/ohos_immersive_light

environment:
  sdk: ^3.9.2
  flutter: ">=3.3.0"   # ← 必加,否则 pub 校验会报 SDK 1.9.x 旧默认

dependencies:
  flutter:
    sdk: flutter
  plugin_platform_interface: ^2.0.2

flutter:
  plugin:
    platforms:
      ohos:
        pluginClass: OhosImmersiveLightPlugin

5.2 发布命令

# 1. 提交所有修改(pub 校验要求干净状态)
git add .
git commit -m "chore: ready to publish 0.1.0"

# 2. 发布
PUB_HOSTED_URL=https://pub.dev flutter pub publish
# 输入 y 确认

Windows 上跑 flutter pub publish 时,如果工程路径深、有中文、含空格,可能卡在生成 tar 包阶段。把工程挪到短路径、无中文、无空格的盘符根目录附近 通常能解决。

5.3 README 与 CHANGELOG

  • README 给「兼容性表格」(Flutter / Dart / SDK / IDE / 设备类型)。
  • 安装方式同时给 path: / git: / pub: 三种,但自己 Windows 联调时只演示 path:
  • 单独维护 docs/DEVICE_COMPATIBILITY.md,标明哪些设备型号实测通过、哪些已知问题。
  • CHANGELOG 按 Keep a Changelog 写。

六、调试技巧

  1. 看原生日志hilog 过滤 tag == 'OhosImmersiveLightPlugin',Dart 侧 print 配合 flutter run 控制台。
  2. MethodChannel 入参解码失败:在原生 MethodCallHandler.handleMethodCall 入口先 hilog 整条 call.args,对 HashMap 字段,别用属性访问,只用 get(key)
  3. PlatformView 不显示:99% 是 @Builder 内写了非 UI 语法,编译期会报;或 viewType 拼写不一致。
  4. 签名报错build-profile.json5 里的 signingConfigs.material.certpath 等要指向本机 .ohos/config/ 下的文件,换机器后不要提交该路径。
  5. hvigor 缓存:改 build-profile.json5 / oh-package.json5 后,跑一次 hvigor clean 再 build。
  6. HdsTabs 不渲染悬浮样式:先 hilog deviceInfo.deviceType,确认设备类型在白名单(phone/tablet/2in1)内;不在就回退。

七、小结

Windows 上写 Flutter OHOS 插件,记住三件最重要的事:

  1. 自带的 example 工程跑不通,自己新建一个 exam_app/ 当宿主
  2. pubspec 用 path: 引用本地插件,不要用 git:
  3. build-profile.json5srcPath 必须是相对路径

把上面这套流程跑通,剩下的就是通用 Flutter OHOS 插件开发——PlatformView + MethodChannel + AbilityAware + @Builder + HashMap 取值。把这套模板记熟后,换个原生组件就能复刻出第二个、第三个插件。


附录:通用 Bug 速查(跨平台)

# 现象 原因 解决
1 Module '"@ohos/flutter_ohos"' has no exported member 'Params' 主包未 export Params @ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformView 子路径导入
2 Only UI component syntax can be written here @Builder 内写了 let/const 删掉中间变量,全部内联
3 Property 'getPlatformViewsController' does not exist OHOS 绑定 API 不同 改用 binding.getPlatformViewRegistry().registerViewFactory(...)
4 Expected 1 arguments, but got 0(Factory super) PlatformViewFactory 需要 MessageCodec super(StandardMessageCodec.INSTANCE)
5 implements PlatformView 报缺方法 PlatformView 是抽象类 extends PlatformView,只实现 getView / dispose
6 titleColors 等 private 字段报「Property is private」 @Component 私有字段 去掉 private 修饰,或改用 Builder 内联传值
7 HashMap 字段取到 undefined args['key'] 属性访问 args as HashMap<string, Object>; argMap.get('key')
8 Use explicit types instead of any, unknown ArkTS 严格模式禁 any 写强类型 as <type> | undefined,try/catch 容错回退
9 pub 报 SDK 1.9.x 旧默认 没写 environment.flutter flutter: ">=3.3.0"
10 pub 报「1 checked-in file is modified」 未 commit git add && git commit 再 publish

文档版本:与 ohos_immersive_light 0.1.x 配套;如 API 变更以仓库与 pub.dev 为准。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐