HarmonyOS Next 实战卡片开发 01
介绍
Form Kit(卡片开发服务)提供一种界面展示形式,可以将应用的重要信息或操作前置到服务卡片(以下简称“卡片”),以达到服务直达、减少跳转层级的体验效果。卡片常用于嵌入到其他应用(当前被嵌入方即卡片使用方只支持系统应用,例如桌面)中作为其界面显示的一部分,并支持拉起页面、发送消息等基础的交互能力。
如:
直达服务
要完成的案例
新建一个卡片
卡片的类型主要有两个:
静态卡片 如果界面需要频繁刷新,不建议使用静态卡片,因为每一次刷新,都会导致卡片实例创建和销毁
动态卡片 如果界面需要频繁刷新,建议使用动态卡片
最后,工程下会多出主要的三个文件,作用如下
新建一个卡片效果
卡片的配置
卡片的配置文件 在 entry/src/main/resources/base/profile/form_config.json 路径
我们实际开发中,可能需要尤为关注的主要有以下几个:
displayName 卡片对外显示的名称
description 卡片对外显示的描述
supportDimensions 卡片支持的尺寸 ,支持多个
defaultDimension 卡片默认显示的尺寸
isDynamic 是否是动态卡片
updateEnabled 是否允许 定时刷新或者定点刷新
scheduledUpdateTime 定点刷新,精确到分钟。 如 10:30
updateDuration 定时刷新,以 30 分钟为一个单位,如 1 表示 1 * 30 分钟
{ "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" ] } ]}
复制代码
对应的配置说明:
表 2 window 对象的内部结构说明
卡片开发支持的能力
大部分情况下,页面支持的能力和卡片支持的能力大致一样。但是实际开发中,结合两方面来考量:
简单测试
entry/src/main/ets/widget/pages/WidgetCard.ets
主要的限制
当导入模块时,仅支持导入标识“支持在 ArkTS 卡片中使用”的模块。
不支持导入共享包。
不支持使用 native 语言开发。
仅支持声明式范式的部分组件、事件、动效、数据管理、状态管理和 API 能力。
卡片的事件处理和使用方的事件处理是独立的,建议在使用方支持左右滑动的场景下卡片内容不要使用左右滑动功能的组件,以防手势冲突影响交互体验。
暂不支持极速预览。
暂不支持断点调试能力。
暂不支持 Hot Reload 热重载。
暂不支持 setTimeOut。
卡片的生命周期
卡片的生命周期文件在
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) { 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; }}
复制代码
卡片通信
实际场景中,因为卡片的运行是可以独立于应用或者页面。所以开发难点其实是在于卡片之间或者卡片和应用之间的通信。
为了方便理解,我们做出以下区分
应用或者页面
卡片
解释
卡片组件和卡片的 Ability 之间通过 message 和 onAddForm 通信
卡片 Ability 通过 onAddForm 中的返回值向卡片组件通信
卡片组件通过触发 message 事件向卡片 Ability 通信
卡片组件和应用的 Ability 之间同 router 和 call 事件
卡片组件通过触发卡片组件通过触发 call 和 router 事件向 应用 Ability 通信
应用通过 updateForm 向卡片组件通信
卡片通过 LocalStorage 装饰器接收数据
首选项可以在以上的任意地方进行通信
卡片 Ability 向卡片组件通信
卡片在创建时,会触发 onAddForm 生命周期,此时返回数据可以直接传递给卡片
另外卡片在被卸载时,会触发 onRemoveForm 生命周期
卡片 Ability
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)@Componentstruct WidgetCard { // 接收onAddForm中返回的卡片Id @LocalStorageProp("formId") formId: string = "xxx"
build() { Column() { Button(this.formId) } .width("100%") .height("100%") .justifyContent(FlexAlign.Center) }}
复制代码
效果
postCardAction
卡片主动向外通信都是通过触发 postCardAction 来实现的。postCardAction 支持三种事件类型
卡片组件向卡片 Ability 通信
卡片页面中可以通过postCardAction接口触发 message 事件拉起 FormExtensionAbility 中的 onUpdateForm
onUpdateForm 中通过 updateForm 来返回数据
卡片组件
记得要携带 formId 过去,因为返回数据时需要根据 formId 找到对应的卡片
entry/src/main/ets/widget/pages/WidgetCard.ets
const localStorage = new LocalStorage()
@Entry(localStorage)@Componentstruct 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) }}
复制代码
卡片 Ability
当卡片组件发起 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); }}
复制代码
效果
卡片组件发起向应用 EntryAbility 通信 router
router 事件的特定是会拉起应用,前台会展示页面,会触发应用的 onCreate 和 onNewWant 生命周期
我们可以利用这个特性做唤起特定页面并且传递数据。
当触发 router 事件时,
如果应用没有在运行,便触发 onCreate 事件
如果应用正在运行,便触发 onNewWant 事件
卡片组件
提前新建好两个页面 pageA 和 pageB
卡片组件新建两个按钮,实现拉起应用并且显示特定页面
entry/src/main/ets/widget/pages/WidgetCard.ets
const localStorage = new LocalStorage()
@Entry(localStorage)@Componentstruct 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) }}
复制代码
应用 EntryAbility
分布在应用的 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"); }}
复制代码
效果
此时实现的效果是,不管有没有启动过页面,我们都可以直接点击卡片跳转到对应的页面
卡片组件发起向应用 EntryAbility 通信 call
卡片还可以通过 postCardAction 的触发 call 事件,call 会拉起应用,但是会在后台的形式运行。需要申请后台运行权限,可以进行比较耗
时的任务
需要注意的是:
申请后台运行应用权限
entry/src/main/module.json5
{ "module": { // ... "requestPermissions": [ { "name": "ohos.permission.KEEP_BACKGROUND_RUNNING" } ],
复制代码
卡片组件触发 call 事件,参数中必须携带 method 属性,用来区分不同的方法
应用 EntryAbility 在 onCreate 中,通过 callee 来监听不同的 method 事件
卡片组件
卡片组件触发 call 事件,参数中必须携带 method 属性,用来区分不同的方法
const localStorage = new LocalStorage()
@Entry(localStorage)@Componentstruct 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) }}
复制代码
应用 EntryAbility
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中存放的是我们的参数 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 中的卡片开发,包括卡片的基本概念、类型、新建卡片、配置、支持的能力、生命周期、通信等方面的内容。
卡片概述
Form Kit 提供将应用重要信息或操作前置到服务卡片的界面展示形式,可减少跳转层级,常用于嵌入系统应用(如桌面),支持拉起页面、发送消息等交互能力。
主要有静态卡片(不建议界面频繁刷新时使用)和动态卡片(适用于界面频繁刷新)。
卡片开发支持的能力
页面支持的能力与卡片大致相同,但实际开发需结合开发文档说明和模拟器及真机测试为准。
卡片开发存在诸多限制,如仅支持导入特定模块、不支持导入共享包、不支持 native 语言开发、仅支持声明式范式的
部分组件等,还暂不支持极速预览、断点调试、Hot Reload 热重载和 setTimeOut 等功能。
卡片的生命周期
卡片的生命周期文件为EntryFormAbility.ets,支持多个生命周期,如onAddForm(卡片创建时触发)、
onCastToNormalForm(转换成常态卡片时触发)、onUpdateForm(卡片更新时触发)等,每个生命周期有其特定的触发时机。
卡片通信
实际场景中,需区分应用或页面与卡片之间的通信。卡片组件和卡片 Ability 之间通过 message 和onAddForm通信;卡片组件和应用
的 Ability 之间通过router和call事件以及updateForm通信;卡片通过LocalStorage装饰器接收数据;首选项可在任意地方通
信。
完整代码
https://gitee.com/ukSir/HarmonyOS-Next-Card.git
评论