适合谁看

  • 正在设计多张鸿蒙卡片的人

  • 不确定什么卡片该动态、什么该静态的人

  • 想控制鸿蒙卡片维护成本的人

问题背景

很多人一开始做鸿蒙卡片时,会默认:既然是卡片,那就都做成动态。

但动态卡片意味着:

  • 需要数据更新策略

  • 需要更复杂的生命周期管理

  • 需要处理内容源稳定性

  • 需要定时更新配置

并不是每一种入口都值得付出这个成本。

项目中的真实场景

食界探味当前有 3 张鸿蒙卡片:

鸿蒙卡片

类型

内容

更新策略

今日探味 (DailyRecommendCard)

动态

每日推荐一道环球美食

每天 00:05 自动更新

搜索美食 (SearchCard)

静态

快速跳转搜索页

不更新

美食许愿箱 (WishBoxCard)

静态

快速跳转心愿单

不更新

DailyRecommendFormAbility.ets 里,通过 STATIC_CARD_NAMES 实现分流:

const STATIC_CARD_NAMES: Set<string> = new Set([
  'SearchCard',
  'AiAssistantCard',
  'WishBoxCard',
]);

export default class DailyRecommendFormAbility extends FormExtensionAbility {
  onAddForm(want: Want): formBindingData.FormBindingData {
    const formName = want.parameters?.['ohos.extra.param.key.form_name'] as string;

    // 静态卡片 → 返回空数据
    if (STATIC_CARD_NAMES.has(formName)) {
      return formBindingData.createFormBindingData({});
    }

    // 动态卡片 → 返回推荐数据
    const item = getRecommendOfToday();
    return formBindingData.createFormBindingData({
      dishName: item.name,
      dishRegion: item.region,
      dishImage: resolveImageResName(item.imageResName),
      dishId: item.id,
      dishHighlight: item.highlight,
      dishSummary: item.summary,
    });
  }
}

核心实现

一、鸿蒙静态卡片适合做什么

更适合静态鸿蒙卡片的,是明确入口型能力:

静态卡片

用途

为什么静态

SearchCard

打开搜索

功能固定,不需要内容更新

AiAssistantCard

进入 AI 助手

功能固定,不需要内容更新

WishBoxCard

打开心愿单

功能固定,不需要内容更新

这类鸿蒙卡片的价值来自"更快进入",不是"展示动态内容"。

静态鸿蒙卡片的特点:

  • 内容固定不变

  • 不需要数据更新策略

  • 生命周期简单(只需要 onAddForm)

  • 维护成本低

二、鸿蒙动态卡片适合做什么

更适合动态鸿蒙卡片的,是内容型入口:

动态卡片

用途

为什么动态

DailyRecommendCard

今日推荐

内容每天变化

每日一题

每天一道题

内容每天变化

今日热榜

每天热榜

内容每天变化

它的价值来自"内容每天变",而不是"入口在哪里"。

动态鸿蒙卡片的特点:

  • 内容定期更新

  • 需要数据更新策略

  • 生命周期复杂(onAddForm + onUpdateForm)

  • 需要处理内容源稳定性

  • 需要配置 updateEnabled + scheduledUpdateTime

三、为什么要分开——三个好处

好处 1:动态更新逻辑只服务必要卡片

如果所有鸿蒙卡片都做成动态,每张卡片都需要:

  • 数据源

  • 更新算法

  • 容错处理

但静态卡片根本不需要这些。分开后,只有 DailyRecommendCard 需要 getRecommendOfToday()resolveImageResName()

好处 2:静态入口卡片更稳定

静态鸿蒙卡片没有数据更新,所以:

  • 不会因为数据源出错而显示异常

  • 不会因为定时更新失败而显示空白

  • 不会因为内容过期而误导用户

好处 3:后期内容和入口的迭代成本不会绑死在一起

所有卡片都做动态时:
  改搜索入口 → 可能影响数据更新逻辑
  改推荐算法 → 可能影响搜索卡片

分开后:
  改搜索入口 → 只改 SearchCard
  改推荐算法 → 只改 DailyRecommendCard

四、在同一个 FormAbility 中实现分流

食界探味的设计很巧妙:3 张鸿蒙卡片共享同一个 DailyRecommendFormAbility,通过 STATIC_CARD_NAMES 分流。

DailyRecommendFormAbility
  │
  ├─ onAddForm(want)
  │   │
  │   ├─ formName ∈ STATIC_CARD_NAMES?
  │   │   ├─ 是 → 返回空数据(静态卡片)
  │   │   └─ 否 → getRecommendOfToday()(动态卡片)
  │   │
  │   ▼
  │   formBindingData.createFormBindingData({...})
  │
  ├─ onUpdateForm(formId)
  │   │
  │   └─ 只有动态卡片会触发
  │       → getRecommendOfToday()
  │       → formProvider.updateForm(formId, data)
  │
  └─ onRemoveForm(formId)
      └─ 日志记录

这种设计的优势:

  • 一个 FormAbility 管理所有卡片,不需要为每张卡片创建单独的 Ability

  • 静态卡片的 onUpdateForm 不会被触发(因为配置了 updateEnabled: false

  • 静态卡片的 onAddForm 返回空数据,由卡片 UI 自己显示默认内容

五、配置层的分工

daily_recommend_form_config.json 中,静态和动态卡片的配置差异:

配置项

动态卡片 (DailyRecommendCard)

静态卡片 (SearchCard)

updateEnabled

true

false

scheduledUpdateTime

00:05

isDefault

true

false

这说明鸿蒙系统层面也区分了静态和动态卡片:

  • 动态卡片:系统会在指定时间触发 onUpdateForm()

  • 静态卡片:系统只在用户添加时触发 onAddForm(),之后不再更新

六、卡片 UI 层的分工

动态鸿蒙卡片的 UI 需要处理数据绑定:

// DailyRecommendCard.ets
@LocalStorageProp('dishName') dishName: string = '环球美食';
@LocalStorageProp('dishRegion') dishRegion: string = '世界';
@LocalStorageProp('dishImage') dishImage: string = 'dish_fallback';
// ... 数据来自 FormAbility

静态鸿蒙卡片的 UI 可以完全硬编码:

// SearchCard.ets(示意)
@Component
struct SearchCard {
  build() {
    Row() {
      Image($r('app.media.icon_search')).width(48).height(48)
      Column() {
        Text('搜索美食').fontSize(16).fontWeight(FontWeight.Bold)
        Text('搜索全球美食与食材').fontSize(12)
      }
    }
    .onClick(() => {
      postCardAction(this, {
        action: 'router',
        abilityName: 'EntryAbility',
        params: { pageId: 'search' }
      });
    })
  }
}

静态卡片不需要 @LocalStorageProp,因为数据是固定的。

七、点击跳转的分工

两种鸿蒙卡片的点击跳转方式一致(都用 postCardAction),但跳转目标不同:

鸿蒙卡片

点击跳转

参数

DailyRecommendCard

dish_detail / explore

pageId + dishId

SearchCard

/search

pageId='search'

WishBoxCard

/wish-box

pageId='wish_box'

动态卡片需要传 dishId 参数(因为内容每天变),静态卡片只需要传 pageId

关键代码位置

文件

作用

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

鸿蒙卡片生命周期(含分流逻辑)

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

动态卡片数据源

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

卡片配置(区分静态/动态)

静态 vs 动态鸿蒙卡片对比表

维度

静态鸿蒙卡片

动态鸿蒙卡片

内容

固定不变

定期更新

数据源

getRecommendOfToday()

updateEnabled

false

true

scheduledUpdateTime

00:05

onAddForm

返回空数据

返回推荐数据

onUpdateForm

不触发

定时触发

UI 数据绑定

不需要

需要 @LocalStorageProp

点击参数

只需 pageId

pageId + 内容参数

维护成本

适用场景

功能入口

内容推荐

常见坑

  • 把所有鸿蒙卡片都做成动态 — 静态入口卡片不需要数据更新

  • 动态卡片没有稳定内容来源 — 数据源出错时需要兜底

  • 静态鸿蒙卡片也强行维护更新逻辑 — 浪费资源,增加复杂度

  • 卡片分工不清,导致点击后入口语义混乱 — 静态是功能入口,动态是内容入口

  • 静态卡片的 onAddForm 返回了数据 — 应该返回空数据,由 UI 显示默认内容

  • 动态卡片没有配置 updateEnabled — 鸿蒙系统不会触发定时更新

可复用模板

鸿蒙 FormAbility 分流模板

const STATIC_CARD_NAMES: Set<string> = new Set(['SearchCard', 'WishBoxCard']);

export default class MyFormAbility extends FormExtensionAbility {
  onAddForm(want: Want): formBindingData.FormBindingData {
    const formName = want.parameters?.['ohos.extra.param.key.form_name'] as string;

    if (STATIC_CARD_NAMES.has(formName)) {
      return formBindingData.createFormBindingData({});  // 静态卡片
    }

    const item = getData();  // 动态卡片
    return formBindingData.createFormBindingData({
      title: item.title,
      content: item.content,
    });
  }

  onUpdateForm(formId: string): void {
    // 只有动态卡片会触发
    const item = getData();
    formProvider.updateForm(formId, formBindingData.createFormBindingData({
      title: item.title,
      content: item.content,
    }));
  }
}

配置模板(区分静态/动态)

{
  "forms": [
    {
      "name": "DynamicCard",
      "updateEnabled": true,
      "scheduledUpdateTime": "00:05",
      "isDefault": true
    },
    {
      "name": "StaticCard",
      "updateEnabled": false,
      "isDefault": false
    }
  ]
}

卡片分工决策模板

判断鸿蒙卡片类型:
  内容是否每天变化? → 是 → 动态卡片
  是否只是功能入口? → 是 → 静态卡片
  需要数据更新策略? → 是 → 动态卡片
  内容固定不变?     → 是 → 静态卡片

本篇总结

鸿蒙静态卡片适合做入口,动态卡片适合做内容。食界探味当前的实现展示了如何在同一个 FormAbility 中通过 STATIC_CARD_NAMES 实现分流:

  • 静态鸿蒙卡片 — SearchCard、WishBoxCard,功能固定,不需要数据更新

  • 动态鸿蒙卡片 — DailyRecommendCard,内容每天变化,需要定时更新

这层分工能明显降低鸿蒙卡片系统复杂度:动态更新逻辑只服务必要卡片,静态入口卡片更稳定,后期迭代成本不会绑死在一起。食界探味当前的实现就是一个很实用的参考。

Logo

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

更多推荐