Form Kit(卡片开发服务)提供一种界面展示形式,可以将应用的重要信息或操作前置到服务卡片(以下简称“卡片”),以达到服务直达、减少跳转层级的体验效果。卡片常用于嵌入到其他应用(当前被嵌入方即卡片使用方只支持系统应用,例如桌面)中作为其界面显示的一部分,并支持拉起页面、发送消息等基础的交互能力。
如:

直达服务



卡片的类型主要有两个:



最后,工程下会多出主要的三个文件,作用如下


卡片的配置文件 在 entry/src/main/resources/base/profile/form_config.json 路径
我们实际开发中,可能需要尤为关注的主要有以下几个:

{
"forms": [
{
"name": "widget",
"displayName": "$string:widget_display_name",
"description": "$string:widget_desc",
"src": "./ets/widget/pages/WidgetCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDynamic": true,
"isDefault": true,
"updateEnabled": false,
"scheduledUpdateTime": "10:30",
"updateDuration": 1,
"defaultDimension": "2*4",
"supportDimensions": [
"2*4"
]
}
]
}对应的配置说明:
属性名称 | 含义 | 数据类型 | 是否可缺省 |
|---|---|---|---|
name | 表示卡片的名称,字符串最大长度为127字节。 | 字符串 | 否 |
displayName | 表示卡片的显示名称。取值可以是名称内容,也可以是对名称内容的资源索引,以支持多语言。字符串最小长度为1字节,最大长度为30字节。 | 字符串 | 否 |
description | 表示卡片的描述。取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。字符串最大长度为255字节。 | 字符串 | 可缺省,缺省为空。 |
src | 表示卡片对应的UI代码的完整路径。当为ArkTS卡片时,完整路径需要包含卡片文件的后缀,如:"./ets/widget/pages/WidgetCard.ets"。当为JS卡片时,完整路径无需包含卡片文件的后缀,如:"./js/widget/pages/WidgetCard" | 字符串 | 否 |
uiSyntax | 表示该卡片的类型,当前支持如下两种类型:- arkts:当前卡片为ArkTS卡片。- hml:当前卡片为JS卡片。 | 字符串 | 可缺省,缺省值为hml |
window | 用于定义与显示窗口相关的配置。 | 对象 | 可缺省,缺省值见表2。 |
isDefault | 表示该卡片是否为默认卡片,每个UIAbility有且只有一个默认卡片。- true:默认卡片。- false:非默认卡片。 | 布尔值 | 否 |
colorMode | 表示卡片的主题样式,取值范围如下:- auto:跟随系统的颜色模式值选取主题。- dark:深色主题。- light:浅色主题。 | 字符串 | 可缺省,缺省值为“auto”。 |
supportDimensions | 表示卡片支持的外观规格,取值范围:- 1 * 2:表示1行2列的二宫格。- 2 * 2:表示2行2列的四宫格。- 2 * 4:表示2行4列的八宫格。- 4 * 4:表示4行4列的十六宫格。- 1 * 1:表示1行1列的圆形卡片。- 6 * 4:表示6行4列的二十四宫格。 | 字符串数组 | 否 |
defaultDimension | 表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中。 | 字符串 | 否 |
updateEnabled | 表示卡片是否支持周期性刷新(包含定时刷新和定点刷新),取值范围:- true:表示支持周期性刷新,可以在定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)两种方式任选其一,当两者同时配置时,定时刷新优先生效。- false:表示不支持周期性刷新。 | 布尔类型 | 否 |
scheduledUpdateTime | 表示卡片的定点刷新的时刻,采用24小时制,精确到分钟。**说明:**updateDuration参数优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。 | 字符串 | 可缺省,缺省时不进行定点刷新。 |
updateDuration | 表示卡片定时刷新的更新周期,单位为30分钟,取值为自然数。当取值为0时,表示该参数不生效。当取值为正整数N时,表示刷新周期为30*N分钟。**说明:**updateDuration参数优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。 | 数值 | 可缺省,缺省值为“0”。 |
formConfigAbility | 表示卡片的配置跳转链接,采用URI格式。 | 字符串 | 可缺省,缺省值为空。 |
metadata | 表示卡片的自定义信息,参考Metadata数组标签。 | 对象 | 可缺省,缺省值为空。 |
dataProxyEnabled | 表示卡片是否支持卡片代理刷新,取值范围:- true:表示支持代理刷新。- false:表示不支持代理刷新。设置为true时,定时刷新和下次刷新不生效,但不影响定点刷新。 | 布尔类型 | 可缺省,缺省值为false。 |
isDynamic | 表示此卡片是否为动态卡片(仅针对ArkTS卡片生效)。- true:为动态卡片 。- false:为静态卡片。 | 布尔类型 | 可缺省,缺省值为true。 |
formVisibleNotify | 表示是否允许卡片使用卡片可见性通知(仅对系统应用的卡片生效)。 | 布尔类型 | 可缺省,缺省值为false。 |
transparencyEnabled | 表示是否支持卡片使用方设置此卡片的背景透明度(仅对系统应用的ArkTS卡片生效。)。- true:支持设置背景透明度 。- false:不支持设置背景透明度。 | 布尔类型 | 可缺省,缺省值为false。 |
fontScaleFollowSystem | 表示卡片使用方设置此卡片的字体是否支持跟随系统变化。- true:支持跟随系统字体大小变化 。- false:不支持跟随系统字体大小变化。 | 布尔类型 | 可缺省,缺省值为true。 |
supportShapes | 表示卡片的显示形状,取值范围如下:- rect:表示方形卡片。- circle:表示圆形卡片。 | 字符串 | 可缺省,缺省值为“rect”。 |
表2 window对象的内部结构说明
属性名称 | 含义 | 数据类型 | 是否可缺省 |
|---|---|---|---|
designWidth | 标识页面设计基准宽度。以此为基准,根据实际设备宽度来缩放元素大小。 | 数值 | 可缺省,缺省值为720px。 |
autoDesignWidth | 标识页面设计基准宽度是否自动计算。当配置为true时,designWidth将会被忽略,设计基准宽度由设备宽度与屏幕密度计算得出。 | 布尔值 | 可缺省,缺省值为false。 |
大部分情况下,页面支持的能力和卡片支持的能力大致一样。但是实际开发中,结合两方面来考量:


entry/src/main/ets/widget/pages/WidgetCard.ets

卡片的生命周期文件在
entry/src/main/ets/entryformability/EntryFormAbility.ets
支持以下生命周期:
描述 | 触发时机 |
|---|---|
onAddForm | 卡片被创建时触发 |
onCastToNormalForm | 卡片转换成常态卡片时触发 |
onUpdateForm | 卡片被更新时触发(调用 updateForm 时) |
onChangeFormVisibility | 卡片可见性修改时触发 |
onFormEvent | 卡片发起特定事件时触发(message) |
onRemoveForm | 卡片被卸载时触发 |
onConfigurationUpdate | 当系统配置更新时调用 |
onAcquireFormState | 卡片状态发生改变时触发 |
onStop | 卡片进程退出时触发 |
import { formBindingData, FormExtensionAbility, formInfo } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want: Want) {
let formData = '';
return formBindingData.createFormBindingData(formData);
}
onCastToNormalForm(formId: string) {
}
onUpdateForm(formId: string) {
}
onFormEvent(formId: string, message: string) {
}
onRemoveForm(formId: string) {
}
onAcquireFormState(want: Want) {
return formInfo.FormState.READY;
}
};实际场景中,因为卡片的运行是可以独立于应用或者页面。所以开发难点其实是在于卡片之间或者卡片和应用之间的通信。
为了方便理解,我们做出以下区分




卡片在创建时,会触发onAddForm生命周期,此时返回数据可以直接传递给卡片
另外卡片在被卸载时,会触发onRemoveForm生命周期
formBindingData:提供卡片数据绑定的能力,包括FormBindingData对象的创建、相关信息的描述
entry/src/main/ets/entryformability/EntryFormAbility.ets
import { formBindingData, FormExtensionAbility, formInfo } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want: Want) {
class FormData {
// 每一张卡片创建时都会被分配一个唯一的id
formId: string = want.parameters!['ohos.extra.param.key.form_identity'].toString();
}
let formData = new FormData()
// 返回数据给卡片
return formBindingData.createFormBindingData(formData);
}
onCastToNormalForm(formId: string) {
}
onUpdateForm(formId: string) {
}
onFormEvent(formId: string, message: string) {
}
onRemoveForm(formId: string) {
}
onAcquireFormState(want: Want) {
return formInfo.FormState.READY;
}
};卡片组件通过LocalStorage来接收onAddForm中返回的数据
const localStorage = new LocalStorage()
@Entry(localStorage)
@Component
struct WidgetCard {
// 接收onAddForm中返回的卡片Id
@LocalStorageProp("formId")
formId: string = "xxx"
build() {
Column() {
Button(this.formId)
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
卡片主动向外通信都是通过触发postCardAction来实现的。postCardAction支持三种事件类型

事件类型 | 描述 |
|---|---|
router | 会拉起应用,前台会展示页面,会触发应用的onCreate和onNewWant生命周期 |
call | 会拉起应用,但是会在后台的形式运行。需要申请后台运行权限,可以进行比较耗时的任务 |
message | 不会拉起应用,一定时间内(10s)如果没有更新的事件,会被销毁,适合做不太耗时的任务 |
卡片页面中可以通过postCardAction接口触发message事件拉起FormExtensionAbility中的onUpdateForm
onUpdateForm中通过updateForm来返回数据
记得要携带formId过去,因为返回数据时需要根据formId找到对应的卡片
entry/src/main/ets/widget/pages/WidgetCard.ets
const localStorage = new LocalStorage()
@Entry(localStorage)
@Component
struct WidgetCard {
// 接收onAddForm中返回的卡片Id
@LocalStorageProp("formId")
formId: string = "xxx"
@LocalStorageProp("num")
num: number = 100
build() {
Column() {
Button(this.formId)
Button("message" + this.num)
.onClick(() => {
postCardAction(this, {
action: 'message',
// 提交过去的参数
params: { num: this.num, aa: 200, formId: this.formId }
});
})
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}当卡片组件发起message事件时,我们可以通过onFormEvent监听到
entry/src/main/ets/entryformability/EntryFormAbility.ets
import { formBindingData, FormExtensionAbility, formInfo, formProvider } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want: Want) {
class FormData {
// 每一张卡片创建时都会被分配一个唯一的id
formId: string = want.parameters!['ohos.extra.param.key.form_identity'].toString();
}
let formData = new FormData()
// 返回数据给卡片
return formBindingData.createFormBindingData(formData);
}
// ...
onFormEvent(formId: string, message: string) {
// 接收到卡片通过message事件传递的数据
// message {"num":100,"aa":200,"params":{"num":100,"aa":200},"action":"message"}
interface IData {
num: number
aa: number
}
interface IRes extends IData {
params: IData,
action: "message"
formId: string
}
const params = JSON.parse(message) as IRes
interface IRet {
num: number
}
const data: IRet = {
num: params.num + 100
}
const formInfo = formBindingData.createFormBindingData(data)
// 返回数据给对应的卡片
formProvider.updateForm(params.formId, formInfo)
}
};
router事件的特定是会拉起应用,前台会展示页面,会触发应用的onCreate和onNewWant生命周期
我们可以利用这个特性做唤起特定页面并且传递数据。
当触发router事件时,


entry/src/main/ets/widget/pages/WidgetCard.ets
const localStorage = new LocalStorage()
@Entry(localStorage)
@Component
struct WidgetCard {
// 接收onAddForm中返回的卡片Id
@LocalStorageProp("formId")
formId: string = "xxx"
@LocalStorageProp("num")
num: number = 100
build() {
Column() {
// Button(this.formId)
// Button("message" + this.num)
// .onClick(() => {
// postCardAction(this, {
// action: 'message',
// params: { num: this.num, aa: 200, formId: this.formId }
// });
// })
Button("A页面")
.onClick(() => {
postCardAction(this, {
action: 'router',
abilityName: 'EntryAbility', // 只能跳转到当前应用下的UIAbility
params: {
targetPage: 'pages/PageA',
}
});
})
Button("B页面")
.onClick(() => {
postCardAction(this, {
action: 'router',
abilityName: 'EntryAbility', // 只能跳转到当前应用下的UIAbility
params: {
targetPage: 'pages/PageB',
}
});
})
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}分布在应用的onCreate和onNewWant编写逻辑实现跳转页面
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { router, window } from '@kit.ArkUI';
import { formInfo } from '@kit.FormKit';
export default class EntryAbility extends UIAbility {
// 要跳转的页面 默认是首页
targetPage: string = "pages/Index"
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 判断是否带有formId 因为我们直接点击图标,也会拉起应用,此时不会有formId
if (want.parameters && want.parameters[formInfo.FormParam.IDENTITY_KEY] !== undefined) {
// 获取卡片的formId
const formId = want.parameters![formInfo.FormParam.IDENTITY_KEY].toString();
// 获取卡片传递过来的参数
interface IData {
targetPage: string
}
const params: IData = (JSON.parse(want.parameters?.params as string))
this.targetPage = params.targetPage
// 我们也可以在这里通过 updateForm(卡片id,数据) 来返回内容给卡片
}
}
// 如果应用已经在运行,卡片的router事件不会再触发onCreate,会触发onNewWant
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
const formId = want.parameters![formInfo.FormParam.IDENTITY_KEY].toString();
// 获取卡片传递过来的参数
interface IData {
targetPage: string
}
const params: IData = (JSON.parse(want.parameters?.params as string))
this.targetPage = params.targetPage
// 跳转页面
router.pushUrl({
url: this.targetPage
})
// 我们也可以在这里通过 updateForm(卡片id,数据) 来返回内容给卡片
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// 跳转到对应的页面
windowStage.loadContent(this.targetPage, (err) => {
if (err.code) {
return;
}
});
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}此时实现的效果是,不管有没有启动过页面,我们都可以直接点击卡片跳转到对应的页面

卡片还可以通过postCardAction的触发call事件,call会拉起应用,但是会在后台的形式运行。需要申请后台运行权限,可以进行比较耗
时的任务
需要注意的是:
entry/src/main/module.json5
{
"module": {
// ...
"requestPermissions": [
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
}
],卡片组件触发call事件,参数中必须携带method属性,用来区分不同的方法
const localStorage = new LocalStorage()
@Entry(localStorage)
@Component
struct WidgetCard {
// 接收onAddForm中返回的卡片Id
@LocalStorageProp("formId")
formId: string = "xxx"
@LocalStorageProp("num")
num: number = 100
build() {
Column() {
Button("call事件" + this.num)
.onClick(() => {
postCardAction(this, {
action: 'call',
abilityName: 'EntryAbility', // 只能跳转到当前应用下的UIAbility
params: {
// 如果事件类型是call,必须传递method属性,用来区分不同的事件
method: "inc",
formId: this.formId,
num: this.num,
}
});
})
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}entry/src/main/ets/entryability/EntryAbility.ets
应用EntryAbility在onCreate中,通过 callee来监听不同的method事件。然后根据需求来处理业务
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { router, window } from '@kit.ArkUI';
import { formBindingData, formInfo, formProvider } from '@kit.FormKit';
import { rpc } from '@kit.IPCKit';
// 占位 防止语法出错,暂无实际作用
class MyParcelable implements rpc.Parcelable {
marshalling(dataOut: rpc.MessageSequence): boolean {
return true
}
unmarshalling(dataIn: rpc.MessageSequence): boolean {
return true
}
}
export default class EntryAbility extends UIAbility {
// 要跳转的页面 默认是首页
targetPage: string = "pages/Index"
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 监听call事件中的特定方法
this.callee.on("inc", (data: rpc.MessageSequence) => {
// data中存放的是我们的参数
params: {
// 如果事件类型是call,必须传递method属性,用来区分不同的事件
// method: "inc",
// formId: this.formId,
// num: this.num,
interface IRes {
formId: string
num: number
}
// 读取参数
const params = JSON.parse(data.readString() as string) as IRes
interface IData {
num: number
}
// 修改数据
const info: IData = {
num: params.num + 100
}
// 响应数据
const dataInfo = formBindingData.createFormBindingData(info)
formProvider.updateForm(params.formId, dataInfo)
}
// 防止语法报错,暂无实际应用
return new MyParcelable()
})
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// 跳转到对应的页面
windowStage.loadContent(this.targetPage, (err) => {
if (err.code) {
return;
}
});
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
本文主要介绍了 HarmonyOS Next 中的卡片开发,包括卡片的基本概念、类型、新建卡片、配置、支持的能力、生命周期、通信等方面的内容。
EntryFormAbility.ets,支持多个生命周期,如onAddForm(卡片创建时触发)、
onCastToNormalForm(转换成常态卡片时触发)、onUpdateForm(卡片更新时触发)等,每个生命周期有其特定的触发时机。
onAddForm通信;卡片组件和应用
的 Ability 之间通过router和call事件以及updateForm通信;卡片通过LocalStorage装饰器接收数据;首选项可在任意地方通
信。