鸿蒙服务卡片

205 阅读4分钟

鸿蒙服务卡片

1.ArkTS卡片相关模块

1.1、form_config.json

resources/base/profile/目录下的form_config.json配置文件用来配置卡片的布局样式,定时定点刷新,动静态卡片设置等。可以去官网阅读form_config.json文件的各个字段信息。快捷访问

由于该配置文件是一个数组对象,因此可以在该配置文件中放置多张不同尺寸的卡片,卡片的布局可以通过src属性名来配置。

{
  "forms": [
    {
      "name": "widget1",
      "displayName": "$string:widget_display_name",
      "description": "$string:widget_desc",
      "src": "./ets/widget/pages/WidgetCard.ets",
      "uiSyntax": "arkts",
      "window": {
        "designWidth": 720,
        "autoDesignWidth": true
      },
      "colorMode": "auto",
      "isDynamic": false,
      "isDefault": true,
      "updateEnabled": false,
      "scheduledUpdateTime": "10:30",
      "updateDuration": 1,
      "defaultDimension": "2*4",
      "supportDimensions": [
        "2*4"
      ]
    },
    {
      "name": "widget2",
      "displayName": "$string:widget_display_name",
      "description": "$string:widget_desc",
      "src": "./ets/widget/pages/WidgetCard.ets",
      "uiSyntax": "arkts",
      "window": {
        "designWidth": 720,
        "autoDesignWidth": true
      },
      "colorMode": "auto",
      "isDynamic": false,
      "isDefault": false,
      "updateEnabled": false,
      "scheduledUpdateTime": "10:30",
      "updateDuration": 1,
      "defaultDimension": "4*4",
      "supportDimensions": [
        "4*4"
      ]
    }
  ]
}

临时卡片预览图如下:

image.png image.png

1.2、EntryFormAbility

在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.
  }

  onFormEvent(formId: string, message: string) {
    // Called when a specified message event defined by the form provider is triggered.
  }

  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;
  }
};

1.3、保存卡片id,包括临时卡片id

鸿蒙的服务卡片在预览的时候会产生临时卡片的id,此时会调用onAddForm回调,当关闭服务卡片预览弹框时,系统会调用onRemoveForm移除临时卡片id。如果我们需要将卡片加桌到桌面,在有多张临时卡片的情况下,保存加桌到桌面的id是一件不容易的事情。这里用到的方法是在onAddForm中先保存全部卡片的id,最后在onRemoveForm中维护卡片id,只保存加桌到桌面的卡片id。

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

export default class EntryFormAbility extends FormExtensionAbility {
  /**
   * 如果卡片弹框有多张卡片,每次懒加载只会先加载前3张卡片,当用户向右滑动卡片时,才继续懒加载后续的卡片,这意味着onAddForm回调会被调用多次
   */
  onAddForm(want: Want) {
    Logger.debug(`EntryFormAbility onAddForm want: ${JSON.stringify(want)}`);
    let formData = '';
    // 临时卡片id也会保存,所以在onRemoveForm回调中需要删除掉,华为官方暂时没有好的办法区分临时卡片和常态卡片
    this.saveServiceCardId(want);
    return formBindingData.createFormBindingData(formData);
  }

  onCastToNormalForm(formId: string) {
    Logger.debug(`EntryFormAbility onCastToNormalForm formId: ${formId}`);
  }

  onUpdateForm(formId: string) {
    // TODO 定时刷新时触发的回调,需要刷新卡片
  }

  onFormEvent(formId: string, message: string) {
    Logger.debug(`EntryFormAbility onFormEvent message: ${message}, formId: ${formId}`);
  }

  /**
   * 如果加桌卡片弹框有多张卡片,当预览结束关闭弹框时,会调用onRemoveForm回调,此时需要删除临时缓存的卡片id
   */
  onRemoveForm(formId: string) {
    Logger.debug(`EntryFormAbility onRemoveForm formId: ${formId}`);
    this.deleteServiceCardId(formId);
  }

  onAcquireFormState(want: Want) {
    // Called to return a {@link FormState} object.
    Logger.debug(`EntryFormAbility onAcquireFormState want: ${JSON.stringify(want)}`);
    return formInfo.FormState.READY;
  }

  /**
   * 删除没有加桌到桌面的卡片id,维护卡片id列表
   */
  private deleteServiceCardId(formId: string) {
    let serviceCardIds = preferences.getPreferencesSync(this.context, {
      name: 'CloudPhoneCard'
    });
    let preFormIdsArray: Array<String> = JSON.parse(serviceCardIds.getSync('formIds', '[]').toString());
    preFormIdsArray.splice(preFormIdsArray.indexOf(formId), 1);
    serviceCardIds.putSync('formIds', JSON.stringify(preFormIdsArray));
    serviceCardIds.flush();
    Logger.debug(`EntryFormAbility deleteServiceCardId serviceCardIds: ${serviceCardIds.getSync('formIds', '[]')}`);
  }

  /**
   * 保存服务卡片的id
   */
  private saveServiceCardId(want: Want): void {
    Logger.debug(`EntryFormAbility saveServiceCardId want: ${JSON.stringify(want)}`);
    let formId: string = '';
    // 获取加桌服务卡片id
    let serviceCardIds = preferences.getPreferencesSync(this.context, {
      name: 'CloudPhoneCard'
    });
    if (want.parameters && want.parameters?.[formInfo.FormParam.IDENTITY_KEY] !== undefined) {
      formId = want?.parameters?.[formInfo.FormParam.IDENTITY_KEY].toString();
    } else {
      return;
    }
    let preFormIdsArray: Array<String> = JSON.parse(serviceCardIds.getSync('formIds', '[]').toString());
    if (preFormIdsArray.indexOf(formId) === -1) {
      preFormIdsArray.push(formId);
    }
    serviceCardIds.putSync('formIds', JSON.stringify(preFormIdsArray));
    serviceCardIds.flush();
    Logger.debug(`EntryFormAbility saveServiceCardId serviceCardIds: ${serviceCardIds.getSync('formIds', '[]')}`);
  }
};

1.4、 应用提供方获取卡片id,这里只得到加桌的卡片id

保存了加桌到桌面的id后,可以在应用中读取id,方法如下:

private getFormIds(): void {
  let options: preferences.Options = { name: 'CloudPhoneCard' };
  // 为了得到最新的数据,需要先清除缓存文件
  preferences.removePreferencesFromCacheSync(getContext(this),options)
  let formIds = preferences.getPreferencesSync(getContext(this), { name: 'CloudPhoneCard' });
  Logger.debug(`EntryAbility getFormIds formIds: ${formIds.getSync('formIds', '[]')}`);
}

1.5、 在卡片中更新网络图片

注意,需要在动态卡片中才能更新图片。如果我们需要在卡片中动态更新网络图片,需要先将网络图片下载到本地保存后再在卡片中更新。在卡片布局页面中需要使用该图片需要再加上前缀memory://,可看官方示例

/**
 * 如果后续卡片需要使用网络图片,需要调用updateNetImg方法更新卡片
 * @aram formId 卡片id
 * @param src 网络图片地址
 */
private async updateNetImg(formId: string, src: string): Promise<void> {
  let fileName = 'file' + Date.now();
  // 在本地沙盒下创建临时文件,用于保存下载的网络图片
  let tmpFile = this.context.getApplicationContext().tempDir + '/' + fileName;
  let imgMap: Record<string, number> = {};
  class FormDataClass {
    // 卡片需要显示图片场景, 必须和下列字段formImages 中的key fileName 相同。
    updateImg: string = fileName;
    // 卡片需要显示图片场景, 必填字段(formImages 不可缺省或改名), fileName 对应 fd
    formImages: Record<string, number> = imgMap;
  }
  let httpRequest = http.createHttp();
  let data = await httpRequest.request(src);
  if (data?.responseCode == http.ResponseCode.OK) {
    try {
      let imgFile = fileIo.openSync(tmpFile, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
      imgMap[fileName] = imgFile.fd;
      try{
        let writeLen: number = await fileIo.write(imgFile.fd, data.result as ArrayBuffer);
        Logger.debug(`EntryFormAbility updateNetImg writeLen: ${writeLen}, tempFile: ${tmpFile}`);
        let formData = new FormDataClass();
        let formInfo = formBindingData.createFormBindingData(formData);
        await formProvider.updateForm(formId, formInfo);
        Logger.debug(`EntryFormAbility updateNetImg updateForm success.`);
      } catch (error) {
        Logger.debug(`EntryFormAbility updateNetImg write data to file failed with error: ${JSON.stringify(error)}`);
      } finally {
        fileIo.closeSync(imgFile);
      };
    } catch (error) {
      Logger.debug(`EntryFormAbility updateNetImg openSync failed with error: ${JSON.stringify(error)}`);
    }
  } else {
    let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData();
    formProvider.updateForm(formId, formInfo);
  }
  httpRequest.destroy();
}