适合谁看

  • 想给现有 Flutter 项目加鸿蒙桌面卡片的人

  • 不想让主工程被原生能力侵入的人

  • 想做增量适配的人

问题背景

很多跨端项目接原生入口时,最怕两件事:

  • 原生代码侵入主业务层

  • 目录结构被打乱

卡片如果接法不当,确实容易出现这两个问题。比如:

  • 在 Flutter 主目录里造一套卡片页面

  • 卡片数据逻辑和 Flutter 页面逻辑混写

  • 配置、生命周期、数据规则揉在一个文件里

项目中的真实场景

食界探味当前卡片相关代码主要集中在鸿蒙壳工程:

app/
├── lib/                          ← Flutter 主业务(不为卡片改动)
│   └── core/platform/
│       └── intent_navigation_channel.dart  ← 只承接卡片点击后的路由
│
├── ohos/                         ← 鸿蒙壳工程(卡片全部在这里)
│   └── entry/src/main/
│       ├── ets/
│       │   ├── formability/      ← 卡片 Ability + 数据
│       │   │   ├── DailyRecommendFormAbility.ets
│       │   │   └── RecommendData.ets
│       │   └── widget/pages/     ← 卡片 UI
│       │       ├── DailyRecommendCard.ets
│       │       ├── SearchCard.ets
│       │       └── WishBoxCard.ets
│       ├── resources/base/profile/  ← 卡片配置
│       │   └── daily_recommend_form_config.json
│       └── module.json5          ← 注册 FormExtensionAbility

Flutter 主业务目录并没有因为卡片而额外长出一套平行结构。

核心实现

一、目录结构——卡片全部收在鸿蒙壳工程

鸿蒙桌面卡片的实现涉及 4 类文件,全部在 app/ohos 下:

文件类型

位置

职责

卡片配置

resources/base/profile/*.json

定义卡片名称、尺寸、更新频率

卡片 Ability

ets/formability/*.ets

管理卡片生命周期

卡片数据

ets/formability/*Data.ets

提供卡片展示内容

卡片 UI

ets/widget/pages/*.ets

渲染卡片界面

这 4 类文件全部在鸿蒙壳工程里,Flutter 主工程完全不感知。

关键原则:卡片是鸿蒙系统入口能力,所以实现应该收在鸿蒙壳工程。

二、Flutter 主工程只承接后续行为

真正需要 Flutter 处理的,只有两件事:

  1. 卡片点击后跳到哪里 — 通过 intent_navigation_channel.dart 处理

  2. 应用内如何继续承接推荐内容 — 通过现有路由和页面处理

// intent_navigation_channel.dart
static const _pageIdToRoute = <String, String>{
  'search': '/search',
  'ai_assistant': '/ai-assistant',
  'wish_box': '/wish-box',
  'ingredients': '/ingredients',
  'explore': '/explore',
};

Flutter 主工程只需要在路由映射表里加一行,就能承接卡片点击。这属于业务层衔接,不属于卡片实现本体。

三、配置、Ability、数据层分开

食界探味把卡片拆成 3 层:

配置层:daily_recommend_form_config.json
  └─ 定义卡片名称、尺寸、更新频率

Ability 层:DailyRecommendFormAbility.ets
  └─ 管理卡片生命周期(onAddForm/onUpdateForm/onRemoveForm)

数据层:RecommendData.ets
  └─ 提供推荐数据、选择算法、资源校验

这 3 层各司其职,互不干扰。改配置不需要动 Ability,改数据不需要动 Ability,改 Ability 不需要动配置。

四、接入步骤——增量式,不破坏现有结构

给 Flutter 项目加鸿蒙桌面卡片的步骤:

步骤 1:在鸿蒙壳工程创建目录

app/ohos/entry/src/main/
├── ets/formability/          ← 新建
├── ets/widget/pages/         ← 新建
└── resources/base/profile/   ← 已有

步骤 2:添加卡片配置文件

// daily_recommend_form_config.json
{
  "forms": [{
    "name": "DailyRecommendCard",
    "displayName": "今日探味",
    "src": "./ets/widget/pages/DailyRecommendCard.ets",
    "updateEnabled": true,
    "scheduledUpdateTime": "00:05",
    "defaultDimension": "2*4"
  }]
}

步骤 3:在 module.json5 注册

{
  "extensionAbilities": [
    {
      "name": "DailyRecommendFormAbility",
      "srcEntry": "./ets/formability/DailyRecommendFormAbility.ets",
      "type": "form",
      "metadata": [
        {
          "name": "ohos.extension.form",
          "resource": "$profile:daily_recommend_form_config"
        }
      ]
    }
  ]
}

步骤 4:创建 Ability 和数据层

// DailyRecommendFormAbility.ets
import { getRecommendOfToday, resolveImageResName } from './RecommendData';

export default class DailyRecommendFormAbility extends FormExtensionAbility {
  onAddForm(want: Want): formBindingData.FormBindingData {
    const item = getRecommendOfToday();
    return formBindingData.createFormBindingData({
      dishName: item.name,
      dishImage: resolveImageResName(item.imageResName),
    });
  }
}

步骤 5:创建卡片 UI

// DailyRecommendCard.ets
@Component
struct DailyRecommendCard {
  @LocalStorageProp('dishName') dishName: string = '环球美食';

  build() {
    Row() {
      Text(this.dishName).fontSize(18)
    }
    .onClick(() => {
      postCardAction(this, {
        action: 'router',
        abilityName: 'EntryAbility',
        params: { pageId: 'dish_detail', dishId: this.dishId }
      });
    })
  }
}

步骤 6:Flutter 侧加路由映射(可选)

// intent_navigation_channel.dart
static const _pageIdToRoute = <String, String>{
  'search': '/search',
  // ... 已有路由
};

6 步完成后,鸿蒙桌面卡片就接入了。Flutter 主工程几乎没改动。

五、为什么这种接入方式不破坏主工程

担心

实际情况

卡片代码会侵入 Flutter 主目录

❌ 卡片全部在 app/ohos

Flutter 主工程需要新增页面

❌ 只需在路由映射表加一行

卡片数据逻辑会影响 Flutter 页面

❌ 数据层在鸿蒙壳工程

卡片配置会和 Flutter 配置冲突

❌ 配置文件完全独立

卡片 UI 会影响 Flutter 渲染

❌ 卡片 UI 是 ArkTS,不是 Flutter

六、和 Intents Kit 的接入方式对比

鸿蒙桌面卡片和 Intents Kit 的接入方式非常相似:

维度

鸿蒙桌面卡片

Intents Kit

实现位置

app/ohos 壳工程

app/ohos 壳工程

Flutter 改动

路由映射表加一行

路由映射表加一行

配置文件

daily_recommend_form_config.json

insight_intent.json

Ability

FormExtensionAbility

InsightIntentExecutor

卡片 UI

ArkTS 声明式 UI

无(系统搜索展示)

两者都遵循"壳工程增量接入"的原则:平台能力收在鸿蒙壳工程,Flutter 只承接后续路由。

关键代码位置

文件

作用

app/ohos/entry/src/main/ets/formability/DailyRecommendFormAbility.ets

鸿蒙卡片 Ability

app/ohos/entry/src/main/ets/formability/RecommendData.ets

卡片数据层

app/ohos/entry/src/main/ets/widget/pages/DailyRecommendCard.ets

卡片 UI

app/ohos/entry/src/main/resources/base/profile/daily_recommend_form_config.json

卡片配置

app/ohos/entry/src/main/module.json5

注册 FormExtensionAbility

app/lib/core/platform/intent_navigation_channel.dart

Flutter 路由承接

目录结构对比——接入前 vs 接入后

接入前:
app/
├── lib/                    ← Flutter 主业务
├── ohos/                   ← 鸿蒙壳工程(基础)
└── android/                ← Android 壳工程

接入后:
app/
├── lib/                    ← Flutter 主业务(几乎不变)
│   └── core/platform/
│       └── intent_navigation_channel.dart  ← 加一行路由映射
│
├── ohos/                   ← 鸿蒙壳工程(新增卡片)
│   └── entry/src/main/
│       ├── ets/
│       │   ├── formability/      ← 新增
│       │   └── widget/pages/     ← 新增
│       ├── resources/base/profile/  ← 新增配置
│       └── module.json5          ← 加注册
│
└── android/                ← Android 壳工程(不变)

Flutter 主业务目录完全不变,只有鸿蒙壳工程有增量。

常见坑

  • 在 Flutter 主目录里额外造一套卡片页面结构 — 卡片应该全部在 app/ohos

  • 卡片数据逻辑和 Flutter 页面逻辑混写 — 数据层在鸿蒙壳工程,Flutter 只承接路由

  • 配置、生命周期、数据规则揉在一个文件里 — 应该拆成配置/Ability/数据三层

  • 卡片点击后没有稳定应用内承接 — Flutter 侧需要在路由映射表里加一行

  • 没有在 module.json5 注册 — 鸿蒙系统不会发现这张卡片

  • 卡片 UI 和 Flutter UI 混用 — 卡片 UI 是 ArkTS,不是 Flutter Widget

可复用模板

鸿蒙卡片接入步骤模板

步骤 1:在 app/ohos/entry/src/main/ 下创建目录
  ├── ets/formability/       ← Ability + 数据
  ├── ets/widget/pages/      ← 卡片 UI
  └── resources/base/profile/ ← 配置文件

步骤 2:添加配置文件(daily_recommend_form_config.json)

步骤 3:在 module.json5 注册 FormExtensionAbility

步骤 4:创建 Ability(DailyRecommendFormAbility.ets)

步骤 5:创建数据层(RecommendData.ets)

步骤 6:创建卡片 UI(DailyRecommendCard.ets)

步骤 7:Flutter 侧加路由映射(intent_navigation_channel.dart)

鸿蒙壳工程目录模板

app/ohos/entry/src/main/
├── ets/
│   ├── formability/
│   │   ├── YourFormAbility.ets    ← 卡片 Ability
│   │   └── YourData.ets           ← 卡片数据
│   └── widget/pages/
│       └── YourCard.ets           ← 卡片 UI
├── resources/base/profile/
│   └── your_form_config.json      ← 卡片配置
└── module.json5                   ← 注册

module.json5 注册模板

{
  "extensionAbilities": [
    {
      "name": "YourFormAbility",
      "srcEntry": "./ets/formability/YourFormAbility.ets",
      "type": "form",
      "metadata": [
        {
          "name": "ohos.extension.form",
          "resource": "$profile:your_form_config"
        }
      ]
    }
  ]
}

本篇总结

给 Flutter 项目加鸿蒙桌面卡片,不等于重构主业务工程。食界探味的做法展示了"壳工程增量接入"的思路:

  1. 卡片全部在鸿蒙壳工程app/ohos 下的 formability、widget、profile

  2. Flutter 主工程几乎不变 — 只需在路由映射表加一行

  3. 配置/Ability/数据三层分离 — 各司其职,互不干扰

  4. 和 Intents Kit 接入方式一致 — 都遵循"壳工程增量接入"原则

只要分清"卡片实现"和"卡片承接",主工程结构就不会乱。这种增量式接入是鸿蒙 + Flutter 项目最稳的做法。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐