HarmonyOS应用开发:桌面卡片实现

前言

桌面卡片是比较常见的功能,本案例详细列举了卡片开发的大部分功能,如使用postCardAction接口快速拉起卡片提供方应用的指定UIAbility,通过message事件刷新卡片内容等,为开发者提供了卡片功能的展示。

效果图预览

使用说明

  1. 长按应用,添加卡片到桌面。
  2. 卡片内可滑动选择案例,点击可进入案例详情。
  3. 部分案例无详情页时,点击跳转到首页瀑布流。

实现思路

  1. 新建卡片
  2. 配置formconfig
  3. 编写卡片UI代码
  4. 触发刷新事件
  5. 触发点击事件

实现步骤

本例涉及的关键特性和实现方案如下:

  1. 新建卡片。右键点击entry目录,选择新建->Service Widget->Dynamic Widget,其中Dynamic Widget为动态卡片,Static Widget为静态卡片。此时会生成几个文件:配置文件form_config.json;卡片AbilityEntryFormAbility.ets;卡片组件WidgetCard.ets

  2. 新建卡片后,根据需要(如卡片大小,刷新时间,动态静态卡片设置)配置form_config.json

    { "forms": [ { "name": "widget", // 卡片的名称。 "displayName": " <math xmlns="http://www.w3.org/1998/Math/MathML"> s t r i n g : w i d g e t d i s p l a y n a m e " , / / 卡片的显示名称。 " d e s c r i p t i o n " : " string:widget_display_name", // 卡片的显示名称。 "description": " </math>string:widgetdisplayname",//卡片的显示名称。"description":"string:widget_desc", // 卡片的描述。 "src": "./ets/widget/pages/WidgetCard.ets", // 卡片对应的UI代码的完整路径。 "uiSyntax": "arkts", // 卡片的类型 "window": { // 用于定义与显示窗口相关的配置。 "designWidth": 720, "autoDesignWidth": true }, "colorMode": "auto", // 卡片的主题样式。 "isDynamic": true, // 卡片是否为动态卡片。 "isDefault": true, // 卡片是否为默认卡片。 "updateEnabled": true, // 卡片是否支持周期性刷新。 "scheduledUpdateTime": "10:30", // 卡片的定点刷新的时刻。 "updateDuration": 1, // 卡片定时刷新的更新周期,单位为30分钟,取值为自然数。 "defaultDimension": "64", // 卡片的默认外观规格。 "supportDimensions": [ // 卡片支持的外观规格,取值范围。 "64" ] } ] }

  3. 编写卡片UI代码。在主文件WidgetCard.ets中添加UI组件,需要注意的是:ArkTS卡片存在较多约束(如不支持导入共享包),较多逻辑不可在卡片中使用,在使用时需要根据文档进行操作。

  4. 编写跳转事件:当应用未被拉起时,点击某个卡片时跳转到具体的案例页面。在EntryAbility.ets中补充逻辑:onCreate生命周期内获取want.parameters.params判断卡片内容的跳转。

    // EntryAbility.ets export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { // ... // 桌面卡片判断跳转内容 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; } // ... } }

  5. 编写跳转事件:当应用在后台时,点击某个卡片时跳转到具体的案例页面。可从onNewWant生命周期获取want.parameters.params判断卡片内容的跳转。

    // EntryAbility.ets export default class EntryAbility extends UIAbility { // 如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期回调 onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { 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; } if (this.currentWindowStage !== null) { // 存在窗口时点击卡片后进行页面跳转 this.onWindowStageCreate(this.currentWindowStage); } } }

  6. 具体跳转逻辑编写。在onWindowStageCreate生命周期内进行具体的跳转逻辑。

    // EntryAbility.ets export default class EntryAbility extends UIAbility { onWindowStageCreate(windowStage: window.WindowStage): void { // 判断是否存在窗口可进行页面跳转 if (this.currentWindowStage === null) { this.currentWindowStage = windowStage; } // 点击卡片后进行页面跳转 if (this.selectPage) { this.storage.setOrCreate('formNavigationRouter', this.selectPage); windowStage.loadContent('pages/EntryView', this.storage, (err, data) => { if (err.code) { logger.error(DOMAIN_NUMBER.toString(), TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); return; } logger.info(DOMAIN_NUMBER.toString(), TAG, 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); }); } // ... } }

  7. 编写跳转事件:在EntryAbility.ets编写事件后,同时在接收案例参数的EntryView.ets内处理页面跳转逻辑。通过和DynamicsRouter内的数据对比,判断通过storage传入的页面是哪一个,然后执行pushUri跳转。

    @Entry(storage) @Component struct EntryView { onPageShow(): void { // 从卡片进入页面时判断具体跳转页面 if (this.formRouter) { waterFlowData.forEach((item: SceneModuleInfo) => { let index = item.appUri.indexOf(this.formRouter); if (index > -1) { if (DynamicsRouter.appRouterStack.slice(-1)[0].name !== item.appUri) { DynamicsRouter.clear(); DynamicsRouter.pushUri(item.appUri); } return; } }) this.formRouter = ''; } } }

  8. 判断卡片大小:获取卡片详情,根据宽高比获取卡片的规格,不同规格显示内容不同。在EntryFormAbility.ets中补充点击卡片进入时查找对应案例的逻辑。在onAddForm生命周期内做卡片生成时,createFormBindingData方法传递属性。

    // EntryFormAbility.ets export default class EntryFormAbility extends FormExtensionAbility { // 卡片对象集合 onAddForm(want: Want): formBindingData.FormBindingData { let isLongCard: boolean = true; if ((want.parameters?.[formInfo.FormParam.WIDTH_KEY] as number) / (want.parameters?.[formInfo.FormParam.HEIGHT_KEY] as number) > 0.666) { isLongCard = false; } // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类 let obj: Record<string, string | boolean> = { 'title': 'titleOnAddForm', 'isLongCard': isLongCard }; let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj); return formData; } }

  9. 编写刷新事件:当定时更新或定点更新触发时,需要更新卡片内容。onUpdateForm生命周期发生在定时更新/定点更新/卡片使用方主动请求更新时,在方法内增加获取案例数据的功能。

    // EntryFormAbility.ets export default class EntryFormAbility extends FormExtensionAbility { // 网络获取README数据并利用formProvider.updateForm更新到卡片 async getData(formId: string) { let detail: CASES[] = []; let httpRequest = http.createHttp(); let webData: http.HttpResponse = await httpRequest.request(URL); if (webData?.responseCode == http.ResponseCode.OK) { try { detail = this.formatData(webData.result.toString()); hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onFormEvent' + 'webData.result:' + webData.result);

    typescript 复制代码
            class FormDataClass {
               detail: CASES[] = detail;
            }
    
            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)}`);
         }
      } 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();

    }

    async onUpdateForm(formId: string): Promise { // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新 hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onUpdateForm'); this.getData(formId); } }

  10. 编写刷新事件:手动刷新内容时,需要更新卡片内容。onFormEvent生命周期发生在卡片主动通过postCardAction接口触发message事件。

    // EntryFormAbility.ets export default class EntryFormAbility extends FormExtensionAbility { // 网络获取README数据并利用formProvider.updateForm更新到卡片 async getData(formId: string) { let detail: CASES[] = []; let httpRequest = http.createHttp(); let webData: http.HttpResponse = await httpRequest.request(URL); if (webData?.responseCode == http.ResponseCode.OK) { try { detail = this.formatData(webData.result.toString()); hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onFormEvent' + 'webData.result:' + webData.result);

    typescript 复制代码
            class FormDataClass {
               detail: CASES[] = detail;
            }
    
            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)}`);
         }
      } 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();

    }

    async onFormEvent(formId: string, message: string): Promise { this.getData(formId); } }

  11. 编写刷新事件:参数传到卡片组件内,组件接收参数。处理WidgetCard.ets卡片内逻辑。卡片页面中使用LocalStorageProp装饰需要刷新的卡片数据。

    let casesCardInfo = new LocalStorage(); @Entry(casesCardInfo) @Component struct Widget_DynamicCard { @LocalStorageProp('detail') detail: CASES[] = []; // 卡片对象集合 private swiperController: SwiperController = new SwiperController(); @LocalStorageProp('isLongCard') isLongCard: boolean = false;

    build() { // ... } }

如果您想系统深入地学习 HarmonyOS 开发或想考取HarmonyOS认证证书,欢迎加入华为开发者学堂:

请点击→: HarmonyOS官方认证培训

相关推荐
低调小一5 小时前
深度复盘:KMP 在字节跳动的工程化落地实践
android·kotlin
编程乐学6 小时前
鸿蒙非原创--DevEcoStudio开发的奶茶点餐APP
华为·harmonyos·deveco studio·鸿蒙开发·奶茶点餐·鸿蒙大作业
鸣弦artha7 小时前
Flutter框架跨平台鸿蒙开发 —— Text Widget:文本展示的艺术
flutter·华为·harmonyos
lili-felicity8 小时前
React Native for Harmony:Rating 评分组件- 支持全星 / 半星 / 禁用 / 自定义样式
react native·华为·harmonyos
grd48 小时前
RN for OpenHarmony 小工具 App 实战:屏幕尺子实现
笔记·harmonyos
No Silver Bullet8 小时前
HarmonyOS NEXT开发进阶(十九):如何在 DevEco Studio 中查看已安装应用的运行日志
华为·harmonyos
歪楼小能手8 小时前
Android16系统go版关闭重力旋转开关后缺失手动旋转屏幕悬浮按钮
android·java·平板
崇山峻岭之间9 小时前
Matlab学习记录37
android·学习·matlab
大雷神10 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地
华为·harmonyos
南村群童欺我老无力.11 小时前
Flutter 框架跨平台鸿蒙开发 - 开发双人对战五子棋游戏
flutter·游戏·华为·typescript·harmonyos