鸿蒙静态卡片和动态卡片在食界探味项目里的分工
适合谁看
-
正在设计多张鸿蒙卡片的人
-
不确定什么卡片该动态、什么该静态的人
-
想控制鸿蒙卡片维护成本的人
问题背景
很多人一开始做鸿蒙卡片时,会默认:既然是卡片,那就都做成动态。
但动态卡片意味着:
-
需要数据更新策略
-
需要更复杂的生命周期管理
-
需要处理内容源稳定性
-
需要定时更新配置
并不是每一种入口都值得付出这个成本。
项目中的真实场景
食界探味当前有 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) |
|---|---|---|
|
|
|
|
|
|
|
无 |
|
|
|
|
这说明鸿蒙系统层面也区分了静态和动态卡片:
-
动态卡片:系统会在指定时间触发
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。
关键代码位置
|
文件 |
作用 |
|---|---|
|
|
鸿蒙卡片生命周期(含分流逻辑) |
|
|
动态卡片数据源 |
|
|
卡片配置(区分静态/动态) |
静态 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,内容每天变化,需要定时更新
这层分工能明显降低鸿蒙卡片系统复杂度:动态更新逻辑只服务必要卡片,静态入口卡片更稳定,后期迭代成本不会绑死在一起。食界探味当前的实现就是一个很实用的参考。
更多推荐



所有评论(0)