1# 服务卡片开发指导(FA模型) 2 3 4## 卡片概述 5 6服务卡片(以下简称“卡片”)是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达、减少体验层级的目的。 7 8卡片常用于嵌入到其他应用(当前只支持系统应用)中作为其界面的一部分显示,并支持拉起页面、发送消息等基础的交互功能。 9 10卡片的基本概念: 11 12- 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。 13 14- 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。 15 16- 卡片提供方:提供卡片显示内容原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。 17 18 19## 运作机制 20 21卡片框架的运作机制如图1所示。 22 23 **图1** 卡片框架运作机制(FA模型) 24 25 26卡片使用方包含以下模块: 27 28- 卡片使用:包含卡片的创建、删除、请求更新等操作。 29 30- 通信适配层:由OpenHarmony SDK提供,负责与卡片管理服务通信,用于将卡片的相关操作到卡片管理服务。 31 32卡片管理服务包含以下模块: 33 34- 周期性刷新:在卡片添加后,根据卡片的刷新策略启动定时任务周期性触发卡片的刷新。 35 36- 卡片缓存管理:在卡片添加到卡片管理服务后,对卡片的视图信息进行缓存,以便下次获取卡片时可以直接返回缓存数据,降低时延。 37 38- 卡片生命周期管理:对于卡片切换到后台或者被遮挡时,暂停卡片的刷新;以及卡片的升级/卸载场景下对卡片数据的更新和清理。 39 40- 卡片使用方对象管理:对卡片使用方的RPC对象进行管理,用于使用方请求进行校验以及对卡片更新后的回调处理。 41 42- 通信适配层:负责与卡片使用方和提供方进行RPC通信。 43 44卡片提供方包含以下模块: 45 46- 卡片服务:由卡片提供方开发者实现,开发者实现生命周期处理创建卡片、更新卡片以及删除卡片等请求,提供相应的卡片服务。 47 48- 卡片提供方实例管理模块:由卡片提供方开发者实现,负责对卡片管理服务分配的卡片实例进行持久化管理。 49 50- 通信适配层:由OpenHarmony SDK提供,负责与卡片管理服务通信,用于将卡片的更新数据主动推送到卡片管理服务。 51 52> **说明:** 53> 实际开发时只需要作为卡片提供方进行卡片内容的开发,卡片使用方和卡片管理服务由系统自动处理。 54 55 56## 接口说明 57 58FormAbility生命周期接口如下: 59 60| 接口名 | 描述 | 61| -------- | -------- | 62| onCreate(want: Want): formBindingData.FormBindingData | 卡片提供方接收创建卡片的通知接口。 | 63| onCastToNormal(formId: string): void | 卡片提供方接收临时卡片转常态卡片的通知接口。 | 64| onUpdate(formId: string): void | 卡片提供方接收更新卡片的通知接口。 | 65| onVisibilityChange(newStatus: Record<string, number>): void | 卡片提供方接收修改可见性的通知接口。 | 66| onEvent(formId: string, message: string): void | 卡片提供方接收处理卡片事件的通知接口。 | 67| onDestroy(formId: string): void | 卡片提供方接收销毁卡片的通知接口。 | 68| onAcquireFormState?(want: Want): formInfo.FormState | 卡片提供方接收查询卡片状态的通知接口。 | 69| onShare?(formId: string): {[key: string]: any} | 卡片提供方接收卡片分享的通知接口。 | 70| onShareForm?(formId: string): Record<string, Object> | 卡片提供方接收卡片分享的通知接口。推荐使用该接口替代onShare接口。如果了实现该接口,onShare将不再被回调。 | 71 72FormProvider类有如下API接口,具体的API介绍详见[接口文档](../reference/apis-form-kit/js-apis-app-form-formProvider.md)。 73 74 75| 接口名 | 描述 | 76| -------- | -------- | 77| setFormNextRefreshTime(formId: string, minute: number, callback: AsyncCallback<void>): void; | 设置指定卡片的下一次更新时间。 | 78| setFormNextRefreshTime(formId: string, minute: number): Promise<void>; | 设置指定卡片的下一次更新时间,以promise方式返回。 | 79| updateForm(formId: string, formBindingData: FormBindingData, callback: AsyncCallback<void>): void; | 更新指定的卡片。 | 80| updateForm(formId: string, formBindingData: FormBindingData): Promise<void>; | 更新指定的卡片,以promise方式返回。 | 81 82 83FormBindingData类有如下API接口,具体的API介绍详见[接口文档](../reference/apis-form-kit/js-apis-app-form-formBindingData.md)。 84 85 86| 接口名 | 描述 | 87| -------- | -------- | 88| createFormBindingData(obj?: Object \| string): FormBindingData | 创建一个FormBindingData对象。 | 89 90 91## 开发步骤 92 93FA卡片开发,即基于[FA模型](../application-models/fa-model-development-overview.md)的卡片提供方开发,主要涉及如下关键步骤: 94 95- [实现卡片生命周期接口](#实现卡片生命周期接口):开发FormAbility生命周期回调函数。 96 97- [配置卡片配置文件](#配置卡片配置文件):配置应用配置文件config.json。 98 99- [卡片信息的持久化](#卡片信息的持久化):对卡片信息进行持久化管理。 100 101- [卡片数据交互](#卡片数据交互):通过updateForm()更新卡片显示的信息。 102 103- [开发卡片页面](#开发卡片页面):使用HML+CSS+JSON开发JS卡片页面。 104 105- [开发卡片事件](#开发卡片事件):为卡片添加router事件和message事件。 106 107 108### 实现卡片生命周期接口 109 110创建FA模型的卡片,需实现卡片的生命周期接口。先参考<!--RP1-->[创建服务卡片](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-service-widget-V5)<!--RP1End-->生成服务卡片模板。 111 1121. 在form.ts中,导入相关模块 113 114 ```ts 115 import type featureAbility from '@ohos.ability.featureAbility'; 116 import type Want from '@ohos.app.ability.Want'; 117 import formBindingData from '@ohos.app.form.formBindingData'; 118 import formInfo from '@ohos.app.form.formInfo'; 119 import formProvider from '@ohos.app.form.formProvider'; 120 import dataPreferences from '@ohos.data.preferences'; 121 import hilog from '@ohos.hilog'; 122 ``` 123 1242. 在form.ts中,实现卡片生命周期接口 125 126 ```ts 127 const TAG: string = '[Sample_FAModelAbilityDevelop]'; 128 const domain: number = 0xFF00; 129 130 const DATA_STORAGE_PATH: string = 'form_store'; 131 let storeFormInfo = async (formId: string, formName: string, tempFlag: boolean, context: featureAbility.Context): Promise<void> => { 132 // 此处仅对卡片ID:formId,卡片名:formName和是否为临时卡片:tempFlag进行了持久化 133 let formInfo: Record<string, string | number | boolean> = { 134 'formName': 'formName', 135 'tempFlag': 'tempFlag', 136 'updateCount': 0 137 }; 138 try { 139 const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH); 140 // put form info 141 await storage.put(formId, JSON.stringify(formInfo)); 142 hilog.info(domain, TAG, `storeFormInfo, put form info successfully, formId: ${formId}`); 143 await storage.flush(); 144 } catch (err) { 145 hilog.error(domain, TAG, `failed to storeFormInfo, err: ${JSON.stringify(err as Error)}`); 146 } 147 }; 148 149 let deleteFormInfo = async (formId: string, context: featureAbility.Context) => { 150 try { 151 const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH); 152 // del form info 153 await storage.delete(formId); 154 hilog.info(domain, TAG, `deleteFormInfo, del form info successfully, formId: ${formId}`); 155 await storage.flush(); 156 } catch (err) { 157 hilog.error(domain, TAG, `failed to deleteFormInfo, err: ${JSON.stringify(err)}`); 158 } 159 } 160 161 class LifeCycle { 162 onCreate: (want: Want) => formBindingData.FormBindingData = (want) => ({ data: '' }); 163 onCastToNormal: (formId: string) => void = (formId) => { 164 }; 165 onUpdate: (formId: string) => void = (formId) => { 166 }; 167 onVisibilityChange: (newStatus: Record<string, number>) => void = (newStatus) => { 168 let obj: Record<string, number> = { 169 'test': 1 170 }; 171 return obj; 172 }; 173 onEvent: (formId: string, message: string) => void = (formId, message) => { 174 }; 175 onDestroy: (formId: string) => void = (formId) => { 176 }; 177 onAcquireFormState?: (want: Want) => formInfo.FormState = (want) => (0); 178 onShareForm?: (formId: string) => Record<string, Object> = (formId) => { 179 let obj: Record<string, number> = { 180 'test': 1 181 }; 182 return obj; 183 }; 184 } 185 186 let obj: LifeCycle = { 187 onCreate(want: Want) { 188 hilog.info(domain, TAG, 'FormAbility onCreate'); 189 if (want.parameters) { 190 let formId = String(want.parameters['ohos.extra.param.key.form_identity']); 191 let formName = String(want.parameters['ohos.extra.param.key.form_name']); 192 let tempFlag = Boolean(want.parameters['ohos.extra.param.key.form_temporary']); 193 // 将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用 194 // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例 195 hilog.info(domain, TAG, 'FormAbility onCreate' + formId); 196 storeFormInfo(formId, formName, tempFlag, this.context); 197 } 198 199 // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类 200 let obj: Record<string, string> = { 201 'title': 'titleOnCreate', 202 'detail': 'detailOnCreate' 203 }; 204 let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj); 205 return formData; 206 }, 207 onCastToNormal(formId: string) { 208 // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理 209 hilog.info(domain, TAG, 'FormAbility onCastToNormal'); 210 }, 211 onUpdate(formId: string) { 212 // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新 213 hilog.info(domain, TAG, 'FormAbility onUpdate'); 214 let obj: Record<string, string> = { 215 'title': 'titleOnUpdate', 216 'detail': 'detailOnUpdate' 217 }; 218 let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj); 219 // 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变 220 formProvider.updateForm(formId, formData).catch((error: Error) => { 221 hilog.error(domain, TAG, 'FormAbility updateForm, error:' + JSON.stringify(error)); 222 }); 223 }, 224 onVisibilityChange(newStatus: Record<string, number>) { 225 // 使用方发起可见或者不可见通知触发,提供方需要做相应的处理,仅系统应用生效 226 hilog.info(domain, TAG, 'FormAbility onVisibilityChange'); 227 }, 228 onEvent(formId: string, message: string) { 229 // 若卡片支持触发事件,则需要重写该方法并实现对事件的触发 230 let obj: Record<string, string> = { 231 'title': 'titleOnEvent', 232 'detail': 'detailOnEvent' 233 }; 234 let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj); 235 // 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变 236 formProvider.updateForm(formId, formData).catch((error: Error) => { 237 hilog.error(domain, TAG, 'FormAbility updateForm, error:' + JSON.stringify(error)); 238 }); 239 hilog.info(domain, TAG, 'FormAbility onEvent'); 240 }, 241 onDestroy(formId: string) { 242 // 删除卡片实例数据 243 hilog.info(domain, TAG, 'FormAbility onDestroy'); 244 // 删除之前持久化的卡片实例数据 245 // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例 246 deleteFormInfo(formId, this.context); 247 }, 248 onAcquireFormState(want: Want) { 249 hilog.info(domain, TAG, 'FormAbility onAcquireFormState'); 250 return formInfo.FormState.READY; 251 } 252 }; 253 254 export default obj; 255 ``` 256 257> **说明:** 258> FormAbility不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务。 259 260### 配置卡片配置文件 261 262卡片需要在应用配置文件config.json中进行配置。 263 264- js模块,用于对应卡片的js相关资源,内部字段结构说明: 265 | 属性名称 | 含义 | 数据类型 | 是否可缺省 | 266 | -------- | -------- | -------- | -------- | 267 | name | 表示JS Component的名字。该标签不可缺省,默认值为default。 | 字符串 | 否 | 268 | pages | 表示JS Component的页面用于列举JS Component中每个页面的路由信息[页面路径+页面名称]。该标签不可缺省,取值为数组,数组第一个元素代表JS FA首页。 | 数组 | 否 | 269 | window | 用于定义与显示窗口相关的配置。 | 对象 | 可缺省。 | 270 | type | 表示JS应用的类型。取值范围如下:<br/>normal:标识该JS Component为应用实例。<br/>form:标识该JS Component为卡片实例。 | 字符串 | 可缺省,缺省值为“normal” 。| 271 | mode | 定义JS组件的开发模式。 | 对象 | 可缺省,缺省值为空。 | 272 273 配置示例如下: 274 275 276 ```json 277 "js": [ 278 // ... 279 { 280 "name": "widget", 281 "pages": [ 282 "pages/index/index" 283 ], 284 "window": { 285 "designWidth": 720, 286 "autoDesignWidth": true 287 }, 288 "type": "form" 289 } 290 ] 291 ``` 292 293- abilities模块,用于对应卡片的FormAbility,内部字段结构说明: 294 | 属性名称 | 含义 | 数据类型 | 是否可缺省 | 295 | -------- | -------- | -------- | -------- | 296 | name | 表示卡片的类名。字符串最大长度为127字节。 | 字符串 | 否 | 297 | description | 表示卡片的描述。取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。字符串最大长度为255字节。 | 字符串 | 可缺省,缺省为空。 | 298 | isDefault | 表示该卡片是否为默认卡片,每个Ability有且只有一个默认卡片。<br/>true:默认卡片。<br/>false:非默认卡片。 | 布尔值 | 否 | 299 | type | 表示卡片的类型。取值范围如下:<br/>JS:JS卡片。 | 字符串 | 否 | 300 | colorMode | 表示卡片的主题样式,取值范围如下:<br/>auto:自适应。<br/>dark:深色主题。<br/>light:浅色主题。 | 字符串 | 可缺省,缺省值为“auto”。 | 301 | supportDimensions | 表示卡片支持的外观规格,取值范围:<br/>1 \* 2:表示1行2列的二宫格。<br/>2 \* 2:表示2行2列的四宫格。<br/>2 \* 4:表示2行4列的八宫格。<br/>4 \* 4:表示4行4列的十六宫格。 | 字符串数组 | 否 | 302 | defaultDimension | 表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中。 | 字符串 | 否 | 303 | updateEnabled | 表示卡片是否支持周期性刷新,取值范围:<br/>true:表示支持周期性刷新,可以在定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)两种方式任选其一,优先选择定时刷新。<br/>false:表示不支持周期性刷新。 | 布尔类型 | 否 | 304 | scheduledUpdateTime | 表示卡片的定点刷新的时刻,采用24小时制,精确到分钟。<br/>updateDuration参数优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。 | 字符串 | 可缺省,缺省值为“0:0”。 | 305 | updateDuration | 表示卡片定时刷新的更新周期,单位为30分钟,取值为自然数。<br/>当取值为0时,表示该参数不生效。<br/>当取值为正整数N时,表示刷新周期为30\*N分钟。<br/>updateDuration参数优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。 | 数值 | 可缺省,缺省值为“0”。 | 306 | formConfigAbility | 表示卡片的配置跳转链接,采用URI格式。 | 字符串 | 可缺省,缺省值为空。 | 307 | formVisibleNotify | 标识是否允许卡片使用卡片可见性通知。 | 字符串 | 可缺省,缺省值为空。 | 308 | jsComponentName | 表示JS卡片的Component名称。字符串最大长度为127字节。 | 字符串 | 否 | 309 | metaData | 表示卡片的自定义信息,包含customizeData数组标签。 | 对象 | 可缺省,缺省值为空。 | 310 | customizeData | 表示自定义的卡片信息。 | 对象数组 | 可缺省,缺省值为空。 | 311 312 配置示例如下: 313 314 315 ```json 316 "abilities": [ 317 // ... 318 { 319 "name": ".FormAbility", 320 "srcPath": "FormAbility", 321 "description": "$string:FormAbility_desc", 322 "icon": "$media:icon", 323 "label": "$string:FormAbility_label", 324 "type": "service", 325 "formsEnabled": true, 326 "srcLanguage": "ets", 327 "forms": [ 328 { 329 "jsComponentName": "widget", 330 "isDefault": true, 331 "scheduledUpdateTime": "10:30", 332 "defaultDimension": "2*2", 333 "name": "widget", 334 "description": "This is a service widget.", 335 "colorMode": "auto", 336 "type": "JS", 337 "formVisibleNotify": true, 338 "supportDimensions": [ 339 "2*2" 340 ], 341 "updateEnabled": true, 342 "updateDuration": 1 343 } 344 ] 345 }, 346 // ... 347 ] 348 ``` 349 350 351### 卡片信息的持久化 352 353因大部分卡片提供方都不是常驻服务,只有在需要使用时才会被拉起获取卡片信息,且卡片管理服务支持对卡片进行多实例管理,卡片ID对应实例ID,因此若卡片提供方支持对卡片数据进行配置,则需要对卡片的业务数据按照卡片ID进行持久化管理,以便在后续获取、更新以及拉起时能获取到正确的卡片业务数据。且需要适配onDestroy卡片删除通知接口,在其中实现卡片实例数据的删除。 354 355 356```ts 357const TAG: string = '[Sample_FAModelAbilityDevelop]'; 358const domain: number = 0xFF00; 359 360const DATA_STORAGE_PATH: string = 'form_store'; 361let storeFormInfo = async (formId: string, formName: string, tempFlag: boolean, context: featureAbility.Context): Promise<void> => { 362 // 此处仅对卡片ID:formId,卡片名:formName和是否为临时卡片:tempFlag进行了持久化 363 let formInfo: Record<string, string | number | boolean> = { 364 'formName': 'formName', 365 'tempFlag': 'tempFlag', 366 'updateCount': 0 367 }; 368 try { 369 const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH); 370 // put form info 371 await storage.put(formId, JSON.stringify(formInfo)); 372 hilog.info(domain, TAG, `storeFormInfo, put form info successfully, formId: ${formId}`); 373 await storage.flush(); 374 } catch (err) { 375 hilog.error(domain, TAG, `failed to storeFormInfo, err: ${JSON.stringify(err as Error)}`); 376 } 377}; 378 379let deleteFormInfo = async (formId: string, context: featureAbility.Context) => { 380 try { 381 const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH); 382 // del form info 383 await storage.delete(formId); 384 hilog.info(domain, TAG, `deleteFormInfo, del form info successfully, formId: ${formId}`); 385 await storage.flush(); 386 } catch (err) { 387 hilog.error(domain, TAG, `failed to deleteFormInfo, err: ${JSON.stringify(err)}`); 388 } 389} 390 391// ... 392 onCreate(want: Want) { 393 hilog.info(domain, TAG, 'FormAbility onCreate'); 394 if (want.parameters) { 395 let formId = String(want.parameters['ohos.extra.param.key.form_identity']); 396 let formName = String(want.parameters['ohos.extra.param.key.form_name']); 397 let tempFlag = Boolean(want.parameters['ohos.extra.param.key.form_temporary']); 398 // 将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用 399 // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例 400 hilog.info(domain, TAG, 'FormAbility onCreate' + formId); 401 storeFormInfo(formId, formName, tempFlag, this.context); 402 } 403 404 // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类 405 let obj: Record<string, string> = { 406 'title': 'titleOnCreate', 407 'detail': 'detailOnCreate' 408 }; 409 let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj); 410 return formData; 411 }, 412// ... 413 414let deleteFormInfo = async (formId: string, context: featureAbility.Context): Promise<void> => { 415 try { 416 const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH); 417 // del form info 418 await storage.delete(formId); 419 hilog.info(domain, TAG, `deleteFormInfo, del form info successfully, formId: ${formId}`); 420 await storage.flush(); 421 } catch (err) { 422 hilog.error(domain, TAG, `failed to deleteFormInfo, err: ${JSON.stringify(err)}`); 423 } 424}; 425 426// ... 427 // 适配onDestroy卡片删除通知接口,在其中实现卡片实例数据的删除。 428 onDestroy(formId: string) { 429 // 删除卡片实例数据 430 hilog.info(domain, TAG, 'FormAbility onDestroy'); 431 // 删除之前持久化的卡片实例数据 432 // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例 433 deleteFormInfo(formId, this.context); 434 } 435// ... 436``` 437 438具体的持久化方法可以参考[应用数据持久化概述](../database/app-data-persistence-overview.md)。 439 440需要注意的是,卡片使用方在请求卡片时传递给提供方应用的Want数据中存在临时标记字段,表示此次请求的卡片是否为临时卡片: 441 442- 常态卡片:卡片使用方会持久化的卡片。如添加到桌面的卡片。 443 444- 临时卡片:卡片使用方不会持久化的卡片。如上划卡片应用时显示的卡片。 445 446临时卡片转常态卡片:上划卡片应用后,此时会显示的卡片为临时卡片;点击卡片上的“图钉”按钮后添加到桌面,此时卡片转为常态卡片。 447 448由于临时卡片的数据具有非持久化的特殊性,某些场景例如卡片服务框架死亡重启,此时临时卡片数据在卡片管理服务中已经删除,且对应的卡片ID不会通知到提供方,所以卡片提供方需要自己负责清理长时间未删除的临时卡片数据。同时对应的卡片使用方可能会将之前请求的临时卡片转换为常态卡片。如果转换成功,卡片提供方也需要对对应的临时卡片ID进行处理,把卡片提供方记录的临时卡片数据转换为常态卡片数据,防止提供方在清理长时间未删除的临时卡片时,把已经转换为常态卡片的临时卡片信息删除,导致卡片信息丢失。 449 450 451### 卡片数据交互 452 453当卡片应用需要更新数据时(如触发了定时更新或定点更新),卡片应用获取最新数据,并调用updateForm()接口更新主动触发卡片的更新。 454 455 456```ts 457const TAG: string = '[Sample_FAModelAbilityDevelop]'; 458const domain: number = 0xFF00; 459 460onUpdate(formId: string) { 461 // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新 462 hilog.info(domain, TAG, 'FormAbility onUpdate'); 463 let obj: Record<string, string> = { 464 'title': 'titleOnUpdate', 465 'detail': 'detailOnUpdate' 466 }; 467 let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj); 468 // 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变 469 formProvider.updateForm(formId, formData).catch((error: Error) => { 470 hilog.error(domain, TAG, 'FormAbility updateForm, error:' + JSON.stringify(error)); 471 }); 472} 473``` 474 475 476### 开发卡片页面 477 478开发者可以使用类Web范式(HML+CSS+JSON)开发JS卡片页面。生成如下卡片页面,可以这样配置卡片页面文件: 479 480 481 482> **说明:** 483> FA模型当前仅支持JS扩展的类Web开发范式来实现卡片的UI。 484 485- HML:使用类Web范式的组件描述卡片的页面信息。 486 487 ```html 488 <div class="container"> 489 <stack> 490 <div class="container-img"> 491 <image src="/common/widget.png" class="bg-img"></image> 492 <image src="/common/rect.png" class="bottom-img"></image> 493 </div> 494 <div class="container-inner"> 495 <text class="title" onclick="routerEvent">{{title}}</text> 496 <text class="detail_text" onclick="messageEvent">{{detail}}</text> 497 </div> 498 </stack> 499 </div> 500 ``` 501 502- CSS:HML中类Web范式组件的样式信息。 503 504 ```css 505 .container { 506 flex-direction: column; 507 justify-content: center; 508 align-items: center; 509 } 510 511 .bg-img { 512 flex-shrink: 0; 513 height: 100%; 514 z-index: 1; 515 } 516 517 .bottom-img { 518 position: absolute; 519 width: 150px; 520 height: 56px; 521 top: 63%; 522 background-color: rgba(216, 216, 216, 0.15); 523 filter: blur(20px); 524 z-index: 2; 525 } 526 527 .container-inner { 528 flex-direction: column; 529 justify-content: flex-end; 530 align-items: flex-start; 531 height: 100%; 532 width: 100%; 533 padding: 12px; 534 } 535 536 .title { 537 font-family: HarmonyHeiTi-Medium; 538 font-size: 14px; 539 color: rgba(255,255,255,0.90); 540 letter-spacing: 0.6px; 541 } 542 543 .detail_text { 544 font-family: HarmonyHeiTi; 545 font-size: 12px; 546 color: rgba(255,255,255,0.60); 547 letter-spacing: 0.51px; 548 text-overflow: ellipsis; 549 max-lines: 1; 550 margin-top: 6px; 551 } 552 ``` 553 554- JSON:卡片页面中的数据和事件交互。 555 556 ```json 557 { 558 "data": { 559 "title": "TitleDefault", 560 "detail": "TextDefault" 561 }, 562 "actions": { 563 "routerEvent": { 564 "action": "router", 565 "abilityName": "com.samples.famodelabilitydevelop.MainAbility", 566 "params": { 567 "message": "add detail" 568 } 569 }, 570 "messageEvent": { 571 "action": "message", 572 "params": { 573 "message": "add detail" 574 } 575 } 576 } 577 } 578 ``` 579 580 581### 开发卡片事件 582 583卡片支持为组件设置交互事件(action),包括router事件和message事件,其中router事件用于Ability跳转,message事件用于卡片开发人员自定义点击事件。关键步骤说明如下: 584 5851. 在hml中为组件设置onclick属性,其值对应到json文件的actions字段中。 586 5872. 如何设置router事件: 588 - action属性值为"router"; 589 - abilityName为跳转目标的Ability名(支持跳转FA模型的PageAbility组件和Stage模型的UIAbility组件),如目前DevEco创建的FA模型的UIAbility默认名为com.example.entry.EntryAbility; 590 - params为传递给跳转目标Ability的自定义参数,可以按需填写。其值可以在目标Ability启动时的want中的parameters里获取。如FA模型EntryAbility的onCreate生命周期里可以通过featureAbility.getWant()获取到want,然后在其parameters字段下获取到配置的参数; 591 5923. 如何设置message事件: 593 - action属性值为"message"; 594 - params为message事件的用户自定义参数,可以按需填写。其值可以在卡片生命周期函数onEvent中的message里获取; 595 596示例如下: 597 598- hml文件 599 600 ```html 601 <div class="container"> 602 <stack> 603 <div class="container-img"> 604 <image src="/common/widget.png" class="bg-img"></image> 605 <image src="/common/rect.png" class="bottom-img"></image> 606 </div> 607 <div class="container-inner"> 608 <text class="title" onclick="routerEvent">{{title}}</text> 609 <text class="detail_text" onclick="messageEvent">{{detail}}</text> 610 </div> 611 </stack> 612 </div> 613 ``` 614 615- css文件 616 617 ```css 618 .container { 619 flex-direction: column; 620 justify-content: center; 621 align-items: center; 622 } 623 624 .bg-img { 625 flex-shrink: 0; 626 height: 100%; 627 z-index: 1; 628 } 629 630 .bottom-img { 631 position: absolute; 632 width: 150px; 633 height: 56px; 634 top: 63%; 635 background-color: rgba(216, 216, 216, 0.15); 636 filter: blur(20px); 637 z-index: 2; 638 } 639 640 .container-inner { 641 flex-direction: column; 642 justify-content: flex-end; 643 align-items: flex-start; 644 height: 100%; 645 width: 100%; 646 padding: 12px; 647 } 648 649 .title { 650 font-family: HarmonyHeiTi-Medium; 651 font-size: 14px; 652 color: rgba(255,255,255,0.90); 653 letter-spacing: 0.6px; 654 } 655 656 .detail_text { 657 font-family: HarmonyHeiTi; 658 font-size: 12px; 659 color: rgba(255,255,255,0.60); 660 letter-spacing: 0.51px; 661 text-overflow: ellipsis; 662 max-lines: 1; 663 margin-top: 6px; 664 } 665 ``` 666 667- json文件 668 669 ```json 670 { 671 "data": { 672 "title": "TitleDefault", 673 "detail": "TextDefault" 674 }, 675 "actions": { 676 "routerEvent": { 677 "action": "router", 678 "abilityName": "com.samples.famodelabilitydevelop.MainAbility", 679 "params": { 680 "message": "add detail" 681 } 682 }, 683 "messageEvent": { 684 "action": "message", 685 "params": { 686 "message": "add detail" 687 } 688 } 689 } 690 } 691 ``` 692