HarmonyOs DevEco Studio小技巧31--卡片的生命周期与卡片的开发

Form Kit简介

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

卡片的基本概念:

  • 卡片使用方:如上图中的桌面,显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。
    • 应用图标:应用入口图标,点击后可拉起应用进程,图标内容不支持交互。
    • 卡片:具备不同规格大小的界面展示,卡片的内容可以进行交互,如实现按钮进行界面的刷新、应用的跳转等。
  • 卡片提供方:包含卡片的应用,提供卡片的显示内容、控件布局以及控件点击处理逻辑。
    • FormExtensionAbility:卡片业务逻辑模块,提供卡片创建、销毁、刷新等生命周期回调。
    • 卡片页面:卡片UI模块,包含页面控件、布局、事件等显示和交互信息。

亮点/特征

  • 服务直达:将元服务/应用的重要信息以卡片形式展示在桌面,用户可以通过快捷手势使用卡片,通过轻量交互行为实现服务直达、减少层级跳转的目的。
  • 永久在线:提供定时、代理等多种卡片刷新机制,实现卡片永久在线。
  • 受限管控:卡片支持的组件、事件、动效、数据管理、状态管理和API能力均进行了一定限制,保障性能、功耗及安全可靠。

服务卡片开发指导(Stage模型开发基于ArkTS UI的卡片)

之前我们接触过有Stage模型 还有 FA模型 但是FA模型暂时基本上用不到,所以只讲一下Stage模型

ArkTS卡片运行机制

实现原理

图1 ArkTS卡片实现原理

  • 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置,当前仅系统应用可以作为卡片使用方。

  • 卡片提供方:提供卡片显示内容的应用,控制卡片的显示内容、控件布局以及控件点击事件。

  • 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,提供formProvider的接口能力,同时提供卡片对象的管理与使用以及卡片周期性刷新等能力。

  • 卡片渲染服务:用于管理卡片渲染实例,渲染实例与卡片使用方上的卡片组件一一绑定。卡片渲染服务运行卡片页面代码widgets.abc进行渲染,并将渲染后的数据发送至卡片使用方对应的卡片组件。

图2 ArkTS卡片渲染服务运行原理

与动态卡片相比,静态卡片整体的运行框架和渲染流程是一致的,主要区别在于,卡片渲染服务将卡片内容渲染完毕后,卡片使用方会使用最后一帧渲染的数据作为静态图片显示,其次卡片渲染服务中的卡片实例会释放该卡片的所有运行资源以节省内存。因此频繁的刷新会导致静态卡片运行时资源不断的创建和销毁,增加卡片功耗。

与JS卡片相比,ArkTS卡片支持在卡片中运行逻辑代码,为确保ArkTS卡片发生问题后不影响卡片使用方应用的使用,ArkTS卡片新增了卡片渲染服务用于运行卡片页面代码widgets.abc,卡片渲染服务由卡片管理服务管理。卡片使用方的每个卡片组件都对应了卡片渲染服务里的一个渲染实例,同一应用提供方的渲染实例运行在同一个ArkTS虚拟机运行环境中,不同应用提供方的渲染实例运行在不同的ArkTS虚拟机运行环境中,通过ArkTS虚拟机运行环境隔离不同应用提供方卡片之间的资源与状态。开发过程中需要注意的是globalThis对象的使用,相同应用提供方的卡片globalThis对象是同一个,不同应用提供方的卡片globalThis对象是不同的。

 ArkTS卡片的优势

卡片作为应用的一个快捷入口,ArkTS卡片相较于JS卡片具备如下几点优势:

  • 统一开发范式,提升开发体验和开发效率。

    提供ArkTS卡片能力后,统一了卡片和页面的开发范式,页面的布局可以直接复用到卡片布局中,提升开发体验和开发效率。

    图3 卡片工程结构对比

  • 增强了卡片的能力,使卡片更加万能。

    • 新增了动效的能力:ArkTS卡片开放了属性动画和显式动画的能力,使卡片的交互更加友好。
    • 新增了自定义绘制的能力:ArkTS卡片开放了Canvas画布组件的能力,卡片可以使用自定义绘制的能力构建更多样的显示和交互效果。
    • 允许卡片中运行逻辑代码:开放逻辑代码运行后很多业务逻辑可以在卡片内部自闭环,拓宽了卡片的业务适用场景。

ArkTS卡片的约束

ArkTS卡片相较于JS卡片具备了更加丰富的能力,但也增加了使用卡片进行恶意行为的风险。由于ArkTS卡片显示在使用方应用中,使用方应用一般为桌面应用,为确保桌面的使用体验以及功耗相关考虑,对ArkTS卡片的能力做了以下约束:

  • 当导入模块时,仅支持导入标识“支持在ArkTS卡片中使用”的模块。

  • 不支持导入共享包。

  • 不支持使用native语言开发。

  • 仅支持声明式范式的部分组件、事件、动效、数据管理、状态管理和API能力。

  • 卡片的事件处理和使用方的事件处理是独立的,建议在使用方支持左右滑动的场景下卡片内容不要使用左右滑动功能的组件,以防手势冲突影响交互体验。

除此之外,当前ArkTS卡片还存在如下约束:

  • 暂不支持极速预览。

  • 暂不支持断点调试能力。

  • 暂不支持Hot Reload热重载。

  • 暂不支持setTimeOut。

ArkTS卡片相关模块

图1 ArkTS卡片相关模块

  • FormExtensionAbility:卡片扩展模块,提供卡片创建、销毁、刷新等生命周期回调。

  • FormExtensionContext:FormExtensionAbility的上下文环境,提供FormExtensionAbility具有的接口和能力。

  • formProvider:提供卡片提供方相关的接口能力,可通过该模块提供接口实现更新卡片、设置卡片更新时间、获取卡片信息、请求发布卡片等。

  • formInfo:提供了卡片信息和状态等相关类型和枚举。

  • formBindingData:提供卡片数据绑定的能力,包括FormBindingData对象的创建、相关信息的描述。

  • 页面布局(WidgetCard.ets):提供声明式范式的UI接口能力。

    • ArkTS卡片特有能力:postCardAction用于卡片内部和提供方应用间的交互,仅在卡片中可以调用。
    • ArkTS卡片能力列表:列举了能在ArkTS卡片中使用的API、组件、事件、属性和生命周期调度。
  • 卡片配置:包含FormExtensionAbility的配置和卡片的配置

    • 在module.json5配置文件中的extensionAbilities标签下,配置FormExtensionAbility相关信息。
    • 在resources/base/profile/目录下的form_config.json配置文件中,配置卡片(WidgetCard.ets)相关信息。

 ArkTS卡片开发指导(看这个)

创建一个ArkTS卡片

创建卡片当前有两种入口:

  • 创建工程时,选择Application,默认不带卡片,可以在创建工程后右键新建卡片。
  • 创建工程时,选择Atomic Service(元服务),也可以在创建工程后右键新建卡片。

1. 右键新建卡片。

在Service Widget菜单可直接选择创建动态或静态服务卡片。创建服务卡片后,也可以在卡片的form_config.json配置文件中,通过isDynamic参数修改卡片类型:isDynamic置空或赋值为"true",则该卡片为动态卡片;isDynamic赋值为"false",则该卡片为静态卡片。

2. 有四种模板,按照自己的需求来(不过一般都自己写样式)

3.在选择卡片的开发语言类型(Language)时,选择ArkTS选项,然后单击“Finish”,即可完成ArkTS卡片创建。 

 注意:经过测试,发现你这里全选的话,在创建的时候,他会根据你

这里的配置创建卡片,但是会在你选择卡片之后,其余的卡片全部销毁。

这里建议按实际需求来选择卡片,以免造成不必要的消费

 ArkTS卡片创建完成后,工程中会新增如下卡片相关文件:

卡片生命周期管理文件(EntryFormAbility.ets)、卡片页面文件(WidgetCard.ets)卡片配置文件(form_config.json)

配置卡片的配置文件

卡片相关的配置文件主要包含FormExtensionAbility的配置和卡片的配置两部分。

1.卡片需要在module.json5配置文件中的extensionAbilities标签下,配置FormExtensionAbility相关信息。FormExtensionAbility需要填写metadata元信息标签,其中键名称为固定字符串“ohos.extension.form”,资源为卡片的具体配置信息的索引。

配置示例如下:

{"module": {//....省略"extensionAbilities": [//..省略{  "name": "EntryFormAbility","srcEntry": "./ets/entryformability/EntryFormAbility.ets","label": "$string:EntryFormAbility_label","description": "$string:EntryFormAbility_desc","type": "form","metadata": [{"name": "ohos.extension.form","resource": "$profile:form_config"}]}]}
}

2. 卡片的具体配置信息。在上述FormExtensionAbility的元信息(“metadata”配置项)中,可以指定卡片具体配置信息的资源索引。例如当resource指定为$profile:form_config时,会使用开发视图的resources/base/profile/目录下的form_config.json作为卡片profile配置文件。内部字段结构说明如下表所示。

表1 卡片form_config.json配置文件

属性名称含义数据类型是否可缺省
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。
{"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": "1*2","supportDimensions": ["1*2","2*2","2*4","4*4","6*4"]}]
}

卡片生命周期管理

创建ArkTS卡片,需实现FormExtensionAbility生命周期接口。

import { formBindingData, FormExtensionAbility, formInfo, formProvider } from "@kit.FormKit";
import { Want, Configuration } from "@kit.AbilityKit";
import { BusinessError } from "@kit.BasicServicesKit";// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';// ArkTS规范中ets文件无法使用Object.keys和for..in...获取Object的key值,请使用自定义函数getObjKeys代替。
// 使用时请将此函数单独抽离至一个ts文件中并导出,在需要用到的ets文件中导入此函数后使用。
function getObjKeys(obj: Object): string[] {let keys = Object.keys(obj);return keys;
}export default class EntryFormAbility extends FormExtensionAbility {/*** 卡片提供方接收创建卡片的通知接口。* @param want 请求创建卡片的能力。* want表示获取卡片状态的描述。描述包括Bundle名称、能力名称、模块名称、卡片名和卡片维度* @return formBindingData.FormBindingData对象,卡片要显示的数据。* */onAddForm(want: Want) {console.log(`${TAG} onAddForm, want: ${want.abilityName}`);let dataObj1: Record<string, string> = {'temperature': '11c','time': '11:00'};let obj1: formBindingData.FormBindingData = formBindingData.createFormBindingData(dataObj1);return obj1;}/*** 卡片提供方接收临时卡片转常态卡片的通知接口。* @param formId 请求转换为常态的卡片标识。* @return 无返回值。* */onCastToNormalForm(formId: string) {console.log(`${TAG} onCastToNormalForm, formId: ${formId}`);}/***卡片提供方接收携带参数的更新卡片的通知接口。获取最新数据后调用formProvider的updateForm接口刷新卡片数据。* @param formId 卡片标识,用于更新卡片数据。* @param wantParams 携带的参数。* */onUpdateForm(formId: string, wantParams?: Record<string, Object>) {console.log(`${TAG} onUpdateForm, formId: ${formId},wantPara: ${wantParams?.['ohos.extra.param.key.host_bg_inverse_color']}`);let param: Record<string, string> = {'temperature': '22c','time': '22:00'}let obj2: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);formProvider.updateForm(formId, obj2).then(() => {console.log(`${TAG} context updateForm`);}).catch((error: BusinessError) => {console.error(`${TAG} context updateForm failed, data: ${error}`);});}/*** 卡片提供方接收修改可见性的通知接口。* 该接口仅对系统应用生效,且需要将formVisibleNotify配置为true。* @param newStatus 卡片可见性变化的状态。* */nChangeFormVisibility(newStatus: Record<string, number>) {console.log(`${TAG} onChangeFormVisibility, newStatus: ${newStatus}`);let param: Record<string, string> = {'temperature': '22c','time': '22:00'}let obj2: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);let keys: string[] = getObjKeys(newStatus);for (let i: number = 0; i < keys.length; i++) {console.log(`${TAG} onChangeFormVisibility, key: ${keys[i]}, value= ${newStatus[keys[i]]}`);formProvider.updateForm(keys[i], obj2).then(() => {console.log(`${TAG} context updateForm`);}).catch((error: BusinessError) => {console.error(`Operation updateForm failed. Cause: ${JSON.stringify(error)}`);});}}/*** 卡片提供方接收处理卡片事件的通知接口。* @param formId 卡片标识。* @param message 卡片事件的消息内容。* */onFormEvent(formId: string, message: string) {console.log(`${TAG} onFormEvent, formId: ${formId}, message: ${message}`);}/*** 卡片提供方接收卡片被移除的通知接口。* @param formId 卡片标识。* */onRemoveForm(formId: string) {console.log(`${TAG} onRemoveForm, formId: ${formId}`);}/*** 卡片提供方接收配置更新的通知接口。* @param newConfig 新的配置。* */onConfigurationUpdate(newConfig: Configuration) {// 仅当前formExtensionAbility存活时更新配置才会触发此生命周期。// 需要注意:formExtensionAbility创建后10秒内无操作将会被清理。console.log(`${TAG} onConfigurationUpdate, config: ${JSON.stringify(newConfig)}`);}/*** 卡片提供方接收卡片状态查询的通知接口。* @param want 查询卡片状态的能力。* want表示获取卡片状态的描述。描述包括Bundle名称、能力名称、模块名称、卡片名和卡片维度* */onAcquireFormState(want: Want) {console.log(`${TAG} onAcquireFormState, want: ${want}`);return formInfo.FormState.UNKNOWN;}/*** 当卡片提供方的卡片进程退出时,触发该回调。* */onStop() {console.log(`${TAG} onStop`);}
};

说明

FormExtensionAbility进程不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务,在生命周期调度完成后会继续存在10秒,如10秒内没有新的生命周期回调触发则进程自动退出。针对可能需要10秒以上才能完成的业务逻辑,建议拉起主应用进行处理,处理完成后使用updateForm通知卡片进行刷新。

开发卡片页面 

卡片页面能力说明

开发者可以使用声明式范式开发ArkTS卡片页面。如下卡片页面由DevEco Studio模板自动生成,开发者可以根据自身的业务场景进行调整。

ArkTS卡片具备JS卡片的全量能力,并且新增了动效能力和自定义绘制的能力,支持声明式范式的部分组件、事件、动效、数据管理、状态管理能力,详见“ArkTS卡片支持的页面能力”。

ArkTS卡片支持的页面能力

ArkTS卡片支持的页面能力详见学习ArkTS语言和ArkTS声明式开发范式。

只有标识“支持在ArkTS卡片中使用”的组件和接口可用于ArkTS卡片,同时请留意卡片场景下的能力差异说明。

例如:以下说明表示@Component装饰器可在ArkTS卡片中使用。

卡片使用动效能力 

ArkTS卡片开放了使用动画效果的能力,支持显式动画、属性动画、组件内转场能力。需要注意的是,ArkTS卡片使用动画效果时具有以下限制:

表1 动效参数限制

名称参数说明限制描述
duration动画播放时长限制最长的动效播放时长为1秒,当设置大于1秒的时间时,动效时长仍为1秒。
tempo动画播放速度卡片中禁止设置此参数,使用默认值1。
delay动画延迟执行的时长卡片中禁止设置此参数,使用默认值0。
iterations动画播放次数卡片中禁止设置此参数,使用默认值1。

说明

静态卡片不支持使用动效能力。

 以下示例代码实现了按钮旋转的动画效果:

@Entry
@Component
export struct Content_D3 {@State rotateAngle: number = 0;build() {Row() {Button('change rotate angle').height('20%').width('90%').margin('5%').onClick(() => {this.rotateAngle = (this.rotateAngle === 0 ? 90 : 0);}).rotate({ angle: this.rotateAngle }).animation({curve: Curve.EaseOut,playMode: PlayMode.Normal,})}.height('100%').alignItems(VerticalAlign.Center)}
}

 

卡片使用自定义绘制能力 

ArkTS卡片开放了自定义绘制的能力,在卡片上可以通过Canvas组件创建一块画布,然后通过CanvasRenderingContext2D对象在画布上进行自定义图形的绘制,如下示例代码实现了在画布的中心绘制了一个笑脸。

@Entry
@Component
struct CustomCanvasDrawingCard {private canvasWidth: number = 0;private canvasHeight: number = 0;// 初始化CanvasRenderingContext2D和RenderingContextSettingsprivate settings: RenderingContextSettings = new RenderingContextSettings(true);private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);build() {Column() {Row() {Canvas(this.context).width('100%').height('100%').onReady(() => {// 在onReady回调中获取画布的实际宽和高this.canvasWidth = this.context.width;this.canvasHeight = this.context.height;// 绘制画布的背景this.context.fillStyle = '#EEF0FF';this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight);// 在画布的中心绘制一个圆this.context.beginPath();let radius = this.context.width / 3;let circleX = this.context.width / 2;let circleY = this.context.height / 2;this.context.moveTo(circleX - radius, circleY);this.context.arc(circleX, circleY, radius, 2 * Math.PI, 0, true);this.context.closePath();this.context.fillStyle = '#FFBB1B';this.context.fill();// 绘制笑脸的左眼let leftR = radius / 13;let leftX = circleX - (radius / 2.3);let leftY = circleY - (radius / 4.5);this.context.beginPath();this.context.arc(leftX, leftY, leftR, 0, 2 * Math.PI, true);this.context.closePath();this.context.fillStyle = '#000';this.context.lineWidth = 15;this.context.stroke();// 绘制笑脸的右眼let rightR = radius / 13;let rightX = circleX + (radius / 2.3);let rightY = circleY - (radius / 4.5);this.context.beginPath();this.context.arc(rightX, rightY, rightR, 0, 2 * Math.PI, true);this.context.closePath();this.context.fillStyle = '#000';this.context.lineWidth = 15;this.context.stroke();// 绘制笑脸的嘴巴let mouthR = radius / 2;let mouthX = circleX;let mouthY = circleY + 10;this.context.beginPath();this.context.arc(mouthX, mouthY, mouthR, Math.PI / 1.4, Math.PI / 3.4, true);this.context.strokeStyle = '#000';this.context.lineWidth = 15;this.context.stroke();this.context.closePath();})}}.height('100%').width('100%')}
}

 

开发卡片事件

卡片事件能力说明

针对动态卡片,ArkTS卡片中提供了postCardAction接口用于卡片内部和提供方应用间的交互,当前支持router、message和call三种类型的事件,仅在卡片中可以调用。

针对静态卡片,ArkTS卡片提供了FormLink用于卡片内部和提供方应用间的交互。

动态卡片事件能力说明

动态卡片事件的主要使用场景如下:

  • router事件:可以使用router事件跳转到指定UIAbility,并通过router事件刷新卡片内容。
  • call事件:可以使用call事件拉起指定UIAbility到后台,并通过call事件刷新卡片内容。
  • message事件:可以使用message拉起FormExtensionAbility,并通过FormExtensionAbility刷新卡片内容。
静态卡片事件能力说明

写到拓展里面了 ---静态卡片能力拓展 

使用router事件跳转到指定UIAbility

在卡片中使用postCardAction接口的router能力,能够快速拉起卡片提供方应用的指定UIAbility,因此UIAbility较多的应用往往会通过卡片提供不同的跳转按钮,实现一键直达的效果。例如相机卡片,卡片上提供拍照、录像等按钮,点击不同按钮将拉起相机应用的不同UIAbility,从而提高用户的体验。

通常使用按钮控件来实现页面拉起,示例代码如下:

  • 在卡片页面中布局两个按钮,点击其中一个按钮时调用postCardAction向指定UIAbility发送router事件,并在事件内定义需要传递的内容。

    @Entry
    @Component
    struct WidgetEventRouterCard {build() {Column() {Text('卡片').fontColor('#fff68383').opacity(0.9).fontSize(14).margin({ top: '8%', left: '10%' })Row() {Column() {Button() {Text('打开页面A').fontColor('#45A6F4').fontSize(12)}.width(120).height(32).margin({ top: '20%' }).backgroundColor('#FFFFFF').borderRadius(16).onClick(() => {postCardAction(this, {action: 'router',abilityName: 'EntryAbility',params: { targetPage: 'PageA' }});})Button() {Text('打开页面B').fontColor('#45A6F4').fontSize(12)}.width(120).height(32).margin({ top: '8%', bottom: '15vp' }).backgroundColor('#FFFFFF').borderRadius(16).onClick(() => {postCardAction(this, {action: 'router',abilityName: 'EntryAbility',params: { targetPage: 'PageB' }});})}}.width('100%').height('80%').justifyContent(FlexAlign.Center)}.width('100%').height('100%').alignItems(HorizontalAlign.Start).backgroundImageSize(ImageSize.Cover)}
    }
  •  在UIAbility中接收router事件并获取参数,根据传递的params不同,选择拉起不同的页面。
    import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import { window } from '@kit.ArkUI';const TAG: string = 'EntryAbility';
    const DOMAIN_NUMBER: number = 0xFF00;export default class EntryAbility extends UIAbility {private selectPage: string = '';private currentWindowStage: window.WindowStage | null = null;onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');hilog.info(DOMAIN_NUMBER, TAG, `Ability onCreate: ${JSON.stringify(want?.parameters)}`);if (want?.parameters?.params) {// want.parameters.params 对应 postCardAction() 中 params 内容let params: Record<string, Object> = JSON.parse(want.parameters.params as string);this.selectPage = params.targetPage as string;hilog.info(DOMAIN_NUMBER, TAG, `onCreate selectPage: ${this.selectPage}`);}}// 如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期回调onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {hilog.info(DOMAIN_NUMBER, TAG, `Ability onNewWant: ${JSON.stringify(want?.parameters)}`);if (want?.parameters?.params) {// want.parameters.params 对应 postCardAction() 中 params 内容let params: Record<string, Object> = JSON.parse(want.parameters.params as string);this.selectPage = params.targetPage as string;hilog.info(DOMAIN_NUMBER, TAG, `onNewWant selectPage: ${this.selectPage}`);}if (this.currentWindowStage !== null) {this.onWindowStageCreate(this.currentWindowStage);}}onDestroy(): void {hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');}onWindowStageCreate(windowStage: window.WindowStage): void {// Main window is created, set main page for this abilityhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');// Main window is created, set main page for this abilitylet targetPage: string;// 根据传递的targetPage不同,选择拉起不同的页面switch (this.selectPage) {case 'PageA':targetPage = 'pages/PageA';break;case 'PageB':targetPage = 'pages/PageB';break;default:targetPage = 'pages/Index';}if (this.currentWindowStage === null) {this.currentWindowStage = windowStage;}windowStage.loadContent(targetPage, (err) => {if (err.code) {hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');return;}hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');});}onWindowStageDestroy(): void {// Main window is destroyed, release UI related resourceshilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');}onForeground(): void {// Ability has brought to foregroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');}onBackground(): void {// Ability has back to backgroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');}
    }
    

    效果图

 

 使用call事件拉起指定UIAbility到后台

许多应用希望借助卡片的能力,实现和应用在前台时相同的功能。例如音乐卡片,卡片上提供播放、暂停等按钮,点击不同按钮将触发音乐应用的不同功能,进而提高用户的体验。在卡片中使用postCardAction接口的call能力,能够将卡片提供方应用的指定的UIAbility拉到后台。同时,call能力提供了调用应用指定方法、传递数据的功能,使应用在后台运行时可以通过卡片上的按钮执行不同的功能。

约束限制

提供方应用需要具备后台运行权限(ohos.permission.KEEP_BACKGROUND_RUNNING)。

权限在 module.json5 里面配置 与page同级

"requestPermissions": [{"name": 'ohos.permission.KEEP_BACKGROUND_RUNNING'}
]
开发步骤

通常使用按钮控件来触发call事件,示例代码如下:

  • 在卡片页面中布局两个按钮,点击其中一个按钮时调用postCardAction向指定UIAbility发送call事件,并在事件内定义需要调用的方法和传递的数据。需要注意的是,method参数为必选参数,且类型需要为string类型,用于触发UIAbility中对应的方法。
@Entry
@Component
struct WidgetEventCallCard {@LocalStorageProp('formId') formId: string = '12400633174999288';build() {Column() {//...Row() {Column() {Button() {//...}//....onClick(() => {postCardAction(this, {action: 'call',abilityName: 'WidgetEventCallEntryAbility', // 只能跳转到当前应用下的UIAbility,与module.json5中定义保持params: {formId: this.formId,method: 'funA' // 在EntryAbility中调用的方法名}});})Button() {//...}//....onClick(() => {postCardAction(this, {action: 'call',abilityName: 'WidgetEventCallEntryAbility', // 只能跳转到当前应用下的UIAbility,与module.json5中定义保持params: {formId: this.formId,method: 'funB', // 在EntryAbility中调用的方法名num: 1 // 需要传递的其他参数}});})}}.width('100%').height('80%').justifyContent(FlexAlign.Center)}.width('100%').height('100%').alignItems(HorizontalAlign.Center)}
}
  • 在UIAbility中接收call事件并获取参数,根据传递的method不同,执行不同的方法。其余数据可以通过readString方法获取。需要注意的是,UIAbility需要onCreate生命周期中监听所需的方法。
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { rpc } from '@kit.IPCKit';
import { hilog } from '@kit.PerformanceAnalysisKit';const TAG: string = 'WidgetEventCallEntryAbility';
const DOMAIN_NUMBER: number = 0xFF00;
const CONST_NUMBER_1: number = 1;
const CONST_NUMBER_2: number = 2;class MyParcelable implements rpc.Parcelable {num: number;str: string;constructor(num: number, str: string) {this.num = num;this.str = str;}marshalling(messageSequence: rpc.MessageSequence): boolean {messageSequence.writeInt(this.num);messageSequence.writeString(this.str);return true;}unmarshalling(messageSequence: rpc.MessageSequence): boolean {this.num = messageSequence.readInt();this.str = messageSequence.readString();return true;}
}export default class WidgetEventCallEntryAbility extends UIAbility {// 如果UIAbility第一次启动,在收到call事件后会触发onCreate生命周期回调onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {try {// 监听call事件所需的方法this.callee.on('funA', (data: rpc.MessageSequence) => {// 获取call事件中传递的所有参数hilog.info(DOMAIN_NUMBER, TAG, `FunACall param:  ${JSON.stringify(data.readString())}`);promptAction.showToast({message: 'FunACall param:' + JSON.stringify(data.readString())});return new MyParcelable(CONST_NUMBER_1, 'aaa');});this.callee.on('funB', (data: rpc.MessageSequence) => {// 获取call事件中传递的所有参数hilog.info(DOMAIN_NUMBER, TAG, `FunBCall param:  ${JSON.stringify(data.readString())}`);promptAction.showToast({message: 'FunBCall param:' + JSON.stringify(data.readString())});return new MyParcelable(CONST_NUMBER_2, 'bbb');});} catch (err) {hilog.error(DOMAIN_NUMBER, TAG, `Failed to register callee on. Cause: ${JSON.stringify(err as BusinessError)}`);}}// 进程退出时,解除监听onDestroy(): void | Promise<void> {try {this.callee.off('funA');this.callee.off('funB');} catch (err) {hilog.error(DOMAIN_NUMBER, TAG, `Failed to register callee off. Cause: ${JSON.stringify(err as BusinessError)}`);}}
}

通过message事件刷新卡片内容

在卡片页面中可以通过postCardAction接口触发message事件拉起FormExtensionAbility,然后由FormExtensionAbility刷新卡片内容,下面是这种刷新方式的简单示例。

  • 在卡片页面通过注册Button的onClick点击事件回调,并在回调中调用postCardAction接口触发message事件拉起FormExtensionAbility。卡片页面中使用LocalStorageProp装饰需要刷新的卡片数据。
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).opacity(0.9).fontSize(14).margin({ top: '8%', left: '10%' })Text(this.detail).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(() => {postCardAction(this, {action: 'message',params: { msgTest: 'messageEvent' }});})}.width('100%').height('40%').justifyContent(FlexAlign.Center)}.width('100%').height('100%').alignItems(HorizontalAlign.Start).backgroundImageSize(ImageSize.Cover)}
}
  •  在FormExtensionAbility的onFormEvent生命周期中调用updateForm接口刷新卡片。
import { formBindingData, FormExtensionAbility, formInfo, formProvider } from "@kit.FormKit";
import { Want, Configuration } from "@kit.AbilityKit";
import { BusinessError } from "@kit.BasicServicesKit";// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';interface CartCount {formId: string
}// ArkTS规范中ets文件无法使用Object.keys和for..in...获取Object的key值,请使用自定义函数getObjKeys代替。
// 使用时请将此函数单独抽离至一个ts文件中并导出,在需要用到的ets文件中导入此函数后使用。
function getObjKeys(obj: Object): string[] {let keys = Object.keys(obj);return keys;
}export default class EntryFormAbility extends FormExtensionAbility {/*** 卡片提供方接收卡片添加的通知接口。* @param want 卡片添加的能力。* want表示获取卡片添加的描述。描述包括Bundle名称、能力名称、模块名称、卡片名和卡片维度* @return 返回一个FormBindingData对象。* */onAddForm(want: Want) {let formId = want.parameters!["ohos.extra.param.key.form_identity"] as string// Called to return a FormBindingData object.let formData: CartCount = {formId: formId};// 这里不允许直接存储首选项 此首选项非应用的首选项return formBindingData.createFormBindingData(formData);}/*** 卡片提供方接收临时卡片转常态卡片的通知接口。* @param formId 请求转换为常态的卡片标识。* @return 无返回值。* */onCastToNormalForm(formId: string) {console.log(`${TAG} onCastToNormalForm, formId: ${formId}`);}/***卡片提供方接收携带参数的更新卡片的通知接口。获取最新数据后调用formProvider的updateForm接口刷新卡片数据。* @param formId 卡片标识,用于更新卡片数据。* @param wantParams 携带的参数。* */onUpdateForm(formId: string, wantParams?: Record<string, Object>) {console.log(`${TAG} onUpdateForm, formId: ${formId},wantPara: ${wantParams?.['ohos.extra.param.key.host_bg_inverse_color']}`);let param: Record<string, string> = {'temperature': '22c','time': '22:00'}let obj2: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);formProvider.updateForm(formId, obj2).then(() => {console.log(`${TAG} context updateForm`);}).catch((error: BusinessError) => {console.error(`${TAG} context updateForm failed, data: ${error}`);});}/*** 卡片提供方接收修改可见性的通知接口。* 该接口仅对系统应用生效,且需要将formVisibleNotify配置为true。* @param newStatus 卡片可见性变化的状态。* */nChangeFormVisibility(newStatus: Record<string, number>) {console.log(`${TAG} onChangeFormVisibility, newStatus: ${newStatus}`);let param: Record<string, string> = {'temperature': '22c','time': '22:00'}let obj2: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);let keys: string[] = getObjKeys(newStatus);for (let i: number = 0; i < keys.length; i++) {console.log(`${TAG} onChangeFormVisibility, key: ${keys[i]}, value= ${newStatus[keys[i]]}`);formProvider.updateForm(keys[i], obj2).then(() => {console.log(`${TAG} context updateForm`);}).catch((error: BusinessError) => {console.error(`Operation updateForm failed. Cause: ${JSON.stringify(error)}`);});}}/*** 卡片提供方接收处理卡片事件的通知接口。* @param formId 卡片标识。* @param message 卡片事件的消息内容。* */onFormEvent(formId: string, message: string): void {// Called when a specified message event defined by the form provider is triggered.console.log(TAG, `FormAbility onFormEvent, formId = ${formId}, message: ${JSON.stringify(message)}`);class FormDataClass {title: string = '我不好'; // 和卡片布局中对应detail: string = '呵呵呵呵呵'; // 和卡片布局中对应}let formData = new FormDataClass();let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData);formProvider.updateForm(formId, formInfo).then(() => {console.log(TAG, 'FormAbility updateForm success.');}).catch((error: BusinessError) => {console.log(TAG, JSON.stringify(FormDataClass));console.log(TAG, `Operation updateForm failed. Cause: ${JSON.stringify(error)}`);})}/*** 卡片提供方接收卡片被移除的通知接口。* @param formId 卡片标识。* */onRemoveForm(formId: string) {console.log(`${TAG} onRemoveForm, formId: ${formId}`);}/*** 卡片提供方接收配置更新的通知接口。* @param newConfig 新的配置。* */onConfigurationUpdate(newConfig: Configuration) {// 仅当前formExtensionAbility存活时更新配置才会触发此生命周期。// 需要注意:formExtensionAbility创建后10秒内无操作将会被清理。console.log(`${TAG} onConfigurationUpdate, config: ${JSON.stringify(newConfig)}`);}/*** 卡片提供方接收卡片状态查询的通知接口。* @param want 查询卡片状态的能力。* want表示获取卡片状态的描述。描述包括Bundle名称、能力名称、模块名称、卡片名和卡片维度* */onAcquireFormState(want: Want) {console.log(`${TAG} onAcquireFormState, want: ${want}`);return formInfo.FormState.UNKNOWN;}/*** 当卡片提供方的卡片进程退出时,触发该回调。* */onStop() {console.log(`${TAG} onStop`);}
};

 

通过router事件刷新卡片内容 

  • 在卡片页面通过注册Button的onClick点击事件回调,并在回调中调用postCardAction接口触发router事件拉起UIAbility。
let storageUpdateRouter = new LocalStorage();@Entry(storageUpdateRouter)
@Component
struct WidgetUpdateRouterCard {@LocalStorageProp('routerDetail') routerDetail: ResourceStr = $r('app.string.init');build() {Column() {Column() {Text(this.routerDetail).opacity(0.9).fontSize(14).margin({ top: '8%', left: '10%', right: '10%' }).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(2)}.width('100%').height('50%').alignItems(HorizontalAlign.Start)Row() {Button() {Text($r('app.string.JumpLabel')).fontColor('#45A6F4').fontSize(12)}.width(120).height(32).margin({ top: '30%', bottom: '10%' }).backgroundColor('#FFFFFF').borderRadius(16).onClick(() => {postCardAction(this, {action: 'router',abilityName: 'EntryAbility', // 只能跳转到当前应用下的UIAbilityparams: {routerDetail: 'RouterFromCard',}});})}.width('100%').height('40%').justifyContent(FlexAlign.Center)}.width('100%').height('100%').alignItems(HorizontalAlign.Start).backgroundImageSize(ImageSize.Cover)}
}
  • 在UIAbility的onCreate或者onNewWant生命周期中可以通过入参want获取卡片的formID和传递过来的参数信息,然后调用updateForm接口刷新卡片。
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { formBindingData, formInfo, formProvider } from '@kit.FormKit';const TAG: string = 'EntryAbility';
const DOMAIN_NUMBER: number = 0xFF00;export default class EntryAbility extends UIAbility {handleFormRouterEvent(want: Want, source: string): void {hilog.info(DOMAIN_NUMBER, TAG, `handleFormRouterEvent ${source}, Want: ${JSON.stringify(want)}`);if (want.parameters && want.parameters[formInfo.FormParam.IDENTITY_KEY] !== undefined) {let curFormId = want.parameters[formInfo.FormParam.IDENTITY_KEY].toString();// want.parameters.params 对应 postCardAction() 中 params 内容let message: string = (JSON.parse(want.parameters?.params as string))?.routerDetail;hilog.info(DOMAIN_NUMBER, TAG, `UpdateForm formId: ${curFormId}, message: ${message}`);let formData: Record<string, string> = {'routerDetail': "不能累,加油吧,明天是美好的"};let formMsg = formBindingData.createFormBindingData(formData);formProvider.updateForm(curFormId, formMsg).then((data) => {hilog.info(DOMAIN_NUMBER, TAG, 'updateForm success.', JSON.stringify(data));}).catch((error: BusinessError) => {hilog.info(DOMAIN_NUMBER, TAG, 'updateForm failed.', JSON.stringify(error));});}}// 如果UIAbility第一次启动,在收到call事件后会触发onCreate生命周期回调onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {this.handleFormRouterEvent(want, 'onCreate');}// 如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期回调onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {hilog.info(DOMAIN_NUMBER, TAG, 'onNewWant Want:', JSON.stringify(want));this.handleFormRouterEvent(want, 'onNewWant');}onDestroy(): void | Promise<void> {try {this.callee.off('PageA');this.callee.off('PageB');} catch (err) {hilog.error(DOMAIN_NUMBER, TAG, `Failed to register callee off. Cause: ${JSON.stringify(err as BusinessError)}`);}}onWindowStageCreate(windowStage: window.WindowStage): void {// Main window is created, set main page for this abilityhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');// Main window is created, set main page for this abilitywindowStage.loadContent('pages/Index', (err) => {if (err.code) {hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');return;}hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');});}onWindowStageDestroy(): void {// Main window is destroyed, release UI related resourceshilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');}onForeground(): void {// Ability has brought to foregroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');}onBackground(): void {// Ability has back to backgroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');}
}

 

通过call事件刷新卡片内容 

  • 在使用postCardAction接口的call事件时,需要在FormExtensionAbility中的onAddForm生命周期回调中更新formId
import { Want } from '@kit.AbilityKit';
import { formBindingData, FormExtensionAbility } from '@kit.FormKit';export default class WidgetCalleeFormAbility extends FormExtensionAbility {onAddForm(want: Want): formBindingData.FormBindingData {class DataObj1 {formId: string = '';}let dataObj1 = new DataObj1();if (want.parameters && want.parameters['ohos.extra.param.key.form_identity'] !== undefined) {let formId: string = want.parameters['ohos.extra.param.key.form_identity'].toString();dataObj1.formId = formId;}let obj1 = formBindingData.createFormBindingData(dataObj1);return obj1;}// ...
}

 在卡片页面通过注册Button的onClick点击事件回调,并在回调中调用postCardAction接口触发call事件拉起UIAbility。

let storageUpdateCall = new LocalStorage();@Entry(storageUpdateCall)
@Component
struct WidgetUpdateCallCard {@LocalStorageProp('formId') formId: string = '12400633174999288';@LocalStorageProp('calleeDetail') calleeDetail: ResourceStr = $r('app.string.init');build() {Column() {Column() {Text(this.calleeDetail).opacity(0.9).fontSize(14).margin({ top: '8%', left: '10%' })}.width('100%').height('50%').alignItems(HorizontalAlign.Start)Row() {Button() {Text($r('app.string.JumpLabel')).fontColor('#45A6F4').fontSize(12)}.width(120).height(32).margin({ top: '30%', bottom: '10%' }).backgroundColor('#FFFFFF').borderRadius(16).onClick(() => {postCardAction(this, {action: 'call',abilityName: 'EntryAbility', // 只能拉起当前应用下的UIAbilityparams: {method: 'funA',formId: this.formId,calleeDetail: 'CallFrom'}});})}.width('100%').height('40%').justifyContent(FlexAlign.Center)}.width('100%').height('100%').alignItems(HorizontalAlign.Start).backgroundImageSize(ImageSize.Cover)}
}
  •  在UIAbility的onCreate生命周期中监听call事件所需的方法,然后在对应方法中调用updateForm接口刷新卡片。

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { formBindingData, formProvider } from '@kit.FormKit';
import { rpc } from '@kit.IPCKit';const TAG: string = 'EntryAbility';
const DOMAIN_NUMBER: number = 0xFF00;
const MSG_SEND_METHOD: string = 'funA';
const CONST_NUMBER_1: number = 1;class MyParcelable implements rpc.Parcelable {num: number;str: string;constructor(num: number, str: string) {this.num = num;this.str = str;};marshalling(messageSequence: rpc.MessageSequence): boolean {messageSequence.writeInt(this.num);messageSequence.writeString(this.str);return true;};unmarshalling(messageSequence: rpc.MessageSequence): boolean {this.num = messageSequence.readInt();this.str = messageSequence.readString();return true;};
}// 在收到call事件后会触发callee监听的方法
let funACall = (data: rpc.MessageSequence): MyParcelable => {// 获取call事件中传递的所有参数let params: Record<string, string> = JSON.parse(data.readString());if (params.formId !== undefined) {let curFormId: string = params.formId;let message: string = params.calleeDetail;hilog.info(DOMAIN_NUMBER, TAG, `UpdateForm formId: ${curFormId}, message: ${message}`);let formData: Record<string, string> = {'calleeDetail': '卧槽没数据啊'};let formMsg: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData);formProvider.updateForm(curFormId, formMsg).then((data) => {hilog.info(DOMAIN_NUMBER, TAG, `updateForm success. ${JSON.stringify(data)}`);}).catch((error: BusinessError) => {hilog.error(DOMAIN_NUMBER, TAG, `updateForm failed: ${JSON.stringify(error)}`);});}return new MyParcelable(CONST_NUMBER_1, 'aaa');
};export default class EntryAbility extends UIAbility {// 如果UIAbility第一次启动,在收到call事件后会触发onCreate生命周期回调onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {try {// 监听call事件所需的方法this.callee.on(MSG_SEND_METHOD, funACall);} catch (error) {hilog.error(DOMAIN_NUMBER, TAG, `${MSG_SEND_METHOD} register failed with error ${JSON.stringify(error)}`);}}// 如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期回调onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {hilog.info(DOMAIN_NUMBER, TAG, 'onNewWant Want:', JSON.stringify(want));try {// 监听call事件所需的方法this.callee.on(MSG_SEND_METHOD, funACall);} catch (error) {hilog.error(DOMAIN_NUMBER, TAG, `${MSG_SEND_METHOD} register failed with error ${JSON.stringify(error)}`);}}onDestroy(): void | Promise<void> {try {this.callee.off('PageA');this.callee.off('PageB');} catch (err) {hilog.error(DOMAIN_NUMBER, TAG, `Failed to register callee off. Cause: ${JSON.stringify(err as BusinessError)}`);}}onWindowStageCreate(windowStage: window.WindowStage): void {// Main window is created, set main page for this abilityhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');// Main window is created, set main page for this abilitywindowStage.loadContent('pages/Index', (err) => {if (err.code) {hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');return;}hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');});}onWindowStageDestroy(): void {// Main window is destroyed, release UI related resourceshilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');}onForeground(): void {// Ability has brought to foregroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');}onBackground(): void {// Ability has back to backgroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');}
}

 

卡片数据交互 

卡片数据交互说明

ArkTS卡片框架提供了updateForm接口和requestForm接口主动触发卡片的页面刷新,通过LocalStorageProp确认需要刷新的卡片数据。

接口是否系统能力约束
updateForm

1. 提供方调用。

2. 提供方仅允许刷新自己的卡片,其他提供方的卡片无法刷新。

requestForm

1. 使用方调用。

2. 仅允许刷新添加到当前使用方的卡片,添加到其他使用方的卡片无法刷新。

 卡片定时刷新和定点刷新

当前卡片框架提供了如下几种按时间刷新卡片的方式:

  • 定时刷新:表示在一定时间间隔内调用onUpdateForm的生命周期回调函数自动刷新卡片内容。可以在form_config.json配置文件的updateDuration字段中进行设置。例如,可以将刷新时间设置为每小时一次。

    说明

    1. 在使用定时和定点刷新功能之前,需要在form_config.json配置文件中设置updateEnabled字段为true,以启用周期性刷新功能。

    当配置了updateDuration(定时刷新)后,该设置会优先于scheduledUpdateTime(定点刷新)生效,即使同时配置了两者,定点刷新也会被忽略。

    2. 为减少卡片被动周期刷新进程启动次数,降低卡片刷新功耗,应用市场在安装应用时可以为该应用配置刷新周期,

    也可以为已经安装的应用动态配置刷新周期,用来限制卡片刷新周期的时长,以达到降低周期刷新进程启动次数的目的。

    ● 当配置了updateDuration(定时刷新)后,若应用市场动态配置了该应用的刷新周期,

    卡片框架会将form_config.json文件中配置的刷新周期与应用市场配置的刷新周期进行比较,取较长的刷新周期做为该卡片的定时刷新周期。

    ● 若应用市场未动态配置该应用的刷新周期,则以form_config.json文件中配置的刷新周期为准。

    ● 若该卡片取消定时刷新功能,该规则将无效。

    ● 卡片定时刷新的更新周期单位为30分钟。应用市场配置的刷新周期范围是1~336,即最短为半小时(1 * 30min)刷新一次,最长为一周(336 * 30min)刷新一次。

    ● 该规则从API11开始生效。若小于API11,则以form_config.json文件中配置的刷新周期为准。

{"forms": [{"name": "UpdateDuration","description": "$string:widget_updateduration_desc","src": "./ets/updateduration/pages/UpdateDurationCard.ets","uiSyntax": "arkts","window": {"designWidth": 720,"autoDesignWidth": true},"colorMode": "auto","isDefault": true,"updateEnabled": true,"scheduledUpdateTime": "10:30","updateDuration": 2,"defaultDimension": "2*2","supportDimensions": ["2*2"]}]
}
  • 定点刷新:表示在每天的某个特定时间点自动刷新卡片内容。可以在form_config.json配置文件中的scheduledUpdateTime字段中进行设置。例如,可以将刷新时间设置为每天的上午10点30分。

说明

当同时配置了定时刷新updateDuration和定点刷新scheduledUpdateTime时,定时刷新的优先级更高。如果想要配置定点刷新,则需要将updateDuration配置为0。

{"forms": [{"name": "ScheduledUpdateTime","description": "$string:widget_scheupdatetime_desc","src": "./ets/scheduledupdatetime/pages/ScheduledUpdateTimeCard.ets","uiSyntax": "arkts","window": {"designWidth": 720,"autoDesignWidth": true},"colorMode": "auto","isDefault": true,"updateEnabled": true,"scheduledUpdateTime": "10:30","updateDuration": 0,"defaultDimension": "2*2","supportDimensions": ["2*2"]}]
}
  •  下次刷新:表示指定卡片的下一次刷新时间。可以通过调用setFormNextRefreshTime接口来实现。最短刷新时间为5分钟。例如,可以在接口调用后的5分钟内刷新卡片内容。
import { FormExtensionAbility, formProvider } from '@kit.FormKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';const TAG: string = 'UpdateByTimeFormAbility';
const FIVE_MINUTE: number = 5;
const DOMAIN_NUMBER: number = 0xFF00;export default class UpdateByTimeFormAbility extends FormExtensionAbility {onFormEvent(formId: string, message: string): void {// Called when a specified message event defined by the form provider is triggered.hilog.info(DOMAIN_NUMBER, TAG, `FormAbility onFormEvent, formId = ${formId}, message: ${JSON.stringify(message)}`);try {// 设置过5分钟后更新卡片内容formProvider.setFormNextRefreshTime(formId, FIVE_MINUTE, (err: BusinessError) => {if (err) {hilog.info(DOMAIN_NUMBER, TAG, `Failed to setFormNextRefreshTime. Code: ${err.code}, message: ${err.message}`);return;} else {hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in setFormNextRefreshTiming.');}});} catch (err) {hilog.info(DOMAIN_NUMBER, TAG, `Failed to setFormNextRefreshTime. Code: ${(err as BusinessError).code}, message: ${(err as BusinessError).message}`);}}// ... 
}

在触发定时、定点或下次刷新后,系统会调用FormExtensionAbility的onUpdateForm生命周期回调,在回调中,可以使用updateForm进行提供方刷新卡片。onUpdateForm生命周期回调的使用请参见通过FormExtensionAbility刷新卡片内容。 

说明

  1. 定时刷新有配额限制,每张卡片每天最多通过定时方式触发刷新50次,定时刷新包含卡片配置项updateDuration和调用setFormNextRefreshTime方法两种方式,当达到50次配额后,无法通过定时方式再次触发刷新,刷新次数会在每天的0点重置。

  2. 当前定时刷新使用同一个计时器进行计时,因此卡片定时刷新的第一次刷新会有最多30分钟的偏差。比如第一张卡片A(每隔半小时刷新一次)在3点20分添加成功,定时器启动并每隔半小时触发一次事件,第二张卡片B(每隔半小时刷新一次)在3点40分添加成功,在3点50分定时器事件触发时,卡片A触发定时刷新,卡片B会在下次事件(4点20分)中才会触发。

  3. 定时刷新和定点刷新仅在屏幕亮屏情况下才会触发,在灭屏场景下仅会记录刷新动作,待亮屏时统一进行刷新。

  4. 如果使能了卡片代理刷新,定时刷新和下次刷新不生效。

 刷新本地图片和网络图片

在卡片上通常需要展示本地图片或从网络上下载的图片,获取本地图片和网络图片需要通过FormExtensionAbility来实现,如下示例代码介绍了如何在卡片上显示本地图片和网络图片。

1. 下载网络图片需要使用到网络能力,需要申请ohos.permission.INTERNET权限,配置方式请参见声明权限。

2. 在EntryFormAbility中的onAddForm生命周期回调中实现本地文件的刷新。

import { Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
import { formBindingData, FormExtensionAbility } from '@kit.FormKit';
import { hilog } from '@kit.PerformanceAnalysisKit';const TAG: string = 'WgtImgUpdateEntryFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;export default class WgtImgUpdateEntryFormAbility extends FormExtensionAbility {// 在添加卡片时,打开一个本地图片并将图片内容传递给卡片页面显示onAddForm(want: Want): formBindingData.FormBindingData {// 假设在当前卡片应用的tmp目录下有一个本地图片:head.PNGlet tempDir = this.context.getApplicationContext().tempDir;hilog.info(DOMAIN_NUMBER, TAG, `tempDir: ${tempDir}`);let imgMap: Record<string, number> = {};try {// 打开本地图片并获取其打开后的fdlet file = fileIo.openSync(tempDir + '/' + 'head.PNG');imgMap['imgBear'] = file.fd;} catch (e) {hilog.error(DOMAIN_NUMBER, TAG, `openSync failed: ${JSON.stringify(e as BusinessError)}`);}class FormDataClass {text: string = 'Image: Bear';loaded: boolean = true;// 卡片需要显示图片场景, 必须和下列字段formImages 中的key 'imgBear' 相同。imgName: string = 'imgBear';// 卡片需要显示图片场景, 必填字段(formImages 不可缺省或改名), 'imgBear' 对应 fdformImages: Record<string, number> = imgMap;}let formData = new FormDataClass();// 将fd封装在formData中并返回至卡片页面return formBindingData.createFormBindingData(formData);}//...
}

3. 在EntryFormAbility中的onFormEvent生命周期回调中实现网络文件的刷新。 

import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
import { http } from '@kit.NetworkKit';
import { hilog } from '@kit.PerformanceAnalysisKit';const TAG: string = 'WgtImgUpdateEntryFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;export default class WgtImgUpdateEntryFormAbility extends FormExtensionAbility {async onFormEvent(formId: string, message: string): Promise<void> {let param: Record<string, string> = {'text': '刷新中...'};let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);formProvider.updateForm(formId, formInfo);// 注意:FormExtensionAbility在触发生命周期回调时被拉起,仅能在后台存在5秒// 建议下载能快速下载完成的小文件,如在5秒内未下载完成,则此次网络图片无法刷新至卡片页面上let netFile = 'https://cn-assets.gitee.com/assets/mini_app-e5eee5a21c552b69ae6bf2cf87406b59.jpg'; // 需要在此处使用真实的网络图片下载链接let tempDir = this.context.getApplicationContext().tempDir;let fileName = 'file' + Date.now();let tmpFile = tempDir + '/' + fileName;let imgMap: Record<string, number> = {};class FormDataClass {text: string = 'Image: Bear' + fileName;loaded: boolean = true;// 卡片需要显示图片场景, 必须和下列字段formImages 中的key fileName 相同。imgName: string = fileName;// 卡片需要显示图片场景, 必填字段(formImages 不可缺省或改名), fileName 对应 fdformImages: Record<string, number> = imgMap;}let httpRequest = http.createHttp()let data = await httpRequest.request(netFile);if (data?.responseCode == http.ResponseCode.OK) {try {let imgFile = fileIo.openSync(tmpFile, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);imgMap[fileName] = imgFile.fd;try{let writeLen: number = await fileIo.write(imgFile.fd, data.result as ArrayBuffer);hilog.info(DOMAIN_NUMBER, TAG, "write data to file succeed and size is:" + writeLen);hilog.info(DOMAIN_NUMBER, TAG, 'ArkTSCard download complete: %{public}s', tmpFile);try {let formData = new FormDataClass();let formInfo = formBindingData.createFormBindingData(formData);await formProvider.updateForm(formId, formInfo);hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'FormAbility updateForm success.');} catch (error) {hilog.error(DOMAIN_NUMBER, TAG, `FormAbility updateForm failed: ${JSON.stringify(error)}`);}} catch (err) {hilog.error(DOMAIN_NUMBER, TAG, "write data to file failed with error message: " + err.message + ", error code: " + err.code);} finally {fileIo.closeSync(imgFile);};} catch (e) {hilog.error(DOMAIN_NUMBER, TAG, `openSync failed: ${JSON.stringify(e as BusinessError)}`);}} else {hilog.error(DOMAIN_NUMBER, TAG, `ArkTSCard download task failed`);let param: Record<string, string> = {'text': '刷新失败'};let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);formProvider.updateForm(formId, formInfo);}httpRequest.destroy();}
}

4. 在卡片页面通过backgroundImage属性展示EntryFormAbility传递过来的卡片内容。

let storageWidgetImageUpdate = new LocalStorage();@Entry(storageWidgetImageUpdate)
@Component
struct WidgetImageUpdateCard {@LocalStorageProp('text') text: ResourceStr = $r('app.string.loading');@LocalStorageProp('loaded') loaded: boolean = false;@LocalStorageProp('imgName') imgName: ResourceStr = $r('app.string.imgName');build() {Column() {Column() {Text(this.text).fontColor('#FFFFFF').opacity(0.9).fontSize(12).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(1).margin({ top: '8%', 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(() => {postCardAction(this, {action: 'message',params: {info: 'refreshImage'}});})}.width('100%').height('40%').justifyContent(FlexAlign.Center)}.width('100%').height('100%').backgroundImage(this.loaded ? 'memory://' + this.imgName : $r('app.media.ImageDisp')).backgroundImageSize(ImageSize.Cover)}
}

说明

  • Image组件通过入参(memory://fileName)中的(memory://)标识来进行远端内存图片显示,其中fileName需要和EntryFormAbility传递对象('formImages': {key: fd})中的key相对应。

  • Image组件通过传入的参数是否有变化来决定是否刷新图片,因此EntryFormAbility每次传递过来的imgName都需要不同,连续传递两个相同的imgName时,图片不会刷新。

  • 在卡片上展示的图片,大小需要控制在2MB以内。

 根据卡片状态刷新不同内容

相同的卡片可以添加到桌面上实现不同的功能,比如添加两张桌面的卡片,一张显示杭州的天气,一张显示北京的天气,设置每天早上7点触发定时刷新,卡片需要感知当前的配置是杭州还是北京,然后将对应城市的天气信息刷新到卡片上,以下示例介绍了如何根据卡片的状态动态选择需要刷新的内容。

卡片配置文件:配置每30分钟自动刷新。

{"forms": [{"name": "WidgetUpdateByStatus","description": "$string:UpdateByStatusFormAbility_desc","src": "./ets/widgetupdatebystatus/pages/WidgetUpdateByStatusCard.ets","uiSyntax": "arkts","window": {"designWidth": 720,"autoDesignWidth": true},"colorMode": "auto","isDefault": true,"updateEnabled": true,"scheduledUpdateTime": "10:30","updateDuration": 1,"defaultDimension": "2*2","supportDimensions": ["2*2"]}]
}

卡片页面:卡片具备不同的状态选择,在不同的状态下需要刷新不同的内容,因此在状态发生变化时通过postCardAction通知EntryFormAbility。

let storageUpdateByStatus = new LocalStorage();@Entry(storageUpdateByStatus)
@Component
struct WidgetUpdateByStatusCard {@LocalStorageProp('textA') textA: Resource = $r('app.string.to_be_refreshed');@LocalStorageProp('textB') textB: Resource = $r('app.string.to_be_refreshed');@State selectA: boolean = false;@State selectB: boolean = false;build() {Column() {Column() {Row() {Checkbox({ name: 'checkbox1', group: 'checkboxGroup' }).padding(0).select(false).margin({ left: 26 }).onChange((value: boolean) => {this.selectA = value;postCardAction(this, {action: 'message',params: {selectA: JSON.stringify(value)}});})Text($r('app.string.status_a')).fontColor('#000000').opacity(0.9).fontSize(14).margin({ left: 8 })}.width('100%').padding(0).justifyContent(FlexAlign.Start)Row() {Checkbox({ name: 'checkbox2', group: 'checkboxGroup' }).padding(0).select(false).margin({ left: 26 }).onChange((value: boolean) => {this.selectB = value;postCardAction(this, {action: 'message',params: {selectB: JSON.stringify(value)}});})Text($r('app.string.status_b')).fontColor('#000000').opacity(0.9).fontSize(14).margin({ left: 8 })}.width('100%').position({ y: 32 }).padding(0).justifyContent(FlexAlign.Start)}.position({ y: 12 })Column() {Row() { // 选中状态A才会进行刷新的内容Text($r('app.string.status_a')).fontColor('#000000').opacity(0.4).fontSize(12)Text(this.textA).fontColor('#000000').opacity(0.4).fontSize(12)}.margin({ top: '12px', left: 26, right: '26px' })Row() { // 选中状态B才会进行刷新的内容Text($r('app.string.status_b')).fontColor('#000000').opacity(0.4).fontSize(12)Text(this.textB).fontColor('#000000').opacity(0.4).fontSize(12)}.margin({ top: '12px', bottom: '21px', left: 26, right: '26px' })}.margin({ top: 80 }).width('100%').alignItems(HorizontalAlign.Start)}.width('100%').height('100%').backgroundImage($r('app.media.CardUpdateByStatus')).backgroundImageSize(ImageSize.Cover)}
}
  • EntryFormAbility:将卡片的状态存储在本地数据库中,在刷新事件回调触发时,通过formId获取当前卡片的状态,然后根据卡片的状态选择不同的刷新内容。
    import { Want } from '@kit.AbilityKit';
    import { preferences } from '@kit.ArkData';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { formBindingData, FormExtensionAbility, formInfo, formProvider } from '@kit.FormKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';const TAG: string = 'UpdateByStatusFormAbility';
    const DOMAIN_NUMBER: number = 0xFF00;export default class UpdateByStatusFormAbility extends FormExtensionAbility {onAddForm(want: Want): formBindingData.FormBindingData {let formId: string = '';let isTempCard: boolean;if (want.parameters) {formId = want.parameters[formInfo.FormParam.IDENTITY_KEY].toString();isTempCard = want.parameters[formInfo.FormParam.TEMPORARY_KEY] as boolean;if (isTempCard === false) { // 如果为常态卡片,直接进行信息持久化hilog.info(DOMAIN_NUMBER, TAG, 'Not temp card, init db for:' + formId);let promise: Promise<preferences.Preferences> = preferences.getPreferences(this.context, 'myStore');promise.then(async (storeDB: preferences.Preferences) => {hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded to get preferences.');await storeDB.put('A' + formId, 'false');await storeDB.put('B' + formId, 'false');await storeDB.flush();}).catch((err: BusinessError) => {hilog.info(DOMAIN_NUMBER, TAG, `Failed to get preferences. ${JSON.stringify(err)}`);});}}let formData: Record<string, Object | string> = {};return formBindingData.createFormBindingData(formData);}onRemoveForm(formId: string): void {hilog.info(DOMAIN_NUMBER, TAG, 'onRemoveForm, formId:' + formId);let promise = preferences.getPreferences(this.context, 'myStore');promise.then(async (storeDB) => {hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded to get preferences.');await storeDB.delete('A' + formId);await storeDB.delete('B' + formId);}).catch((err: BusinessError) => {hilog.info(DOMAIN_NUMBER, TAG, `Failed to get preferences. ${JSON.stringify(err)}`);});}// 如果在添加时为临时卡片,则建议转为常态卡片时进行信息持久化onCastToNormalForm(formId: string): void {hilog.info(DOMAIN_NUMBER, TAG, 'onCastToNormalForm, formId:' + formId);let promise: Promise<preferences.Preferences> = preferences.getPreferences(this.context, 'myStore');promise.then(async (storeDB: preferences.Preferences) => {hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded to get preferences.');await storeDB.put('A' + formId, 'false');await storeDB.put('B' + formId, 'false');await storeDB.flush();}).catch((err: BusinessError) => {hilog.info(DOMAIN_NUMBER, TAG, `Failed to get preferences. ${JSON.stringify(err)}`);});}onUpdateForm(formId: string): void {let promise: Promise<preferences.Preferences> = preferences.getPreferences(this.context, 'myStore');promise.then(async (storeDB: preferences.Preferences) => {hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded to get preferences from onUpdateForm.');let stateA = await storeDB.get('A' + formId, 'false');let stateB = await storeDB.get('B' + formId, 'false');// A状态选中则更新textAif (stateA === 'true') {let param: Record<string, string> = {'textA': 'AAA'};let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);await formProvider.updateForm(formId, formInfo);}// B状态选中则更新textBif (stateB === 'true') {let param: Record<string, string> = {'textB': 'BBB'};let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);await formProvider.updateForm(formId, formInfo);}hilog.info(DOMAIN_NUMBER, TAG, `Update form success stateA:${stateA} stateB:${stateB}.`);}).catch((err: BusinessError) => {hilog.info(DOMAIN_NUMBER, TAG, `Failed to get preferences. ${JSON.stringify(err)}`);});}onFormEvent(formId: string, message: string): void {// 存放卡片状态hilog.info(DOMAIN_NUMBER, TAG, 'onFormEvent formId:' + formId + 'msg:' + message);let promise: Promise<preferences.Preferences> = preferences.getPreferences(this.context, 'myStore');promise.then(async (storeDB: preferences.Preferences) => {hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded to get preferences.');let msg: Record<string, string> = JSON.parse(message);if (msg.selectA !== undefined) {hilog.info(DOMAIN_NUMBER, TAG, 'onFormEvent selectA info:' + msg.selectA);await storeDB.put('A' + formId, msg.selectA);}if (msg.selectB !== undefined) {hilog.info(DOMAIN_NUMBER, TAG, 'onFormEvent selectB info:' + msg.selectB);await storeDB.put('B' + formId, msg.selectB);}await storeDB.flush();}).catch((err: BusinessError) => {hilog.info(DOMAIN_NUMBER, TAG, `Failed to get preferences. ${JSON.stringify(err)}`);});}
    }

    说明

    通过本地数据库进行卡片信息的持久化时,建议先在onAddForm生命周期中通过TEMPORARY_KEY判断当前添加的卡片是否为常态卡片:如果是常态卡片,则直接进行卡片信息持久化;如果为临时卡片,则可以在卡片转为常态卡片(onCastToNormalForm)时进行持久化;同时需要在卡片销毁(onRemoveForm)时删除当前卡片存储的持久化信息,避免反复添加删除卡片导致数据库文件持续变大。

卡片生命周期拓展--@ohos.app.form.FormExtensionAbility (FormExtensionAbility)

FormExtensionAbility为卡片扩展模块,提供卡片创建、销毁、刷新等生命周期回调。

说明

本模块首批接口从API version 9开始支持。后续版本的新增接口,采用上角标单独标记接口的起始版本。

如下模块不支持在FormExtensionAbility引用,可能会导致程序异常退出。

  • @ohos.ability.particleAbility (ParticleAbility模块)
  • @ohos.multimedia.audio (音频管理)
  • @ohos.multimedia.camera (相机管理)
  • @ohos.multimedia.media (媒体服务)
  • @ohos.resourceschedule.backgroundTaskManager (后台任务管理)

属性

模型约束: 此接口仅可在Stage模型下使用。

系统能力: SystemCapability.Ability.Form

名称类型可读可写说明
contextFormExtensionContext

FormExtensionAbility的上下文环境,继承自ExtensionContext。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

onAddForm

onAddForm(want: Want): formBindingData.FormBindingData

卡片提供方接收创建卡片的通知接口。

模型约束: 此接口仅可在Stage模型下使用。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.Ability.Form

参数:

参数名类型必填说明
wantWant当前卡片相关的Want类型信息,包括卡片ID、卡片名称、卡片样式等。这些卡片信息必须作为持久数据进行管理,以便后续更新和删除卡片。

返回值:

类型说明
formBindingData.FormBindingDataformBindingData.FormBindingData对象,卡片要显示的数据。

示例:

import { formBindingData, FormExtensionAbility } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';export default class MyFormExtensionAbility extends FormExtensionAbility {/*** 卡片提供方接收创建卡片的通知接口。* @param want 请求创建卡片的能力。* want表示获取卡片状态的描述。描述包括Bundle名称、能力名称、模块名称、卡片名和卡片维度* @return formBindingData.FormBindingData对象,卡片要显示的数据。* */onAddForm(want: Want) {console.log(`${TAG} onAddForm, want: ${want.abilityName}`);let dataObj1: Record<string, string> = {'temperature': '11c','time': '11:00'};let obj1: formBindingData.FormBindingData = formBindingData.createFormBindingData(dataObj1);return obj1;}
}

onCastToNormalForm

onCastToNormalForm(formId: string): void

卡片提供方接收临时卡片转常态卡片的通知接口。

模型约束: 此接口仅可在Stage模型下使用。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.Ability.Form

参数:

参数名类型必填说明
formIdstring请求转换为常态的卡片标识。

示例:

import { FormExtensionAbility } from '@kit.FormKit';// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';export default class MyFormExtensionAbility extends FormExtensionAbility {
/*** 卡片提供方接收临时卡片转常态卡片的通知接口。* @param formId 请求转换为常态的卡片标识。* @return 无返回值。* */
onCastToNormalForm(formId: string) {console.log(`${TAG} onCastToNormalForm, formId: ${formId}`);
}
};

onUpdateForm

onUpdateForm(formId: string, wantParams?: Record<string, Object>): void

卡片提供方接收携带参数的更新卡片的通知接口。获取最新数据后调用formProvider的updateForm接口刷新卡片数据。

模型约束: 此接口仅可在Stage模型下使用。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.Ability.Form

参数:

参数名类型必填说明
formIdstring请求更新的卡片ID。
wantParams12+Record<string, Object>更新参数。

示例:

import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
import { BusinessError } from '@kit.BasicServicesKit';// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';export default class MyFormExtensionAbility extends FormExtensionAbility {
/***卡片提供方接收携带参数的更新卡片的通知接口。获取最新数据后调用formProvider的updateForm接口刷新卡片数据。* @param formId 卡片标识,用于更新卡片数据。* @param wantParams 携带的参数。* */onUpdateForm(formId: string, wantParams?: Record<string, Object>) {console.log(`${TAG} onUpdateForm, formId: ${formId},wantPara: ${wantParams?.['ohos.extra.param.key.host_bg_inverse_color']}`);let param: Record<string, string> = {'temperature': '22c','time': '22:00'}let obj2: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);formProvider.updateForm(formId, obj2).then(() => {console.log(`${TAG} context updateForm`);}).catch((error: BusinessError) => {console.error(`${TAG} context updateForm failed, data: ${error}`);});}
};

onChangeFormVisibility

onChangeFormVisibility(newStatus: Record<string, number>): void

卡片提供方接收修改可见性的通知接口。

该接口仅对系统应用生效,且需要将formVisibleNotify配置为true。

模型约束: 此接口仅可在Stage模型下使用。

系统能力: SystemCapability.Ability.Form

参数:

参数名类型必填说明
newStatusRecord<string, number>请求修改的卡片标识和可见状态。

示例:

import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
import { BusinessError } from '@kit.BasicServicesKit';// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';// ArkTS规范中ets文件无法使用Object.keys和for..in...获取Object的key值,请使用自定义函数getObjKeys代替。
// 使用时请将此函数单独抽离至一个ts文件中并导出,在需要用到的ets文件中导入此函数后使用。
function getObjKeys(obj: Object): string[] {let keys = Object.keys(obj);return keys;
}export default class MyFormExtensionAbility extends FormExtensionAbility {
/*** 卡片提供方接收修改可见性的通知接口。* 该接口仅对系统应用生效,且需要将formVisibleNotify配置为true。* @param newStatus 卡片可见性变化的状态。* */nChangeFormVisibility(newStatus: Record<string, number>) {console.log(`${TAG} onChangeFormVisibility, newStatus: ${newStatus}`);let param: Record<string, string> = {'temperature': '22c','time': '22:00'}let obj2: formBindingData.FormBindingData = formBindingData.createFormBindingData(param);let keys: string[] = getObjKeys(newStatus);for (let i: number = 0; i < keys.length; i++) {console.log(`${TAG} onChangeFormVisibility, key: ${keys[i]}, value= ${newStatus[keys[i]]}`);formProvider.updateForm(keys[i], obj2).then(() => {console.log(`${TAG} context updateForm`);}).catch((error: BusinessError) => {console.error(`Operation updateForm failed. Cause: ${JSON.stringify(error)}`);});}}
};

onFormEvent

onFormEvent(formId: string, message: string): void

卡片提供方接收处理卡片事件的通知接口。

模型约束: 此接口仅可在Stage模型下使用。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.Ability.Form

参数:

参数名类型必填说明
formIdstring请求触发事件的卡片标识。
messagestring事件消息。

示例:

import { FormExtensionAbility } from '@kit.FormKit';// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';export default class MyFormExtensionAbility extends FormExtensionAbility {
/*** 卡片提供方接收处理卡片事件的通知接口。* @param formId 卡片标识。* @param message 卡片事件的消息内容。* */onFormEvent(formId: string, message: string) {console.log(`${TAG} onFormEvent, formId: ${formId}, message: ${message}`);}
};

onRemoveForm

onRemoveForm(formId: string): void

卡片提供方接收销毁卡片的通知接口。

模型约束: 此接口仅可在Stage模型下使用。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.Ability.Form

参数:

参数名类型必填说明
formIdstring请求销毁的卡片标识。

示例:

import { FormExtensionAbility } from '@kit.FormKit';// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';export default class MyFormExtensionAbility extends FormExtensionAbility {/*** 卡片提供方接收卡片被移除的通知接口。* @param formId 卡片标识。* */onRemoveForm(formId: string) {console.log(`${TAG} onRemoveForm, formId: ${formId}`);}
};

onConfigurationUpdate

onConfigurationUpdate(newConfig: Configuration): void

当系统配置更新时调用。

仅当前formExtensionAbility存活时更新配置才会触发此生命周期。需要注意:formExtensionAbility创建后10秒内无操作将会被清理。

模型约束: 此接口仅可在Stage模型下使用。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.Ability.Form

参数:

参数名类型必填说明
newConfigConfiguration表示需要更新的配置信息。

示例:

import { FormExtensionAbility } from '@kit.FormKit';
import { Configuration } from '@kit.AbilityKit';// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';export default class MyFormExtensionAbility extends FormExtensionAbility {/*** 卡片提供方接收配置更新的通知接口。* @param newConfig 新的配置。* */onConfigurationUpdate(newConfig: Configuration) {// 仅当前formExtensionAbility存活时更新配置才会触发此生命周期。// 需要注意:formExtensionAbility创建后10秒内无操作将会被清理。console.log(`${TAG} onConfigurationUpdate, config: ${JSON.stringify(newConfig)}`);}
};

onAcquireFormState

onAcquireFormState?(want: Want): formInfo.FormState

卡片提供方接收查询卡片状态通知接口,默认返回卡片初始状态(该方法可以选择性重写)。

模型约束: 此接口仅可在Stage模型下使用。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.Ability.Form

参数:

参数名类型必填说明
wantWantwant表示获取卡片状态的描述。描述包括Bundle名称、能力名称、模块名称、卡片名和卡片维度。

示例:

import { FormExtensionAbility, formInfo } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';export default class MyFormExtensionAbility extends FormExtensionAbility {/*** 卡片提供方接收卡片状态查询的通知接口。* @param want 查询卡片状态的能力。* want表示获取卡片状态的描述。描述包括Bundle名称、能力名称、模块名称、卡片名和卡片维度* */onAcquireFormState(want: Want) {console.log(`${TAG} onAcquireFormState, want: ${want}`);return formInfo.FormState.UNKNOWN;}
};

onStop

onStop?(): void

当卡片提供方的卡片进程退出时,触发该回调。

模型约束: 此接口仅可在Stage模型下使用。

元服务API: 从API version 12开始,该接口支持在元服务中使用。

系统能力: SystemCapability.Ability.Form

示例:

import { FormExtensionAbility } from '@kit.FormKit';// 日志标签。方便查看日志。
const TAG: string = 'EntryFormAbility';export default class MyFormExtensionAbility extends FormExtensionAbility {/*** 当卡片提供方的卡片进程退出时,触发该回调。* */onStop() {console.log(`${TAG} onStop`);}
}

绘制卡片页面拓展--使用 DevEco CodeGenie 绘制卡片样式

 

这里预览失败了,可能是我电脑卡住了,但是没事,这里有代码 ,可以点击</>或者保存到工程

代码报错了不着急,因为那些图片我们没有,只能自己准备图片了 

如果它能把图片一起打包过来就完美了

效果图,说实话,随便找的图片所以看着有点丑,但是把确实能用 

静态卡片能力拓展 

供静态卡片交互组件,用于静态卡片内部和提供方应用间的交互,当前支持router、message和call三种类型的事件。

说明

  • 该组件从API Version 10开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。

  • 该组件仅可以在静态卡片中使用。

权限

子组件

支持单个子组件

接口

FormLink(options: FormLinkOptions)

卡片能力: 从API version 10开始,该接口支持在ArkTS卡片中使用。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名类型必填说明
optionsFormLinkOptions定义卡片信息

FormLinkOptions对象说明

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

名称类型必填说明
actionstring

action的类型,支持三种预定义的类型:

- router:跳转到提供方应用的指定UIAbility。

- message:自定义消息,触发后会调用提供方FormExtensionAbility的onFormEvent()生命周期回调。

- call:后台启动提供方应用。触发后会拉起提供方应用的指定UIAbility(仅支持launchType为singleton的UIAbility,即启动模式为单实例的UIAbility),但不会调度到前台。提供方应用需要具备后台运行权限(ohos.permission.KEEP_BACKGROUND_RUNNING)。

说明:

不推荐使用router事件刷新卡片UI。

卡片能力: 从API version 10开始,该接口支持在ArkTS卡片中使用。

moduleNamestring

action为router / call 类型时跳转的模块名。

卡片能力: 从API version 10开始,该接口支持在ArkTS卡片中使用。

bundleNamestring

action为router / call 类型时跳转的包名。

卡片能力: 从API version 10开始,该接口支持在ArkTS卡片中使用。

abilityNamestring

action为router / call 类型时跳转的UIAbility名。

卡片能力: 从API version 10开始,该接口支持在ArkTS卡片中使用。

uri11+string

action为router 类型时跳转的UIAbility的统一资源标识符。uri和abilityName同时存在时,abilityName优先。

卡片能力: 从API version 11开始,该接口支持在ArkTS卡片中使用。

paramsObject

当前action携带的额外参数,内容使用JSON格式的键值对形式。call 类型时需填入参数'method',且类型需要为string类型,用于触发UIAbility中对应的方法。

说明:

不推荐通过params传递卡片内部的状态变量。

卡片能力: 从API version 10开始,该接口支持在ArkTS卡片中使用。

属性

支持通用属性

事件

不支持通用事件

示例 

@Entry
@Component
struct FormLinkDemo {build() {Column() {Text("这是一个静态卡片").fontSize(20).margin(10)// router事件用于静态卡片跳转到对应的UIAbilityFormLink({action: "router",abilityName: "EntryAbility",params: {'message': 'testForRouter' // 自定义要发送的message}}) {Button("router event").width(120)}.margin(10)// message事件触发FormExtensionAbility的onFormEvent生命周期FormLink({action: "message",abilityName: "EntryAbility",params: {'message': 'messageEvent' // 自定义要发送的message}}) {Button("message event").width(120)}.margin(10)// call事件用于触发UIAbility中对应的方法FormLink({action: "call",abilityName: "EntryAbility",params: {'method': 'funA', // 在EntryAbility中调用的方法名'num': 1 // 需要传递的其他参数}}) {Button("call event").width(120)}.margin(10)// router事件用于静态卡片deeplink跳转到对应的UIAbilityFormLink({action: "router",uri: 'example://uri.ohos.com/link_page',params: {message: 'router msg for static uri deeplink' // 自定义要发送的message}}) {Button("deeplink event").width(120)}.margin(10)}.justifyContent(FlexAlign.Center).width('100%').height('100%')}
}

 

待跳转应用 module.json5 uris 配置示例: 

"abilities": [{"skills": [{"uris": [{"scheme": "example","host": "uri.ohos.com","path": "link_page"},]}],}
]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/476101.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SSRF漏洞利用

2.漏洞利用 2.1 SSRF中URL的伪协议 file:// 从⽂件系统中获取⽂件内容&#xff0c;如&#xff0c;file:///etc/passwd dict:// 字典服务器协议&#xff0c;访问字典资源&#xff0c;如dict://ip:6379/info sftp:// ssh⽂件传输协议或安全⽂件传输协议 ldap:// 轻量级⽬录访问…

nacos镜像启动时候报Public Key Retrieval is not allowed

在nacos的配置文件里加上一句allowPublicKeyRetrievaltrue

【pytorch-04】:线性回归案例(手动构建)

文章目录 1 构建数据集2 构建假设函数3 损失函数4 优化方法5 训练函数6.总结 1 构建数据集 为什么构建数据加载器&#xff1f; 在进行训练的时候都是采用的不是全部的数据&#xff0c;而是采用一个batch_size的数据进行训练&#xff0c;每次向模型当中送入batch_size数据&#…

实验室管理效率提升:Spring Boot技术的力量

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

STM32H7开发笔记(2)——H7外设之多路定时器中断

STM32H7开发笔记&#xff08;2&#xff09;——H7外设之多路定时器中断 文章目录 STM32H7开发笔记&#xff08;2&#xff09;——H7外设之多路定时器中断0.引言1.CubeMX配置2.软件编写 0.引言 本文PC端采用Win11STM32CubeMX4.1.0.0Keil5.24.2的配置&#xff0c;硬件使用STM32H…

springboot基于微信小程序的旧衣回收系统的设计与实现

摘 要 微信小程序的旧衣回收系统是一种专为环保生活设计的应用软件。这款小程序的主要功能包括&#xff1a;系统首页、个人中心、用户管理、回收人员管理、旧衣服分类管理、旧衣信息管理、回收预约管理、回收派单管理、回收订单管理、积分商品管理、积分兑换管理、管理员管理、…

路由缓存后跳转到新路由时,上一路由中的tip信息框不销毁问题解决

上一路由tip信息框不销毁问题解决 路由缓存篇问题描述及截图解决思路关键代码 路由缓存篇 传送门 问题描述及截图 路由缓存后跳转新路由时&#xff0c;上一个路由的tip信息框没销毁。 解决思路 在全局路由守卫中获取DOM元素&#xff0c;通过css去控制 关键代码 修改文…

40分钟学 Go 语言高并发:并发下载器开发实战教程

并发下载器开发实战教程 一、系统设计概述 1.1 功能需求表 功能模块描述技术要点分片下载将大文件分成多个小块并发下载goroutine池、分片算法断点续传支持下载中断后继续下载文件指针定位、临时文件管理进度显示实时显示下载进度和速度进度计算、速度统计错误处理处理下载过…

【前端】JavaScript中的indexOf()方法详解:基础概念与背后的应用思路

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: 前端 文章目录 &#x1f4af;前言&#x1f4af;什么是indexOf()方法&#xff1f;参数解释返回值示例 &#x1f4af;indexOf() 方法的工作原理&#x1f4af;特殊案例&#xff1a;undefined 的处理示例代码图示解释 &#x1f4af;i…

HarmonyOS4+NEXT星河版入门与项目实战------Button组件

文章目录 1、控件图解2、案例实现1、代码实现2、代码解释3、运行效果4、总结1、控件图解 这里我们用一张完整的图来汇整 Button 的用法格式、属性和事件,如下所示: 按钮默认类型就是胶囊类型。 2、案例实现 这里我们实现一个根据放大和缩小按钮来改变图片大小的功能。 功…

WPF窗体基本知识-笔记-命名空间

窗体程序关闭方式 命名空间:可以理解命名空间的作用为引用下面的控件对象 给控件命名:一般都用x:Name,也可以用Name但是有的控件不支持 布局控件(容器)的类型 布局控件继承于Panel的控件,其中下面的border不是布局控件,panel是抽象类 在重叠的情况下,Zindex值越大的就在上面 Z…

【Qt】QComboBox设置默认显示为空

需求 使用QComboBox&#xff0c;遇到一个小需求是&#xff0c;想要设置未点击出下拉列表时&#xff0c;内容显示为空。并且不想在下拉列表中添加一个空条目。 实现 使用setPlaceholderText()接口。我们先来看下帮助文档&#xff1a; 这里说的是&#xff0c;placeholderText是…

音频信号采集前端电路分析

音频信号采集前端电路 一、实验要求 要求设计一个声音采集系统 信号幅度&#xff1a;0.1mVpp到1Vpp 信号频率&#xff1a;100Hz到16KHz 搭建一个带通滤波器&#xff0c;滤除高频和低频部分 ADC采用套件中的AD7920&#xff0c;转换率设定为96Ksps &#xff1b;96*161536 …

[开源]1.2K star!中后台方向的低代码可视化平台,超赞!

大家好&#xff0c;我是JavaCodexPro&#xff01; “时间就是金钱&#xff0c;效率就是生命”&#xff0c;快速搭建高质量中后台的低代码可视化搭建平台尤为重要&#xff01; 今天JavaCodexPro给大家分享一款超赞的低代码可视化搭建平台 - Marsview &#xff0c;旨在简化开发…

Leetcode 完全二叉树的节点个数

不讲武德的解法 java 实现 class Solution {public int countNodes(TreeNode root) {if(root null) return 0;return countNodes(root.left) countNodes(root.right) 1;} }根据完全二叉树和满二叉树的性质做 class Solution {public int countNodes(TreeNode root) {if (r…

基于CVE安全公告号,全面修复麒麟ARM系统OpenSSH漏洞

前言&#xff1a;负责的其中一个从0开始搭建的某生产项目上线前需要做青藤安全扫描&#xff0c;过了后才允许上线&#xff0c;该项目从操作系统、中间件、数据库、容器等全国产信创化&#xff0c;公司公告为CVE安全公告号&#xff0c;而修复漏洞的责任归我&#xff0c;需要根据…

【每日 C/C++ 问题】

一、什么是 C 中的初始化列表&#xff1f;它的作用是什么&#xff1f; 作用&#xff1a;c提供了初始化列表语法&#xff0c;用来初始化属性 语法&#xff1a;构造函数&#xff08;&#xff09;&#xff1a;属性1&#xff08;值1&#xff09;&#xff0c;属性2&#xff08;值…

【前端知识】Javascript前端框架Vue入门

前端框架VUE入门 概述基础语法介绍组件特性组件注册Props 属性声明事件组件 v-model(双向绑定)插槽Slots内容与出口 组件生命周期样式文件使用1. 直接在<style>标签中写CSS2. 引入外部CSS文件3. 使用CSS预处理器4. 在main.js中全局引入CSS文件5. 使用CSS Modules6. 使用P…

【代码pycharm】动手学深度学习v2-04 数据操作 + 数据预处理

数据操作 数据预处理 1.数据操作运行结果 2.数据预处理实现运行结果 第四课链接 1.数据操作 import torch # 张量的创建 x1 torch.arange(12) print(1.有12个元素的张量&#xff1a;\n,x1) print(2.张量的形状&#xff1a;\n,x1.shape) print(3.张量中元素的总数&#xff1…

《Python浪漫的烟花表白特效》

一、背景介绍 烟花象征着浪漫与激情&#xff0c;将它与表白结合在一起&#xff0c;会创造出别具一格的惊喜效果。使用Python的turtle模块&#xff0c;我们可以轻松绘制出动态的烟花特效&#xff0c;再配合文字表白&#xff0c;打造一段专属的浪漫体验。 接下来&#xff0c;让…