卡片传递消息给应用(message 事件)
1. message 事件是什么?
场景:
动态卡片上有一个「刷新」「切换状态」「点赞」之类的按钮,不想跳转页面,也不需要后台长时间运行 UIAbility,只是想:
- 点一下按钮 → 通知应用「我被点了」;
- 应用(FormExtensionAbility)根据这条消息更新数据;
- 然后刷新卡片 UI。
这种交互就用 postCardAction 的 message 事件:
- 发送方:卡片页面(WidgetCard.ets)
- 处理方:FormExtensionAbility(EntryFormAbility.ets) 的
onFormEvent(formId, message) - 刷新方式:在
onFormEvent里调用formProvider.updateForm(formId, formBindingData)更新卡片绑定数据
2. 整体流程(顺序记一下)
- 卡片创建时 :
EntryFormAbility.onAddForm()通过formBindingData把初始数据(如title、detail)传给卡片;- 卡片用
@LocalStorageProp接收这些数据并显示。
- 用户点击卡片按钮 :
- 在按钮的
onClick中调用postCardAction(this, { action: 'message', params: {...} }); - 这会触发 FormExtensionAbility 的
onFormEvent(formId, message)回调。
- 在按钮的
- FormExtensionAbility.onFormEvent 收到消息 :
formId:是哪张卡片;message:卡片发来的参数(字符串,通常是 JSON / 简单值);- 在里面根据业务生成新的卡片数据对象,调用
updateForm(formId, newBinding)刷新卡片内容。
- 卡片 UI 自动刷新 :
- 卡片结构中绑定的 LocalStorage 字段(
@LocalStorageProp('title')等)会被系统更新; - 页面自动重新渲染,不需要你手动 setState。
- 卡片结构中绑定的 LocalStorage 字段(
3. 基础示例(官方风格版本整理)
3.1 卡片页面:发送 message 事件
ts
// 存储卡片数据的 LocalStorage
let storageUpdateByMsg = new LocalStorage();
@Entry(storageUpdateByMsg)
@Component
struct UpdateByMessageCard {
@LocalStorageProp('title') title: ResourceStr = $r('app.string.default_title');
@LocalStorageProp('detail') detail: ResourceStr = $r('app.string.DescriptionDefault');
build() {
Column() {
// 上半部分:显示标题和详情
Column() {
Text(this.title)
.fontColor('#FFFFFF')
.opacity(0.9)
.fontSize(14)
.margin({ top: '8%', left: '10%' })
Text(this.detail)
.fontColor('#FFFFFF')
.opacity(0.6)
.fontSize(12)
.margin({ top: '5%', left: '10%' })
}
.width('100%')
.height('50%')
.alignItems(HorizontalAlign.Start)
// 下半部分:一个"刷新"按钮
Row() {
Button() {
Text($r('app.string.update'))
.fontColor('#45A6F4')
.fontSize(12)
}
.width(120)
.height(32)
.margin({ top: '30%', bottom: '10%' })
.backgroundColor('#FFFFFF')
.borderRadius(16)
.onClick(() => {
// 关键:发送 message 事件
postCardAction(this, {
action: 'message',
params: { msgTest: 'messageEvent' } // 自定义参数
});
})
}
.width('100%')
.height('40%')
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Start)
.backgroundImage($r('app.media.CardEvent')) // 背景图
.backgroundImageSize(ImageSize.Cover)
}
}
要点:
- 用
@Entry(storage)+@LocalStorageProp()管住卡片显示的数据;- 按钮点击时只需要
action: 'message'即可,无需填abilityName,默认会唤起当前卡片的FormExtensionAbility;params是你自定义的消息内容(可以是简单字符串,也可以是结构化对象序列化后的字符串)。
3.2 FormExtensionAbility:接收 message & 更新卡片
ts
// EntryFormAbility.ets
import { BusinessError } from '@kit.BasicServicesKit';
import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG: string = 'EntryFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;
export default class EntryFormAbility extends FormExtensionAbility {
// 当卡片调用 postCardAction(..., { action: 'message' }) 时会回调到这里
onFormEvent(formId: string, message: string): void {
hilog.info(
DOMAIN_NUMBER,
TAG,
`FormAbility onFormEvent, formId = ${formId}, message: ${JSON.stringify(message)}`
);
// 1. 根据 message 自定义业务逻辑,这里简单演示更新 title/detail
class FormDataClass {
title: string = 'Title Update.'; // 对应卡片上的 title
detail: string = 'Description update success.'; // 对应卡片上的 detail
}
// 2. 创建新的卡片数据绑定对象
let formData = new FormDataClass();
let formInfo: formBindingData.FormBindingData =
formBindingData.createFormBindingData(formData);
// 3. 调用 updateForm 刷新卡片
formProvider.updateForm(formId, formInfo).then(() => {
hilog.info(DOMAIN_NUMBER, TAG, 'FormAbility updateForm success.');
}).catch((error: BusinessError) => {
hilog.info(
DOMAIN_NUMBER,
TAG,
`Operation updateForm failed. Cause: ${JSON.stringify(error)}`
);
})
}
// ... 还会有 onAddForm / onRemoveForm / onUpdateForm 等生命周期
}
要点:
formId:告诉你是哪一张卡片触发了事件;message:就是卡片传进来的params内容,通常是字符串。如果你传的是 JSON,可以JSON.parse(message)再按字段处理;updateForm(formId, formBindingData)是刷新卡片 UI 的关键一步;FormDataClass里字段名要和卡片@LocalStorageProp('xxx')一一对应。
4. 在官方示例的基础上稍微升级一个实战版
4.1 卡片页面:发送 "toggle" 指令
ts
let storageUpdateByMsg = new LocalStorage();
@Entry(storageUpdateByMsg)
@Component
struct UpdateByMessageCard {
@LocalStorageProp('title') title: string = '当前状态:未学习';
@LocalStorageProp('detail') detail: string = '点击下方按钮开始学习打卡';
build() {
Column() {
Column() {
Text(this.title)
.fontColor('#FFFFFF')
.opacity(0.9)
.fontSize(16)
.margin({ top: '8%', left: '10%' })
Text(this.detail)
.fontColor('#FFFFFF')
.opacity(0.6)
.fontSize(12)
.margin({ top: '5%', left: '10%' })
}
.width('100%')
.height('50%')
.alignItems(HorizontalAlign.Start)
Row() {
Button() {
Text('切换状态')
.fontColor('#45A6F4')
.fontSize(12)
}
.width(120)
.height(32)
.margin({ top: '30%', bottom: '10%' })
.backgroundColor('#FFFFFF')
.borderRadius(16)
.onClick(() => {
// 发送一个"toggle"操作
postCardAction(this, {
action: 'message',
params: { op: 'toggleStatus' }
});
})
}
.width('100%')
.height('40%')
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
.backgroundImage($r('app.media.CardEvent'))
.backgroundImageSize(ImageSize.Cover)
}
}
4.2 FormExtensionAbility:根据 op 切换不同内容
ts
import { BusinessError } from '@kit.BasicServicesKit';
import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG: string = 'EntryFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;
// 这里简单用内存 map 模拟每个 formId 的状态(生产环境一般用持久化存储)
const statusMap: Map<string, boolean> = new Map();
export default class EntryFormAbility extends FormExtensionAbility {
onFormEvent(formId: string, message: string): void {
hilog.info(
DOMAIN_NUMBER,
TAG,
`onFormEvent, formId = ${formId}, raw message = ${JSON.stringify(message)}`
);
let payload: { op?: string } = {};
try {
payload = JSON.parse(message); // 如果 params 本身就是对象字符串,可以 JSON.parse
} catch (e) {
// 如果 message 不是 JSON,可以自己按格式解析,这里简单忽略
hilog.info(DOMAIN_NUMBER, TAG, `parse message failed: ${JSON.stringify(e)}`);
}
if (payload.op === 'toggleStatus') {
// 1. 读取当前状态(默认 false 表示"未学习")
const oldStatus = statusMap.get(formId) ?? false;
const newStatus = !oldStatus;
statusMap.set(formId, newStatus);
// 2. 根据新状态生成不同的显示内容
class FormDataClass {
title: string;
detail: string;
constructor(status: boolean) {
if (status) {
this.title = '当前状态:已学习 ✅';
this.detail = '继续保持,每天都来打卡吧!';
} else {
this.title = '当前状态:未学习 ❌';
this.detail = '点击按钮开始今天的学习打卡。';
}
}
}
const formData = new FormDataClass(newStatus);
const binding = formBindingData.createFormBindingData(formData);
// 3. 刷新卡片
formProvider.updateForm(formId, binding).then(() => {
hilog.info(DOMAIN_NUMBER, TAG, 'updateForm success.');
}).catch((error: BusinessError) => {
hilog.error(
DOMAIN_NUMBER,
TAG,
`updateForm failed. Cause: ${JSON.stringify(error)}`
);
});
} else {
hilog.info(DOMAIN_NUMBER, TAG, 'Unknown op, ignore.');
}
}
}
5. 和 call 事件的简单对比(帮你在脑子里分清)
| 特性 | message | call |
|---|---|---|
| 触发方式 | postCardAction(..., {action:'message'}) |
postCardAction(..., {action:'call'}) |
| 目标 | FormExtensionAbility.onFormEvent |
指定 UIAbility(比如 XXXEntryAbility) |
| 是否拉起 UIAbility | ❌(只是激活 FormExtensionAbility) | ✅(后台启动某个 UIAbility) |
| 场景 | 刷新卡片、轻量交互 | 后台执行复杂逻辑、长任务 |
| 权限要求 | 无特殊权限要求 | 需要 KEEP_BACKGROUND_RUNNING |