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