卡片页面交互(postCardAction & FormLink)
1. 整体概念
在 HarmonyOS 里,服务卡片(Widget) 和「卡片提供方应用」(通常就是你的 App 模块)之间,可以做三类交互:
- 页面跳转:从卡片点进去进入应用页面
- 拉起进程:在后台启动应用做一些工作(不一定切到前台)
- 消息传递:卡片和 FormExtensionAbility 之间交换数据、刷新卡片
对应到 ArkTS API:
| 卡片类型 | 交互入口 | 支持事件类型 |
|---|---|---|
| 动态卡片 | postCardAction() |
router / message / call |
| 静态卡片 | FormLink 组件 |
router / message / call 华为开发者+1 |
注意
postCardAction只能在卡片页面中调用。FormLink只能在静态卡片 中使用。CSDN博客+1
2. postCardAction 接口说明(动态卡片)
函数签名
postCardAction(component: Object, action: Object): void
component:一般直接传this(当前组件实例)action:一个对象,描述这次交互的类型和参数 CSDN博客
action 常用字段
| 字段名 | 类型 | 说明 |
|---|---|---|
action |
string | 必填,`'router' |
bundleName |
string | 可选,目标应用包名(一般省略,默认当前包) |
moduleName |
string | 可选,目标模块名 |
abilityName |
string | 目标 UIAbility 名(router / call 常用) |
uri |
string | Deeplink 跳转用的 URI(router 专用,和 abilityName 同时写时优先 abilityName) |
params |
object | 自定义参数,对方通过 want.parameters.params 或 message 接收 |
call 特别规则 :
当
action: 'call'时,params必须 带一个method: string字段,用于指定 UIAbility 中要执行的方法名;同时 UIAbility 的launchType必须是singleton,并且应用需要有ohos.permission.KEEP_BACKGROUND_RUNNING权限。CSDN博客+1
3. FormLink 组件说明(静态卡片)
静态卡片不能直接绑定事件处理函数,它通过一个专门的容器组件 FormLink 来触发交互。华为开发者
基本用法:
FormLink({
action: 'router', // 'router' / 'message' / 'call'
abilityName: 'EntryAbility',
params: { message: 'from static widget' }
}) {
// 这里写卡片内部具体 UI
Column() {
Text('静态卡片入口')
}
}
内部参数含义和 postCardAction 的 action 对象是一致的,只是写法从「函数」变成了「组件属性」。
4. 三种事件类型详解
4.1 router 事件(页面跳转)
作用:
- 从卡片跳转到指定 UIAbility
- 对于普通三方应用,只能跳到自己应用的 UIAbility (也就是"卡片提供方"应用)。Gitee+1
特点:
- 只能在点击事件里触发(Button/Row 的
onClick里去调) - 可以通过
params携带数据到 UIAbility - UIAbility 在
onCreate/onNewWant中通过want.parameters拿到这些参数博客园+1
常见场景:
- 点击卡片「查看详情」,跳到应用详情页
- 点击卡片某个 tab,直接跳到应用内部对应页面
- 点击日历卡片某一天,跳到应用里的当天待办列表
4.2 message 事件(卡片 ↔ FormExtensionAbility)
作用:
- 给 FormExtensionAbility 发送一条自定义消息 ,会触发它的
onFormEvent(formId, message)回调,用来做:- 卡片内容刷新
- 简单业务操作(切换开关、点赞等)CSDN博客+1
特点:
- 不会启动 UIAbility(不会拉起应用界面)
- 适合「只改卡片、不跳转」的交互,如刷新、切换状态
4.3 call 事件(后台拉起 UIAbility)
作用:
- 在后台 启动指定 UIAbility 做一些耗时任务,比如:
- 后台拉取数据
- 后台执行某个算法
- 后台更新卡片数据后再推送回来 Gitee+1
特点:
- 不会把 UIAbility 调度到前台(用户看不到界面)
- 需要
KEEP_BACKGROUND_RUNNING权限 params.method必须设置,UIAbility 内通过约定的方法名来执行对应逻辑
5. 综合案例:动态卡片 + router/message/call
下面做一个完整的小例子:
场景 :
做一个「学习打卡」卡片:
- 显示今日学习时长
- 按钮1:「打开详情」 → router,跳到 App 内
StudyDetail页面- 按钮2:「刷新数据」 → message,触发 FormExtensionAbility 拉一次最新学习记录并更新卡片
- 按钮3:「后台同步」 → call,在后台启动 UIAbility 做一次云同步
5.1 卡片页面 WidgetCard.ets(动态卡片)
ts
// WidgetCard.ets(动态卡片示例)
// 注意:这里省略 import,一般创建 ArkTS 卡片工程后默认就能使用 postCardAction、LocalStorage 等。
let storage = new LocalStorage()
@Entry(storage)
@Component
struct WidgetCard {
@LocalStorageProp('todayMinutes') todayMinutes: number = 0
@LocalStorageProp('formId') formId: string = ''
private readonly ACTION_ROUTER: string = 'router'
private readonly ACTION_MESSAGE: string = 'message'
private readonly ACTION_CALL: string = 'call'
build() {
Column() {
// 标题 & 当天学习时长
Column() {
Text('今日学习')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 4 })
Text(`${this.todayMinutes} 分钟`)
.fontSize(28)
.fontWeight(FontWeight.Bold)
}
.margin({ bottom: 12 })
// 按钮区域
Row({ space: 8 }) {
// 1. router:打开详情页
Button('打开详情')
.type(ButtonType.Capsule)
.onClick(() => {
postCardAction(this, {
action: this.ACTION_ROUTER,
// 一般只需 abilityName,默认当前 bundle/module
abilityName: 'EntryAbility',
params: {
targetPage: 'studyDetail', // 约定的目标页面标识
from: 'widget',
formId: this.formId
}
})
})
// 2. message:刷新卡片数据
Button('刷新')
.type(ButtonType.Capsule)
.onClick(() => {
postCardAction(this, {
action: this.ACTION_MESSAGE,
// 不写 abilityName 时,默认触发当前模块的 FormExtensionAbility.onFormEvent
params: {
op: 'refresh',
time: Date.now()
}
})
})
}
Row({ space: 8, margin: { top: 8 } }) {
// 3. call:后台同步学习记录
Button('后台同步')
.type(ButtonType.Capsule)
.onClick(() => {
postCardAction(this, {
action: this.ACTION_CALL,
abilityName: 'EntryAbility',
params: {
method: 'syncStudyData', // UIAbility 中约定的方法名
formId: this.formId
}
})
})
}
}
.padding(12)
.width('100%')
.height('100%')
}
}
@LocalStorageProp('formId')对应的是 EntryFormAbilityonAddForm里写入的字段,用来区分是哪张卡片。CSDN博客+1
5.2 FormExtensionAbility:处理 message 事件 & 初始化数据
ts
// EntryFormAbility.ts
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formBindingData from '@ohos.app.form.formBindingData';
import formInfo from '@ohos.app.form.formInfo';
import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
// 创建卡片时给它一份初始数据
onAddForm(want): formBindingData.FormBindingData {
const formId = want.parameters[formInfo.FormParam.IDENTITY_KEY] as string
const data: Record<string, string> = {
formId: formId,
todayMinutes: '0'
}
return formBindingData.createFormBindingData(data)
}
// 处理 message 事件
onFormEvent(formId: string, message: string) {
// message 是一个 JSON 字符串
let payload: { op?: string, [key: string]: unknown } = {}
try {
payload = JSON.parse(message)
} catch (e) {
console.error(`onFormEvent parse message error: ${JSON.stringify(e)}`)
}
if (payload.op === 'refresh') {
// 这里可以从本地数据库 / 网络获取最新学习时长,这里举例写死
const latestMinutes = 45
const data: Record<string, string> = {
formId: formId,
todayMinutes: latestMinutes.toString()
}
const binding = formBindingData.createFormBindingData(data)
formProvider.updateForm(formId, binding)
}
}
}
- 当卡片点击「刷新」按钮时,触发
message事件 →onFormEvent→ 重新计算数据 →updateForm→ 卡片 UI 自动更新。CSDN博客
5.3 EntryAbility:处理 router / call 事件
ts
// EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import type Want from '@ohos.app.ability.Want';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import window from '@ohos.window';
import formBindingData from '@ohos.app.form.formBindingData';
import formProvider from '@ohos.app.form.formProvider';
let currentTargetPage: string = 'index';
let cachedWindowStage: window.WindowStage | null = null;
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
this.handleRouterOrCall(want);
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam) {
this.handleRouterOrCall(want);
if (cachedWindowStage) {
this.onWindowStageCreate(cachedWindowStage);
}
}
onWindowStageCreate(windowStage: window.WindowStage) {
cachedWindowStage = windowStage;
// 根据 currentTargetPage 决定加载哪个页面
const page = currentTargetPage === 'studyDetail'
? 'pages/StudyDetail'
: 'pages/Index';
windowStage.loadContent(page);
}
private handleRouterOrCall(want: Want) {
// 处理 router / call 传进来的 params
if (want.parameters && want.parameters.params) {
try {
const params = JSON.parse(want.parameters.params as string);
currentTargetPage = params.targetPage ?? currentTargetPage;
// 如果是 call 事件发起的后台同步,也会走到这里
if (params.method === 'syncStudyData') {
this.doBackgroundSync(params.formId as string);
}
} catch (e) {
console.error(`handleRouterOrCall parse params error: ${JSON.stringify(e)}`)
}
}
}
// call 事件:后台同步学习数据,并刷新卡片
private doBackgroundSync(formId: string) {
// 这里模拟:把 todayMinutes 同步为 60
const data: Record<string, string> = {
formId: formId,
todayMinutes: '60'
}
const binding = formBindingData.createFormBindingData(data)
formProvider.updateForm(formId, binding)
}
}
- router 事件:主要通过
params.targetPage告诉 UIAbility 要显示哪个页面。- call 事件:通过
params.method和其它参数,让 UIAbility 在后台执行逻辑(这里是doBackgroundSync),完成后用updateForm刷新卡片内容。seaxiang.com+1
6. 静态卡片使用 router 的一个小例子(FormLink)
如果你有一个 静态卡片 ,只能用 FormLink 触发事件,例如:
ts
// 静态卡片 WidgetCard.ets
@Entry
@Component
struct WidgetCard {
readonly ACTION_TYPE: string = 'router'
readonly ABILITY_NAME: string = 'EntryAbility'
build() {
FormLink({
action: this.ACTION_TYPE,
abilityName: this.ABILITY_NAME,
params: {
targetPage: 'studyDetail',
from: 'staticWidget'
}
}) {
// 卡片内部 UI
Column() {
Text('点我查看今日学习详情')
.fontSize(18)
.margin(12)
}
}
}
}
UIAbility 侧的处理逻辑和前面 router 的例子是一模一样的,只是触发入口从 postCardAction 换成了 FormLink。harmonyosdev.csdn.net+1