1# 卡片使用方开发指导(仅对系统应用开放) 2 3## 卡片概述 4 5卡片是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达,减少体验层级的目的。 6 7卡片常用于嵌入到其他应用(当前只支持系统应用)中作为其界面的一部分显示,并支持拉起页面,发送消息等基础的交互功能。卡片使用方负责显示卡片。 8 9- 卡片的基本概念: 10 11 - 卡片提供方:提供卡片显示内容原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。 12 13 - 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。 14 15 - 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。 16 17  18 19## 场景介绍 20 21卡片使用方开发,即基于Stage模型的卡片使用方开发,主要指导如下: 22 23- 卡片组件FormComponent的使用。 24- 通过formHost模块提供的卡片使用方相关接口操作卡片的删除、更新等行为。 25 26## 卡片组件 27 28提供卡片组件,实现卡片的显示功能。详情见[FormComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-formcomponent-sys.md)。 29 30> **说明:** 31> 32> - 该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 33> 34> - 该组件为卡片组件的使用方。 35> 36> - 该组件使用需要具有系统签名。 37> 38> - 本模块为系统接口。 39 40通过卡片组件成功添加卡片时,会调用到卡片提供方FormExtensionAbility中的[onAddForm](../reference/apis-form-kit/js-apis-app-form-formExtensionAbility.md#onaddform)方法。 41 42### 临时卡片和常态卡片 43 44在卡片组件中的temporary字段可以配置卡片是临时卡片还是常态卡片。true为临时卡片,false为常态卡片。 45 46- 常态卡片:卡片使用方会持久化的卡片。如添加到桌面的卡片。 47 48- 临时卡片:卡片使用方不会持久化的卡片。如上划卡片应用时显示的卡片。 49 50由于临时卡片的数据具有非持久化的特殊性,某些场景例如卡片服务框架死亡重启,此时临时卡片数据在卡片管理服务中已经删除,且对应的卡片ID不会通知到提供方,所以卡片提供方需要自己负责清理长时间未删除的临时卡片数据。同时对应的卡片使用方可能会将之前请求的临时卡片转换为常态卡片。如果转换成功,卡片提供方也需要对对应的临时卡片ID进行处理,把卡片提供方记录的临时卡片数据转换为常态卡片数据,防止提供方在清理长时间未删除的临时卡片时,把已经转换为常态卡片的临时卡片信息删除,导致卡片信息丢失。 51 52## formHost接口 53 54formHost提供一系列的卡片使用方接口,来操作卡片的更新、删除等行为,具体的API介绍详见[接口文档](../reference/apis-form-kit/js-apis-app-form-formHost-sys.md)。 55 56## 卡片使用方示例 57 58```ts 59//Index.ets 60import { HashMap, HashSet } from '@kit.ArkTS'; 61import { formHost, formInfo, formObserver } from '@kit.FormKit'; 62import { bundleMonitor } from '@kit.AbilityKit'; 63import { BusinessError } from '@kit.BasicServicesKit'; 64 65@Entry 66@Component 67struct formHostSample { 68 // 卡片尺寸枚举。 69 static FORM_DIMENSIONS_MAP = [ 70 '1*2', 71 '2*2', 72 '2*4', 73 '4*4', 74 '2*1', 75 '1*1', 76 '6*4', 77 ] 78 79 // 模拟卡片尺寸。 80 static FORM_SIZE = [ 81 [120, 60], // 1*2 82 [120, 120], // 2*2 83 [240, 120], // 2*4 84 [240, 240], // 4*4 85 [60, 120], // 2*1 86 [60, 60], // 1*1 87 [240, 360], // 6*4 88 ] 89 90 @State message: Resource | string = $r('app.string.Host'); 91 formCardHashMap: HashMap<string, formInfo.FormInfo> = new HashMap(); 92 @State showFormPicker: boolean = false; 93 @State operation: Resource | string = $r('app.string.formOperation'); 94 @State index: number = 2; 95 @State space: number = 8; 96 @State arrowPosition: ArrowPosition = ArrowPosition.END; 97 formIds: HashSet<string> = new HashSet(); 98 currentFormKey: string = ''; 99 focusFormInfo: formInfo.FormInfo = { 100 bundleName: '', 101 moduleName: '', 102 abilityName: '', 103 name: '', 104 displayName: '', 105 displayNameId: 0, 106 description: '', 107 descriptionId: 0, 108 type: formInfo.FormType.eTS, 109 jsComponentName: '', 110 colorMode: formInfo.ColorMode.MODE_AUTO, 111 isDefault: false, 112 updateEnabled: false, 113 formVisibleNotify: true, 114 scheduledUpdateTime: '', 115 formConfigAbility: '', 116 updateDuration: 0, 117 defaultDimension: 6, 118 supportDimensions: [], 119 supportedShapes: [], 120 customizeData: {}, 121 isDynamic: false, 122 transparencyEnabled: false 123 } 124 formInfoRecord: TextCascadePickerRangeContent[] = []; 125 pickerBtnMsg: Resource | string = $r('app.string.formType'); 126 @State showForm: boolean = true; 127 @State selectFormId: string = '0'; 128 @State pickDialogIndex: number = 0; 129 130 aboutToAppear(): void { 131 try { 132 // 检查系统是否准备好。 133 formHost.isSystemReady().then(() => { 134 console.log('formHost isSystemReady success'); 135 136 // 订阅通知卡片不可见的事件和卡片可见通知事件。 137 let notifyInvisibleCallback = (data: formInfo.RunningFormInfo[]) => { 138 console.log(`form change invisibility, data: ${JSON.stringify(data)}`); 139 } 140 let notifyVisibleCallback = (data: formInfo.RunningFormInfo[]) => { 141 console.log(`form change visibility, data: ${JSON.stringify(data)}`); 142 } 143 formObserver.on('notifyInvisible', notifyInvisibleCallback); 144 formObserver.on('notifyVisible', notifyVisibleCallback); 145 146 // 注册监听应用的安装事件。 147 try { 148 bundleMonitor.on('add', (bundleChangeInfo) => { 149 console.info(`bundleName : ${bundleChangeInfo.bundleName} userId : ${bundleChangeInfo.userId}`); 150 this.getAllBundleFormsInfo(); 151 }) 152 } catch (errData) { 153 let message = (errData as BusinessError).message; 154 let errCode = (errData as BusinessError).code; 155 console.log(`errData is errCode:${errCode} message:${message}`); 156 } 157 // 注册监听应用的更新事件。 158 try { 159 bundleMonitor.on('update', (bundleChangeInfo) => { 160 console.info(`bundleName : ${bundleChangeInfo.bundleName} userId : ${bundleChangeInfo.userId}`); 161 this.getAllBundleFormsInfo(); 162 }) 163 } catch (errData) { 164 let message = (errData as BusinessError).message; 165 let errCode = (errData as BusinessError).code; 166 console.log(`errData is errCode:${errCode} message:${message}`); 167 } 168 // 注册监听应用的卸载事件。 169 try { 170 bundleMonitor.on('remove', (bundleChangeInfo) => { 171 console.info(`bundleName : ${bundleChangeInfo.bundleName} userId : ${bundleChangeInfo.userId}`); 172 this.getAllBundleFormsInfo(); 173 }) 174 } catch (errData) { 175 let message = (errData as BusinessError).message; 176 let errCode = (errData as BusinessError).code; 177 console.log(`errData is errCode:${errCode} message:${message}`); 178 } 179 }).catch((error: BusinessError) => { 180 console.error(`error, code: ${error.code}, message: ${error.message}`); 181 }); 182 } 183 catch (error) { 184 console.error(`catch error, code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`); 185 } 186 } 187 188 aboutToDisappear(): void { 189 // 删除所有卡片。 190 this.formIds.forEach((id) => { 191 console.log('delete all form') 192 formHost.deleteForm(id); 193 }); 194 // 注销监听应用的安装。 195 try { 196 bundleMonitor.off('add'); 197 } catch (errData) { 198 let message = (errData as BusinessError).message; 199 let errCode = (errData as BusinessError).code; 200 console.log(`errData is errCode:${errCode} message:${message}`); 201 } 202 // 注销监听应用的更新。 203 try { 204 bundleMonitor.off('update'); 205 } catch (errData) { 206 let message = (errData as BusinessError).message; 207 let errCode = (errData as BusinessError).code; 208 console.log(`errData is errCode:${errCode} message:${message}`); 209 } 210 // 注销监听应用的卸载。 211 try { 212 bundleMonitor.off('remove'); 213 } catch (errData) { 214 let message = (errData as BusinessError).message; 215 let errCode = (errData as BusinessError).code; 216 console.log(`errData is errCode:${errCode} message:${message}`); 217 } 218 // 取消订阅通知卡片不可见和通知卡片可见事件。 219 formObserver.off('notifyInvisible'); 220 formObserver.off('notifyVisible'); 221 } 222 223 // 将所有卡片信息存入formHapRecordMap中。 224 getAllBundleFormsInfo() { 225 this.formCardHashMap.clear(); 226 this.showFormPicker = false; 227 let formHapRecordMap: HashMap<string, formInfo.FormInfo[]> = new HashMap(); 228 this.formInfoRecord = []; 229 formHost.getAllFormsInfo().then((formList: Array<formInfo.FormInfo>) => { 230 console.log('getALlFormsInfo size:' + formList.length) 231 for (let formItemInfo of formList) { 232 let formBundleName = formItemInfo.bundleName; 233 if (formHapRecordMap.hasKey(formBundleName)) { 234 formHapRecordMap.get(formBundleName).push(formItemInfo) 235 } else { 236 let formInfoList: formInfo.FormInfo[] = [formItemInfo]; 237 formHapRecordMap.set(formBundleName, formInfoList); 238 } 239 } 240 for (let formBundle of formHapRecordMap.keys()) { 241 let bundleFormInfo: TextCascadePickerRangeContent = { 242 text: formBundle, 243 children: [] 244 } 245 let bundleFormList: formInfo.FormInfo[] = formHapRecordMap.get(formBundle); 246 bundleFormList.forEach((formItemInfo) => { 247 let dimensionName = formHostSample.FORM_DIMENSIONS_MAP[formItemInfo.defaultDimension - 1]; 248 bundleFormInfo.children?.push({ text: formItemInfo.name + '#' + dimensionName }); 249 this.formCardHashMap.set(formBundle + "#" + formItemInfo.name + '#' + dimensionName, formItemInfo); 250 }) 251 this.formInfoRecord.push(bundleFormInfo); 252 } 253 this.formCardHashMap.forEach((formItem: formInfo.FormInfo) => { 254 console.info(`formCardHashmap: ${JSON.stringify(formItem)}`); 255 }) 256 this.showFormPicker = true; 257 }) 258 } 259 260 build() { 261 Column() { 262 Text(this.message) 263 .fontSize(30) 264 .fontWeight(FontWeight.Bold) 265 266 Divider().vertical(false).color(Color.Black).lineCap(LineCapStyle.Butt).margin({ top: 10, bottom: 10 }) 267 268 Row() { 269 // 点击查询所有卡片信息。 270 Button($r('app.string.inquiryForm')) 271 .onClick(() => { 272 this.getAllBundleFormsInfo(); 273 }) 274 275 // 点击按钮弹出选择界面,点击确定后,添加默认尺寸的所选卡片。 276 Button($r('app.string.selectAddForm')) 277 .enabled(this.showFormPicker) 278 .onClick(() => { 279 console.info("TextPickerDialog: show()") 280 TextPickerDialog.show({ 281 range: this.formInfoRecord, 282 selected: this.pickDialogIndex, 283 canLoop: false, 284 disappearTextStyle: { color: Color.Red, font: { size: 10, weight: FontWeight.Lighter } }, 285 textStyle: { color: Color.Black, font: { size: 12, weight: FontWeight.Normal } }, 286 selectedTextStyle: { color: Color.Blue, font: { size: 12, weight: FontWeight.Bolder } }, 287 onAccept: (result: TextPickerResult) => { 288 this.currentFormKey = result.value[0] + "#" + result.value[1]; 289 this.pickDialogIndex = result.index[0] 290 console.info(`TextPickerDialog onAccept: ${this.currentFormKey}, ${this.pickDialogIndex}`); 291 if (!this.formCardHashMap.hasKey(this.currentFormKey)) { 292 console.error(`invalid formItemInfo by form key`) 293 return; 294 } 295 this.showForm = true; 296 this.focusFormInfo = this.formCardHashMap.get(this.currentFormKey); 297 }, 298 onCancel: () => { 299 console.info("TextPickerDialog : onCancel()") 300 }, 301 onChange: (result: TextPickerResult) => { 302 this.pickerBtnMsg = result.value[0] + '#' + result.value[1]; 303 console.info("TextPickerDialog:onChange:" + this.pickerBtnMsg) 304 } 305 }) 306 }) 307 .margin({ left: 10 }) 308 } 309 .margin({ left: 10 }) 310 311 Divider().vertical(false).color(Color.Black).lineCap(LineCapStyle.Butt).margin({ top: 10, bottom: 10 }) 312 313 if(this.showForm){ 314 Text(this.pickerBtnMsg) 315 .margin({ top: 10, bottom: 10 }) 316 } 317 318 if (this.showForm) { 319 Text('formId: ' + this.selectFormId) 320 .margin({ top: 10, bottom: 10 }) 321 322 // 卡片组件。 323 FormComponent({ 324 id: Number.parseInt(this.selectFormId), 325 name: this.focusFormInfo.name, 326 bundle: this.focusFormInfo.bundleName, 327 ability: this.focusFormInfo.abilityName, 328 module: this.focusFormInfo.moduleName, 329 dimension: this.focusFormInfo.defaultDimension, 330 temporary: false, 331 }) 332 .size({ 333 width: formHostSample.FORM_SIZE[this.focusFormInfo.defaultDimension - 1][0], 334 height: formHostSample.FORM_SIZE[this.focusFormInfo.defaultDimension - 1][1], 335 }) 336 .borderColor(Color.Black) 337 .borderRadius(10) 338 .borderWidth(1) 339 .onAcquired((form: FormCallbackInfo) => { 340 console.log(`onAcquired: ${JSON.stringify(form)}`) 341 this.selectFormId = form.id.toString(); 342 this.formIds.add(this.selectFormId); 343 }) 344 .onRouter(() => { 345 console.log(`onRouter`) 346 }) 347 .onError((error) => { 348 console.error(`onError: ${JSON.stringify(error)}`) 349 this.showForm = false; 350 }) 351 .onUninstall((info: FormCallbackInfo) => { 352 this.showForm = false; 353 console.error(`onUninstall: ${JSON.stringify(info)}`) 354 this.formIds.remove(this.selectFormId); 355 }) 356 357 // select列表,列出部分formHost接口功能。 358 Row() { 359 Select([{ value: $r('app.string.deleteForm') }, 360 { value: $r('app.string.updateForm') }, 361 { value: $r('app.string.visibleForms') }, 362 { value: $r('app.string.invisibleForms') }, 363 { value: $r('app.string.enableFormsUpdate') }, 364 { value: $r('app.string.disableFormsUpdate') }, 365 ]) 366 .selected(this.index) 367 .value(this.operation) 368 .font({ size: 16, weight: 500 }) 369 .fontColor('#182431') 370 .selectedOptionFont({ size: 16, weight: 400 }) 371 .optionFont({ size: 16, weight: 400 }) 372 .space(this.space) 373 .arrowPosition(this.arrowPosition) 374 .menuAlign(MenuAlignType.START, { dx: 0, dy: 0 }) 375 .optionWidth(200) 376 .optionHeight(300) 377 .onSelect((index: number, text?: string | Resource) => { 378 console.info('Select:' + index) 379 this.index = index; 380 if (text) { 381 this.operation = text; 382 } 383 }) 384 385 // 根据select列表所选的功能,对当前卡片执行对应操作。 386 Button($r('app.string.execute'), { 387 type: ButtonType.Capsule 388 }) 389 .fontSize(16) 390 .onClick(() => { 391 switch (this.index) { 392 case 0: 393 try { 394 formHost.deleteForm(this.selectFormId, (error: BusinessError) => { 395 if (error) { 396 console.error(`deleteForm error, code: ${error.code}, message: ${error.message}`); 397 } else { 398 console.log('formHost deleteForm success'); 399 } 400 }); 401 } catch (error) { 402 console.error(`deleteForm catch error, code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`); 403 } 404 this.showForm = false; 405 this.selectFormId = ''; 406 break; 407 case 1: 408 try { 409 formHost.requestForm(this.selectFormId, (error: BusinessError) => { 410 if (error) { 411 console.error(`requestForm error, code: ${error.code}, message: ${error.message}`); 412 } 413 }); 414 } catch (error) { 415 console.error(`requestForm catch error, code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`); 416 } 417 break; 418 case 2: 419 try { 420 formHost.notifyVisibleForms([this.selectFormId], (error: BusinessError) => { 421 if (error) { 422 console.error(`notifyVisibleForms error, code: ${error.code}, message: ${error.message}`); 423 } else { 424 console.info('notifyVisibleForms success'); 425 } 426 }); 427 } catch (error) { 428 console.error(`notifyVisibleForms catch error, code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`); 429 } 430 break; 431 case 3: 432 try { 433 formHost.notifyInvisibleForms([this.selectFormId], (error: BusinessError) => { 434 if (error) { 435 console.error(`notifyInvisibleForms error, code: ${error.code}, message: ${error.message}`); 436 } else { 437 console.info('notifyInvisibleForms success'); 438 } 439 }); 440 } catch (error) { 441 console.error(`notifyInvisibleForms catch error, code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`); 442 } 443 break; 444 case 4: 445 try { 446 formHost.enableFormsUpdate([this.selectFormId], (error: BusinessError) => { 447 if (error) { 448 console.error(`enableFormsUpdate error, code: ${error.code}, message: ${error.message}`); 449 } 450 }); 451 } catch (error) { 452 console.error(`enableFormsUpdate catch error, code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`); 453 } 454 break; 455 case 5: 456 try { 457 formHost.disableFormsUpdate([this.selectFormId], (error: BusinessError) => { 458 if (error) { 459 console.error(`disableFormsUpdate error, code: ${error.code}, message: ${error.message}`); 460 } else { 461 console.info('disableFormsUpdate success'); 462 } 463 }); 464 } catch (error) { 465 console.error(`disableFormsUpdate catch error, code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`); 466 } 467 break; 468 } 469 }) 470 } 471 .margin({ 472 top: 20, 473 bottom: 10 474 }) 475 } 476 } 477 } 478} 479``` 480 481 482 483## 相关实例 484 485针对卡片使用方开发,有以下实例可供参考: 486 487- [卡片使用方(Stage)(API12)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/DocsSample/Form/FormHost) 488