鸿蒙——应用服务卡片显示图片和唤起指定页面

目录

1. 简介

服务卡片架构

亮点/特征

2. ArkTS卡片开发指导 

创建一个ArkTS卡片

 配置卡片的配置文件

卡片生命周期管理

3. 功能开发

3.1 刷新图片

方案一:官方文档思路

方案2 ,仅供参考(如有不对,请多指正。)

3.2 卡片动态更新内容

实现原理

实现步骤

3.3 点击卡片唤起特定页

基础实现原理

 实现步骤

总结 


1. 简介

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

服务卡片架构

图1 服务卡片架构

WidgetArchitecture

卡片的基本概念:

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

卡片的常见使用步骤如下:

图2 卡片常见使用步骤 

WidgetUse

  1. 长按“桌面图标”,弹出操作菜单。
  2. 点击“服务卡片”选项,进入卡片预览界面。
  3. 点击“添加到桌面”按钮,即可在桌面上看到新添加的卡片。

亮点/特征

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

2. ArkTS卡片开发指导 

创建一个ArkTS卡片

在已有的应用工程中,可以通过右键新建ArkTS卡片,具体的操作方式如下。

1,右键新建卡片。

说明:

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

2,根据实际业务场景,选择一个卡片模板。

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

4. ArkTS卡片创建完成后,工程中会新增如下卡片相关文件:卡片生命周期管理文件(EntryFormAbility.ets)、卡片页面文件(WidgetCard.ets)和卡片配置文件(form_config.json)。

 配置卡片的配置文件

本人开发时编辑器(DevEco Studio)会自动配置,无需手工配置!!!

如果有,请忽略这一步;没有的话,请按要求配置。

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

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

    配置示例如下:

  2. {
      "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"
              }
            ]
          }
        ]
      }
    }

  3. 卡片的具体配置信息。在上述FormExtensionAbility的元信息(“metadata”配置项)中,可以指定卡片具体配置信息的资源索引。配置示例如下:

{
  "forms": [
    {
      "name": "widget",
      "description": "This is a service widget.",
      "src": "./ets/widget/pages/WidgetCard.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"
      ],
      "formConfigAbility": "ability://com.example.entry.EntryAbility",
      "dataProxyEnabled": false,
      "isDynamic": true,
      "transparencyEnabled": false,
      "metadata": []
    }
  ]
}

卡片生命周期管理

在EntryFormAbility.ets中,实现FormExtensionAbility生命周期接口,其中在onAddForm的入参want中可以通过FormParam取出卡片的相关信息。

 

import { formBindingData, FormExtensionAbility, formInfo } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';

export default class EntryFormAbility extends FormExtensionAbility {
 // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
   onAddForm(want: Want) {
      // Called to return a FormBindingData object.
      let formData = '';
      return formBindingData.createFormBindingData(formData);
   }

   // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
   onCastToNormalForm(formId: string) {
      // Called when the form provider is notified that a temporary form is successfully
      // converted to a normal form.
   }

   // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
   onUpdateForm(formId: string) {
      // Called to notify the form provider to update a specified form.
   }

   // 需要配置formVisibleNotify为true,且为系统应用才会回调
   onChangeFormVisibility(newStatus: Record<string, number>) {
      // Called when the form provider receives form events from the system.
   }

   // 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
   onFormEvent(formId: string, message: string) {
      // Called when a specified message event defined by the form provider is triggered.
   }

   // 当对应的卡片删除时触发的回调,入参是被删除的卡片ID
   onRemoveForm(formId: string) {
      // Called to notify the form provider that a specified form has been destroyed.
   }

   // 卡片提供方接收查询卡片状态通知接口,默认返回卡片初始状态。
   onAcquireFormState(want: Want) {
      // Called to return a {@link FormState} object.
      return formInfo.FormState.READY;
   }

   //自动生成没有这个,有需求需要自己配
   onConfigurationUpdate(config: Configuration) {
   // 当前formExtensionAbility存活时更新系统配置信息时触发的回调。
   // 需注意:formExtensionAbility创建后5秒内无操作将会被清理。
   console.info('[EntryFormAbility] onConfigurationUpdate:' + JSON.stringify(config));
   }
};

这是编译器自动生成的文件,我们只需要明白每个生命周期钩子是何时触发的,知道在哪个钩子里完成我们的业务。

3. 功能开发

应用服务卡片功能很多,像卡片定时刷新和定点刷新,根据卡片状态刷新不同的内容,刷新本地和网络图片,卡片代理刷新等 ,还有router,call,message事件。

本文篇幅有限,就主要介绍我使用过的加载网络图片,刷新图片和点击卡片唤起特定页。

3.1 刷新图片

1, 下载网络图片需要使用到网络能力,需要在main目录下的module.json5文件中配置申请ohos.permission.INTERNET权限。

"requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
]

  官方文档

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

 

const TAG: string = 'WgtImgUpdateEntryFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;

export default class WgtImgUpdateEntryFormAbility extends FormExtensionAbility {
  // 在添加卡片时,打开一个本地图片并将图片内容传递给卡片页面显示
  onAddForm(want: Want): formBindingData.FormBindingData {
    // 假设在当前卡片应用的tmp目录下有一个本地图片:head.PNG
    let tempDir = this.context.getApplicationContext().tempDir;
    // 打开本地图片并获取其打开后的fd
    let file: fileFs.File;
    let imgBear: Record<string, number>;
    try {
      file = fs.openSync(tempDir + '/' + 'head.PNG');
      imgBear = {
        'imgBear': file.fd
      };
    } catch (e) {
      hilog.error(DOMAIN_NUMBER, TAG, `openSync failed: ${JSON.stringify(e as Base.BusinessError)}`);
    }

    class FormDataClass {
      text: string = 'Image: Bear';
      imgName: string = 'imgBear';
      loaded: boolean = true;
      formImages: Record<string, number> = imgBear;
    }

    let formData = new FormDataClass();

    // 将fd封装在formData中并返回至卡片页面
    return formBindingData.createFormBindingData(formData);
  }
  ...
}

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

 

const TAG: string = 'WgtImgUpdateEntryFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;

export default class WgtImgUpdateEntryFormAbility extends FormExtensionAbility {
  onFormEvent(formId: string, message: string): 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 httpRequest = http.createHttp()
    httpRequest.request(netFile, (err, data) => {
      if (!err && data.responseCode == http.ResponseCode.OK) {
        let imgFile = fs.openSync(tmpFile, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
        fs.write(imgFile.fd, data.result as ArrayBuffer).then((writeLen: number) => {
          hilog.info(DOMAIN_NUMBER, TAG, "write data to file succeed and size is:" + writeLen);
        }).catch((err: Base.BusinessError) => {
          hilog.error(DOMAIN_NUMBER, TAG, "write data to file failed with error message: " + err.message + ", error code: " + err.code);
        }).finally(() => {
          fs.closeSync(imgFile);
        });

        hilog.info(DOMAIN_NUMBER, TAG, 'ArkTSCard download complete: %{public}s', tmpFile);
        let file: fileFs.File;
        let fileInfo: Record<string, string | number> = {};
        try {
          file = fs.openSync(tmpFile);
          fileInfo[fileName] = file.fd;
        } catch (e) {
          hilog.error(DOMAIN_NUMBER, TAG, `openSync failed: ${JSON.stringify(e as Base.BusinessError)}`);
        }

        class FormDataClass {
          text: string = 'Image: Bear' + fileName;
          imgName: string = fileName;
          loaded: boolean = true;
          formImages: object = fileInfo;
        }

        let formData = new FormDataClass();
        let formInfo = formBindingData.createFormBindingData(formData);
        formProvider.updateForm(formId, formInfo).then(() => {
          hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'FormAbility updateForm success.');
        }).catch((error: Base.BusinessError) => {
          hilog.error(DOMAIN_NUMBER, TAG, `FormAbility updateForm failed: ${JSON.stringify(error)}`);
        });
      } else {
        hilog.error(DOMAIN_NUMBER, TAG, `ArkTSCard download task failed. Cause: ${JSON.stringify(err)}`);
        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('memory://' + this.imgName)
      .backgroundImageSize(ImageSize.Cover)
   }
}

我的写法---仅供参考(如有不对,请多指正)

1,编写图片加载类

 

import type fileFs from '@ohos.file.fs'
import fs from '@ohos.file.fs'

// 作用:下载需要的图片资源 然后把图片资源处理为下发给card组件使用的标准格式
class FormData {
  fileNameList: string[]
  formImages: Record<string, string | number>

  constructor(fileNameList: string[], formImages: Record<string, string | number>) {
    this.fileNameList = fileNameList
    this.formImages = formImages
  }
}
class LoadImageForFormData {
  // 要下载的图片列表
  private imageUrls: string[]
  // 图片下载完毕执行函数
  private finishCb: (formInfo: formBindingData.FormBindingData) => void
  // 当前Ability
  private ability: FormExtensionAbility
  // 当前正在下载的图片下标
  private curIndex: number = 0
  // 本地地址
  private tempDir: string = ''
  // 内存中的图片对象
  private formImages: Record<string, string | number> = {}
  // 图片文件的名称列表
  private fileNameList: string[] = []
  // 初始FormData数据
  initialFormData = new FormData([], {})

  constructor(imageUrls: string[], finishCb: (formInfo: formBindingData.FormBindingData) => void,
    ability: FormExtensionAbility) {
    this.imageUrls = imageUrls
    this.finishCb = finishCb
    this.ability = ability
    this.tempDir = ability.context.getApplicationContext().tempDir
  }

  // 动态添加要下载的图片
  addImage (imageUrls: string[]){
    this.imageUrls = imageUrls
    return this
  }
  // 开始下载图片
  startLoad ()  {
    if (this.imageUrls.length === 0) {
      console.error('please provide download imglist')
      return
    }
    let netFile = this.imageUrls[this.curIndex] // 需要在此处使用真实的网络图片下载链接
    let fileName = 'file' + Date.now()
    let tmpFile = this.tempDir + '/' + fileName
    request.downloadFile(
      this.ability.context,
      {
        url: netFile,
        filePath: tmpFile,
        enableMetered: true,
        enableRoaming: true
      }
    ).then((task) => {
      task.on('complete', () => {
        let file: fileFs.File
        try {
          // fs资源读取模块
          file = fs.openSync(tmpFile)
          this.formImages[fileName] = file.fd
        } catch (e) {
          console.error(`openSync failed: ${JSON.stringify(e)}`)
        }
        this.fileNameList.push(fileName)
        this.curIndex++
        if (this.curIndex < this.imageUrls.length) {
          // 如果还没下载完毕,继续下载
          this.startLoad()
        } else {
          // 全部下载完毕更新数据
          this.initialFormData.fileNameList = this.fileNameList
          this.initialFormData.formImages = this.formImages
          let formInfo = formBindingData.createFormBindingData(this.initialFormData)
          this.finishCb(formInfo)
        }
      })
    }).catch(() => {

    })
  }
}

2.,FormExtensionAbility中下发数据

 

// 要下载的图片
const goodsList = [
   'https://dongfangshuxing.oss-cn-beijing.aliyuncs.com/2023/03/14/yuxiangrousi.jpg',
   'https://dongfangshuxing.oss-cn-beijing.aliyuncs.com/2023/03/14/yuxiangrousi.jpg',
]
export default class EntryFormAbility extends FormExtensionAbility {
   // 手动补充返回值类型
   onAddForm(want: Want): formBindingData.FormBindingData {
      const  ImgData= new LoadImageForFormData(
         goodsList,
         (formInfo: formBindingData.FormBindingData) => {
            // 找到要更新的卡片id
            const formId = want.parameters && want.parameters['ohos.extra.param.key.form_identity'].toString()
            // 根据卡片id和信息更新卡片内容
            formProvider.updateForm(formId, formInfo)
         },
         this
      )

      ImgData.startLoad()
      // 必须要return
      return formBindingData.createFormBindingData(ImgData.initialFormData)
   }
}

 goodsList放的是我的网络图片,各位可以根据自己业务的需求安放自己的图片数据。

3,卡片组件消费数据

 

let storageWidgetImageUpdate = new LocalStorage()

@Entry(storageWidgetImageUpdate)
@Component
struct WidgetCard {
   @LocalStorageProp('fileNameList') fileNameList: string[] = []

   build() {
      Column() {
         Row() {
            ForEach(
               this.fileNameList,
               (url: string) => {
                  Row() {
                     Image('memory://'+url)
                        .borderRadius(12)
                        .width(50)
                        .aspectRatio(1)
                  }
                  .backgroundColor('#eee')
                  .borderRadius(12)
               }
            )
         }
         .justifyContent(FlexAlign.SpaceBetween)
         .width('100%')
         .layoutWeight(1)
         .padding({
            left: 20,
            right: 20
         })
         .backgroundColor('#fff')
         .borderRadius({
            topRight: 16,
            topLeft: 16
         })
         .onClick(() => {

         })

         Row() {
            Text('Hello World')
               .fontColor('#fff')
               .fontSize(16)
         }
         .height(40)
         .width('100%')
         .justifyContent(FlexAlign.Center)
      }
      .linearGradient({
         angle: '135',
         colors: [
            [Color.White, '0%'],
            [Color.Blue, '100%']
         ]
      })
      .height('100%')
   }
}

具体效果如图:

 因为篇幅有限,后面的卡片更新和跳转指定页面我就不官方展示官方方案了,有兴趣的请自行去官方文档查看。

文档地址:卡片事件能力说明 (openharmony.cn)

3.2 卡片动态更新内容

在点击刷新按钮之后,期望可以更新卡片的商品内容,显示最新的推荐内容。

实现原理

在卡片组件中触发message事件,在FormExtensionAblility的onForEvent钩子中监听事件,然后执行卡片的updateForm生命周期方法传入要更新的数据即可

  1. 卡片触发一个事件 message
  2. FormAbility中监听事件触发,监听到之后依旧按照标准的数据格式,把新的图片在此进行下载,继续处理成标准格式,继续调用卡片更新的固定方法传入新的图片内容
实现步骤

1,更新UI结构

 Row() {
   Text('Hello World')
      .fontColor('#fff')
      .fontSize(16)
   Button('OK')
      .onClick(()=>{
         postCardAction(this,
            {
               action: 'message',
               abilityName:'EntryAbility'
            }
         )
      })
}
.height(40)
.width('100%')
.justifyContent(FlexAlign.Center)

2,FormExtensionAbility中监听事件并更新卡片

 

const newGoodsList = [
   'https://dongfangshuxing.oss-cn-beijing.aliyuncs.com/2023/03/10/18988304-a76a-46c4-a39f-26ea31e7a179.jpg',
   'https://dongfangshuxing.oss-cn-beijing.aliyuncs.com/2023/03/10/18988304-a76a-46c4-a39f-26ea31e7a179.jpg',
]

   onFormEvent(formId: string, message: string) {
      const lF = new LoadImageForFormData(
         newGoodsList,
         (formInfo: formBindingData.FormBindingData) => {
            // 根据卡片id和信息更新卡片内容
            formProvider.updateForm(formId, formInfo)
         },
         this
      )

      lF.startLoad()
   }
 

实现效果图如下:

 

3.3 点击卡片唤起特定页

当我们点击卡片主体内容的时候,期望可以唤起特定的落地页

基础实现原理
  1. 卡片组件点击之后通过postCardAction触发router事件并携带参数
  2. 在应用的UIAbility中接收router事件,解析参数完成跳转

 实现步骤

1. 准备落地页(新建一个页面Page)

@Entry
@Component
struct RouterPage {
   @State message: string = 'Hello HarmonyOS';

   build() {
      Row() {
         Column() {
            Text(this.message)
               .fontSize(50)
               .fontWeight(FontWeight.Bold)
         }
         .width('100%')
      }
      .height('100%')
   }
}

点击之后通过方法传递参数

 .onClick(() => {
    postCardAction(this, {
      action: 'router',
      abilityName: 'EntryAbility',
      params: { targetPage: 'RouterPage' }
    })
  })

3. 在UIAbility中接收router事件并获取参数,根据传递的params不同,选择拉起不同的页面

1-未启动应用的情况

 

export default class EntryAbility extends UIAbility {
  // 存放拉起页地址
  private selectPage: string = ''
  
  // UIAbility如果没有运行,会执行onCreate
  async onCreate(want, launchParam) {
    if (want.parameters !== undefined) {
      let params: Record<string, string> = JSON.parse(JSON.stringify(want.parameters))
      this.selectPage = params.targetPage
    }
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    let targetPage: string = this.selectPage ? `pages/${this.selectPage}` : 'pages/Index'
    
    windowStage.loadContent(targetPage, (err, data) => {
      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. Data: %{public}s', JSON.stringify(data) ?? '');
    })
  }
}

 2- 已经启动的情况

注意:已经启动时只执行 onNewWant钩子函数

第一次启动时存下来 windowStage实例,二次启动时直接在onNewWant钩子中通过windowState实例

手动调用onWindowStageCreate方法 执行判断逻辑

 

export default class EntryAbility extends UIAbility {
  private selectPage: string = ''
  private currentWindowStage: window.WindowStage | null = null

  // UIAbility如果已经运行 会执行onNewWant
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    console.info("卡片 onNewWant:" + JSON.stringify(want))
    if (want.parameters?.params !== undefined) {
      let params: Record<string, string> = JSON.parse(JSON.stringify(want.parameters))
      this.selectPage = params.targetPage
    }
    if (this.currentWindowStage !== null) {
      this.onWindowStageCreate(this.currentWindowStage)
    }
  }


  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    let targetPage: string = this.selectPage ? `pages/${this.selectPage}` : 'pages/Index'

    if (this.currentWindowStage === null) {
      this.currentWindowStage = windowStage
    }

    windowStage.loadContent(targetPage, (err, data) => {
      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. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }
}

效果如下(视频上传不了,我就只能放图片了,抱歉)

  ------》(点击OK)

   ------》(点击图片)

总结 

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

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

在功能开发中,主要展现的是我的写法,仅供参考。如果想要按照自己的思路完成功能,请参考官方文档:Form Kit简介 (openharmony.cn)

  • 44
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值