精讲鸿蒙服务卡片开发

📌注意:

  1. 普通应用也可以添加卡片
  2. 元服务默认就有一张2*2的卡片,并且因为元服务没有icon,以卡片作为入口
  3. 后续课程中如非特别说明,卡片的开发方式在普通应用和元服务中一致

1 服务卡片介绍

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

1.1 卡片的基本概念

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

1.2 卡片常用添加方法

**针对应用**

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

1.2.2 针对元服务

  1. 手指捏合屏幕
  2. 点击“服务卡片”选项,拉到底,找到“其他服务卡片”,找到元服务,进入卡片预览界面
  3. 点击添加到“桌面按钮”,即可在桌面上看到新添加的卡片

1.2.3 划重点

  1. 应用和元服务都可以使用服务卡片
    1. 对应用而言:卡片是可选的,因为有icon作为入口
    2. 对元服务而言:卡片是必须的,因为没有icon作为入口,默认
  2. 添加卡片到桌面:
    1. 对应用而言:选中icon,点击 ”服务卡片“,选择并添加即可
    2. 对元服务而言:捏合桌面,选择”服务卡片“,在 “其他服务卡片”中找到元应用,选择卡片并添加即可

1.3 服务卡片UI页面开发方式

在Stage模型下,服务卡片的UI页面支持通过ArkTSJS两种语言进行开发:

  • 基于声明式范式ArkTS UI开发的卡片,简称ArkTS卡片
  • 基于类Web范式JS UI开发的卡片,简称JS卡片。

ArkTS卡片与JS卡片具备不同的实现原理及特征,在场景能力上的差异如下表所示。

类别JS卡片ArkTS卡片
开发范式类Web范式声明式范式
组件能力支持支持
布局能力支持支持
事件能力支持支持
自定义动效不支持支持
自定义绘制不支持支持
逻辑代码执行(不包含import能力)不支持支持

划重点: 开发中推荐使用 ArkTS 卡片进行开发,可以使用最多的ArkTs的特性,和应用开发体验基本一致

2 创建服务卡片

2.1 创建步骤

  1. 创建卡片:右键-新建-Service Widget

  1. 根据实际业务场景,选择一个卡片模板。
    1. 基础卡片:最简单的卡片模版
    2. 图文信息:图片在上,信息在下的卡片模版
    3. 沉浸式信息:图片作为背景,信息覆盖在上面
  2. 在选择卡片的开发语言类型(Language)时,选择ArkTS选项,然后单击“Finish”,即可完成ArkTS卡片创建。

2.2 卡片涉及文件

ArkTS卡片创建之后需要关注的几个核心文件:

  • FormExtensionAbility:卡片业务逻辑模块,提供卡片创建、销毁、刷新等生命周期回调。
  • 卡片页面WidgetCard.ets:卡片UI模块,包含页面控件、布局、事件等显示和交互信息。
  • form_config.json:卡片的描述和配置信息,包括定义卡片大小,定时刷新等功能

2.3 卡片相关配置

相比于之前应用的界面,现在额外增加了卡片,在进入到卡片界面开发之后,咱们先搞明白几个较为常见,都和卡片配置相关的问题

  1. 卡片如何和应用进行关联?
  2. 多张卡片,如何设置默认卡片(服务卡片界面默认选中的那张)?
  3. 如何调整卡片保存的位置?
  4. 如何删除卡片(某张 / 所有)?

2.3.1 问题1 卡片如何和应用进行关联?

卡片在module.json5配置文件中的extensionAbilities标签下配置,添加卡片时会自动添加卡片与应用的关联。

配置示例如下:

2.3.2 问题2、3、4

  1. 多张卡片,如何设置默认卡片(服务卡片界面默认选中的那张)?

  2. 如何调整卡片保存的位置?

  3. 如何删除卡片(某张 / 所有)?

默认情况下会使用开发视图的resources/base/profile/目录下的form_config.json作为卡片profile配置文件。

内部字段详细结构说明可以参考官方文档,这里咱们只针对需求部分进行说明

多张卡片,如何设置默认卡片(服务卡片界面默认选中的那张)?

  1. 调整form_config.json中的卡片配置信息isDefault为true
  2. 每个UIAbility有且只有一个默认卡片,所以当有多个卡片信息时,只需要配置一个的isDefault为true

如何调整卡片保存的位置?

  1. src:表示卡片对应的UI代码的完整路径,使用ArkTs卡片时,需要包含后缀名
  2. 调整卡片位置及对应的src属性即可改变卡片保存位置

form_config.json

{
  "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": false,
      "scheduledUpdateTime": "10:30",
      "updateDuration": 1,
      "defaultDimension": "2*2",
      "supportDimensions": [
        "2*2"
      ]
    },
    {
      "name": "widget1",
      "description": "This is a service widget.",
      "src": "./ets/widget1/pages/Widget1Card.ets",
      "uiSyntax": "arkts",
      "window": {
        "designWidth": 720,
        "autoDesignWidth": true
      },
      "colorMode": "auto",
        "isDefault": false, 
      "updateEnabled": false,
      "scheduledUpdateTime": "10:30",
      "updateDuration": 1,
      "defaultDimension": "2*2",
      "supportDimensions": [
        "2*2"
      ]
    }
  ]
}
  1. 如何删除卡片?
    1. 删除某张卡片:
      1. 删除卡片文件:ets/widget/pages/xxx.ets
      2. 删除配置信息:resources/base/profile/form_config.json,卡片数组中,和卡片相关的那个对象即可
    2. 删除所有卡片:
      1. 删除卡片文件:ets/widget/pages/xxx.ets
      2. 删除配置文件:resources/base/profile/form_config.json
      3. 删除module.json5中,和卡片相关的配置
        module.json5
{
  "module": {
    ...
    "extensionAbilities": [
       {
        "name": "EntryFormAbility",
        "srcEntrance": "./ets/entryformability/EntryFormAbility.ts",
        "label": "$string:EntryFormAbility_label",
        "description": "$string:EntryFormAbility_desc",
        "type": "form",
        "metadata": [
          {
            "name": "ohos.extension.form",
            "resource": "$profile:form_config"
          }
        ]
      } 
    ]
  }
}

3 服务卡片页面开发

📌使用ArkTS方式开发的卡片,支持绝大多数ArkTS声明式开发范式,但是因为卡片一般显示桌面应用,为确保桌面的使用体验以及功耗相关考虑,对于ArkTS的卡片有一些约束,这里列出影响较大的2个:

  1. 暂不支持导入模块
  1. 仅支持声明式范式的部分组件、事件、动效、数据管理、状态管理和API能力(常用的都可以使用)

更为具体的约束细节,可以参考如下官方文档

  1. ArkTS卡片的约束2. 卡片能力说明

3.1 页面尺寸

服务卡片支持多种卡片尺寸:微、小、中、大。卡片展示的尺寸大小分别对应桌面不同的宫格数量,微卡片对应 12 宫格,小卡片对应 22 宫格,中卡片对应 24 宫格,大卡片对应 44 宫格。

卡片尺寸对应宫格数
微卡片1*2 宫格
小卡片2*2 宫格
中卡片2*4 宫格
大卡片4*4 宫格

同一个应用可以支持多种不同类型的服务卡片,不同尺寸与类型可以通过卡片管理界面进行切换和选择。

上滑应用图标展示的默认卡片的尺寸由开发者指定。

服务卡片在上架时必定要包含小尺寸卡片,即2*2尺寸卡片,该尺寸在不同设备上具备最佳兼容性。

划重点:

  1. 实际开发时根据产品的要求,添加对应尺寸的卡片即可
  2. 服务卡片的设计原则参考-服务卡片设计原则

3.2 尺寸调整

调整卡片属性中的 supportDimensions 属性即可调整对应卡片的宫格数,需要注意的是,只能设置如下的值:

12,22,24,44

同时为了在布局实现时更为便捷,可以每张卡就设置一种宫格数

  • defaultDimension :表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中。
  • supportDimensions:表示卡片支持的外观规格,取值范围:
    • 1 * 2:表示1行2列的二宫格。
    • 2 * 2:表示2行2列的四宫格。
    • 2 * 4:表示2行4列的八宫格。
    • 4 * 4:表示4行4列的十六宫格。

📌defaultDimension 的值只能是来源于supportDimensions数组中定义的值,否则卡片不会显示

{
  "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*4" ,
       "supportDimensions": [ 
        "2*4"
      ] 
      
        // 错误写法 
        "defaultDimension": " 2*4 ",
      "supportDimensions": [
        " 2*2 "
      ] 
    }
  ]
}

3.3 页面开发

实际开发时虽然卡片能力有部分限制,但是绝大多数都是支持的,根据实际需求实现效果即可,这里不再展开,卡片开发的重点是之后的 卡片事件 及 数据交互

3.4 卡片生命周期

卡片生命周期管理文件(EntryFormAbility.ts)

EntryFormAbility.ts页面中默认有很多生命周期函数,其中使用较为频繁的生命周期函数为:

  1. onAddForm:添加卡片时触发
  2. onUpdateForm:卡片支持定时更新/定点更新/卡片使用方主动请求更新功能时触发
  3. onFormEvent:卡片通过message触发事件时触发
  4. onRemoveForm:移除卡片时触发
// FormInfo模块提供对卡片的相关卡片信息和状态进行设置和查询的能力
import formInfo from '@ohos.app.form.formInfo';
// 卡片数据绑定模块提供卡片数据绑定的能力。包括FormBindingData对象的创建、相关信息的描述。
import formBindingData from '@ohos.app.form.formBindingData';
// FormExtensionAbility为卡片扩展模块,提供卡片创建、销毁、刷新等生命周期回调。
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';

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

  // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
  onCastToNormalForm(formId) {
    // Called when the form provider is notified that a temporary form is successfully
    // converted to a normal form.
  }
  // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
  onUpdateForm(formId) {
    // Called to notify the form provider to update a specified form.
  }

  onChangeFormVisibility(newStatus) {
    // Called when the form provider receives form events from the system.
  }

  // 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
  onFormEvent(formId, message) {
    // Called when a specified message event defined by the form provider is triggered.
  }
  // 当对应的卡片删除时触发的回调,入参是被删除的卡片ID
  onRemoveForm(formId) {
    // Called to notify the form provider that a specified form has been destroyed.
  }
  //  卡片提供方接收查询卡片状态通知接口,默认返回卡片初始状态。
  onAcquireFormState(want) {
    // Called to return a {@link FormState} object.
    return formInfo.FormState.READY;
  }
};

📌FormExtensionAbility进程不能常驻后台
即在卡片生命周期函数中无法处理长时间的任务,在生命周期调度完成后会继续存活5秒
如果5秒内没有新的生命周期回调触发则进程自动退出。
针对可能需要5秒以上才能完成的业务逻辑,建议拉起主应用进行处理,处理完成后使用updateForm方法通知卡片进行刷新。

4 给卡片设置数据

因为卡片不支持import,所以没法通过引入@ohos.net.http等包来进行数据请求,因此需要通过借助外部能力获取数据后,再传入卡片内部进行展示。

外部能力有:

  1. EntryFormAbility / EntryAbility
  2. 应用或者元服务的各个页面(包括组件)

常见的场景:

  1. 添加卡片到桌面时设置初始数据 → EntryFormAbility 的onAddForm生命周期函数中完成
  2. 在应用或者元服务页面获取完数据后,主动将数据更新到卡片中

4.1 添加卡片到桌面时初始化数据

当用户把卡片添加到桌面上时,会触发FormExtensionAbility中的 onAddForm生命周期方法,此时我们可以给卡片传递初始数据

分为同步异步两种方式

4.1.1 同步方式(不常用)

当将FormExtensionAbility中一个已有的数据传递给卡片时,可以直接使用同步方式

分3步走:

  1. 在FormExtensionAbility中的onAddForm中定义数据对象
  2. 通过onAddForm的返回值返回数据
  3. 卡片中通过localStorageProp接收

案例场景: 在onAddForm中给卡片传入 一个标题 和 一个描述信息,在卡片中正常显示

  1. 在FormExtensionAbility中的onAddForm中定义需要传递给卡片的数据对象
  2. 在onAddForm中返回通过 formBindingData.createFormBindingData 绑定的数据
 export default class EntryFormAbility extends FormExtensionAbility {
   onAddForm(want) {
      // 1. 定义需要传递给卡片的数据对象
      let formData = {
        title:'博学谷鸿蒙课程',
        desc:'鸿蒙千帆起,我要当舵手'
      };
      // 2. 返回绑定后的数据对象,卡片将自动获取到该数据进行显示
      return formBindingData.createFormBindingData(formData);
    }
 }
  1. 卡片WidgetCard.ets中通过localStorageProp接收
const storage = new LocalStorage(); 

 @Entry(storage) 
@Component
struct WidgetCard {
    @LocalStorageProp("title") title: string = '';
  @LocalStorageProp("desc") desc: string = '' 

  build() {
    Column() {
        Text(this.title) 
       Text(this.desc) 
    }
    .width("100%")
    .height("100%")
    .onClick(() => {
      postCardAction(this, {
        "action": "router",
        "abilityName": "EntryAbility",
        "params": {
          "message": "add detail"
        }
      });
    })
  }
}

在模拟器中将卡片到桌面,如果上述代码没有问题的话,应该就可以看到传递的默认数据渲染到卡片上啦

划重点:

  1. 在EntryFormAbility中通过onAddForm的返回值设置初始数据
  2. 在卡片中通过LocalStorageProp接收并使用数据

4.1.2 异步方式(常用)

当FormExtensionAbility请求一个数据后,是异步回调的,此时需要用到异步方式更新卡片数据

分3步走:

  1. 在FormExtensionAbility中的onAddForm中异步获取一个数据
  2. 通过formProvider.updateForm()方法异步更新卡片数据
  3. 卡片中通过localStorageProp接收

案例场景: 在FormExtensionAbility的onAddForm中异步获取数据后,将数据更新到卡片上

数据:① title:博学谷鸿蒙课程 ② desc:鸿蒙千帆起,我要当舵手

  1. 在EntryFormAbility的onAddForm方法中用setTimeout模拟异步请求数据
  2. 在setTimeout回调函数中使用formProvider.updateForm方法传入异步数据
import formInfo from '@ohos.app.form.formInfo';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formProvider from '@ohos.app.form.formProvider';


export default class EntryFormAbility extends FormExtensionAbility {

  onAddForm(want) {
     let formData = {
      title:'鸿蒙课程',
      desc:'鸿蒙千帆起,我要当舵手'
    }; 

    // 1. 定义需要传递给卡片的数据对象
     setTimeout(()=>{
      formData = {
        title:'鸿蒙课程-刷新后',
        desc:'鸿蒙千帆起,我要当舵手 - 刷新后'
      };
      // 1. 定义需要传递给卡片的数据对象
       let bindData = formBindingData.createFormBindingData(formData); 
      // 2. 获取当前添加到桌面的卡片id
       let formid = want.parameters[formInfo.FormParam.IDENTITY_KEY]; 
      // 3. 更新数据到卡片上
       formProvider.updateForm(formid,bindData) .then(res=>{
        console.log('mylog','更新成功');
      }).catch(err=>{
        console.log('mylog','更新失败');
      });
      
    },2000);  // 注意:此处不能超过5000,即5秒钟,因为EntryFormAbility的生命周期为5秒 

    // 当异步更新时,onAddForm方法最后可以返回 null
     return formBindingData.createFormBindingData(formData); 
  }
};

📌注意 FormExtensionAbility进程不能常驻后台,在生命周期调度完成后会继续存在5秒,如5秒内没有新的生命周期回调触发则进程自动退出

  1. 卡片WidgetCard.ets中通过localStorageProp接收
const storage = new LocalStorage();

@Entry(storage)
@Component
struct WidgetCard {
  @LocalStorageProp("title") title: string = '';
  @LocalStorageProp("desc") desc: string = ''

  build() {
    Column() {
      Text(this.title)
      Text(this.desc)
    }
    .width("100%")
    .height("100%")
    .onClick(() => {
      postCardAction(this, {
        "action": "router",
        "abilityName": "EntryAbility",
        "params": {
          "message": "add detail"
        }
      });
    })
  }
}

4.2 在应用 /元服务中更新数据到卡片

卡片添加到桌面上以后,用户在访问应用/元服务中的页面时,可以从页面中主动向卡片推送数据,以此来更新卡片数据

分为3步走

  1. 在卡片添加到桌面时,可以在onAddForm中将卡片id保存到首选项中
  2. 在主应用或者元服务页面中调用**formProvider.updateForm(**卡片id,数据) 给卡片发送数据
  3. 卡片中通过localStorageProp接收数据

前置准备:在项目common中创建一个PreferencesUtil.ts工具类函数(以后可以直接使用它来存储和获取卡片id数据)

PreferencesUtil.zip

4.2.1 存卡片id

  1. 在卡片添加到桌面时,可以在onAddForm中将卡片id保存到首选项中(使用PreferencesUtil工具函数实现)
import formInfo from '@ohos.app.form.formInfo';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import { PreferencesUtil } from '../common/PreferencesUtil';


export default class EntryFormAbility extends FormExtensionAbility {

  onAddForm(want) {
    let formData = {
      title:'鸿蒙课程',
      desc:'鸿蒙千帆起,我要当舵手'
    };

    // 1. 获取卡片id
    let formId = want.parameters[formInfo.FormParam.IDENTITY_KEY];
    //2. 将卡片id保存到首选项
    PreferencesUtil.getInstance().addFormId(this.context,formId);

    // 2. 将数据转为卡片识别的对象返回
    return formBindingData.createFormBindingData(formData);
  }
};

4.2.2 页面中发送数据

  1. 在主应用或者元服务页面中调用**formProvider.updateForm(**卡片id,数据) 给卡片发送数据

在src/main/ets/pages/Home/QATop.ets中向卡片发送数据

 async aboutToAppear() {
    //  1. 从首选项获取卡片id
     let cardids = await PreferencesUtil.getInstance().getFormIds(getContext(this)); 

    // 2. 遍历卡片id,更新卡片数据
    let data = {
      title: '鸿蒙课程 - 主应用推送刷新'+Math.random(),
      desc: '鸿蒙千帆起,我要当舵手-主应用推送刷新'+Math.random()
    };

     cardids.forEach(cardid => {
      let bindData = formBindingData.createFormBindingData(data);
      formProvider.updateForm(cardid, bindData)
        .then(res => {
          console.log('mylog',JSON.stringify(res))
        })
        .catch(err => {
          console.log('mylog','err',JSON.stringify(err))
        })
    }) 
  }
  
  
Button('更新卡片数据')
  .onClick(async () => {
    await this.updataData();
  })

4.2.3 卡片中接收数据

卡片WidgetCard.ets中通过localStorageProp接收

const storage = new LocalStorage();

@Entry(storage) 
@Component
struct WidgetCard {
   @LocalStorageProp("title") title: string = '';
  @LocalStorageProp("desc") desc: string = '' 

  build() {
    Column() {
      Text(this.title)
      Text(this.desc)
    }
    .width("100%")
    .height("100%")
    .onClick(() => {
      postCardAction(this, {
        "action": "router",
        "abilityName": "EntryAbility",
        "params": {
          "message": "add detail"
        }
      });
    })
  }
}

4.2.4 测试卡片数据刷新结果

  • 添加卡片到桌面上
  • 进入主应用执行刷新数据
  • 检查桌面上的卡片数据是否更新成功

📌**注意:**坑点: 添加卡片到桌面后,在不杀掉主应用的情况下,通过主应用向卡片更新数据不成功,需要先杀掉主应用后再打开主应用才能正常

4.2.5 主应用不能刷新卡片数据bug解决

📌坑点: 添加卡片到桌面后,在不杀掉主应用的情况下,通过主应用向卡片更新数据不成功,需要先杀掉主应用后再打开主应用才能正常

原因: 在主应用中获取不到onAddForm函数中保存到首选项的卡片id数据(预测卡片和主应用是两个进程,主应用不杀掉之前,2个进程间数据不能共享)

解决: 在onAddForm函数中通知主应用将卡片id也保存一份到自己的首选项中,自己存自己取就能解决进程间数据不能共享的问题

  1. 在onAddForm生命周期事件中增加发布方法
import { PublishEventType } from '../common/Constants';

onAddForm(want) {
    // Called to return a FormBindingData object.
    let formData = {
      title:'博学谷鸿蒙课程',
      desc:'鸿蒙千帆起,我要当舵手'
    };

    // 1. 定义需要传递给卡片的数据对象
    setTimeout(()=>{
      formData = {
        title:'鸿蒙课程-刷新后',
        desc:'鸿蒙千帆起,我要当舵手 - 刷新后'
      };
      // 1. 定义需要传递给卡片的数据对象
      let bindData = formBindingData.createFormBindingData(formData);
      // 2. 获取当前添加到桌面的卡片id
      let formid = want.parameters[formInfo.FormParam.IDENTITY_KEY];

      PreferencesUtil.getInstance().addFormId(this.context,formid)
        SubscriberClass.publish(PublishEventType.APP_PUBLISH,formid) 

      // 3. 更新数据到卡片上
      formProvider.updateForm(formid,bindData).then(res=>{
        console.log('mylog','更新成功');
      }).catch(err=>{
        console.log('mylog','更新失败');
      });

    },2000); // 注意:此处不能超过5000,即5秒钟,因为EntryFormAbility的生命周期为5秒


    return formBindingData.createFormBindingData(formData);
  }
  1. 在index.ets中增加订阅方法
 async aboutToAppear(){
  //   判断如果用户登录过,则首选项中有数据,则获取到数据后再重新设置一下内存中的用户数据
  //   否则就跳转到登录页面
    let user = await AppStorageKit.GetLoginUser(getContext(this));
    if(user && user.uid){
    // 用户存在
      AppStorageKit.SetLoginUser(user,getContext(this))
      console.log('Index,判断用户是存在的')
    }else{
      router.pushUrl({url:Constants.PAGE_LOGIN})
    }

 //  接收通知保存卡片数据
 SubscriberClass.subscribe(PublishEventType.APP_PUBLISH,null,(formid)=>{
   PreferencesUtil.getInstance().addFormId(getContext(this),formid)
   console.log('mylog->','publish',formid)
 }) 
  }

5 卡片事件

为什么要有卡片事件: 因为卡片本身不能导入任何包来进行操作,比如:不能直接做http请求,所以只能通过不同事件,调起各种能力来进行相关功能操作。

在卡片内部可以通过postCardAction()主动与主应用的Ability以及卡片自己的Ability进行交互,当前支持:

  1. router:拉起应用到前台,用户直接操作应用中的功能
  2. call:拉起应用到后台,用户无感知,不做任何界面操作 - 长时任务
  3. message:拉起FormExtensionAbility(卡片生命周期管理文件)- 短时任务 5秒以内

**postCardAction**参数

接口定义: postCardAction(component: Object, action: Object): void

接口参数说明:

参数名参数类型必填参数描述
componentObject当前自定义组件的实例,通常传入this。
actionObjectaction的具体描述,详情见下表。

action参数说明:

KeyValue样例描述
“action”stringaction的类型,支持三种预定义的类型: * “router”:跳转到提供方应用的指定UIAbility。 * ** “message”:自定义消息。触发后会调用提供方FormExtensionAbility的onFormEvent()生命周期回调。 * ** “call”:后台启动提供方应用。触发后会拉起提供方应用的指定UIAbility(仅支持launchType
为singleton的UIAbility,即启动模式为单实例的UIAbility),但不会调度到前台。提供方应用需要具备后台运行权限
(ohos.permission.KEEP_BACKGROUND_RUNNING)
“bundleName”string“router” / “call” 类型时跳转的包名,可选。
“moduleName”string“router” / “call” 类型时跳转的模块名,可选。
“abilityName”string“router” / “call” 类型时跳转的UIAbility名,必填
“params”Object当前action携带的额外参数,内容使用JSON格式的键值对形式。 "call"类型时需填入参数’method’,且类型需要为string类型,用于触发UIAbility中对应的方法,必填

5.2 postCardAction()示例代码

Button('跳转')
  .width('40%')
  .height('20%')
  .onClick(() => {
    postCardAction(this, {
      'action': 'router',
      'bundleName': 'com.example.myapplication',
      'abilityName': 'EntryAbility',
      'params': {
        'message': 'testForRouter' // 自定义要发送的message
      }
    });
  })

5.3 router事件-拉起应用到前台

postCardAction接口的router类型,可以从卡片中拉起应用到前台,然后用户可以在界面上做一些操作。

场景:例如相机卡片,卡片上提供拍照、录像等按钮,就可以通过postCardAction的route类型来实现拉起

场景示例代码:通过postCardAction的router类型,拉起应用,并跳转到指定页面

  1. 在卡片中点击按钮,触发postCardAction,通过router拉起应用后,传入不同的参数
@Entry
@Component
struct WidgetCard {
  build() {
    Column() {
      Button('功能A')
        .margin('20%')
        .onClick(() => {
          console.info('Jump to EntryAbility funA');
           postCardAction(this, {
            'action': 'router',
            'abilityName': 'EntryAbility', // 只能跳转到当前应用下的UIAbility
            'params': {
              'targetPage': 'funA' // 在EntryAbility中处理这个信息
            }
          }); 
        })

      Button('功能B')
        .margin('20%')
        .onClick(() => {
          console.info('Jump to EntryAbility funB');
           postCardAction(this, {
            'action': 'router',
            'abilityName': 'EntryAbility', // 只能跳转到当前应用下的UIAbility
            'params': {
              'targetPage': 'funB' // 在EntryAbility中处理这个信息
            }
          }); 
        })
    }
    .width('100%')
    .height('100%')
  }
}
  1. 在UIAbility中接收router事件并获取参数,根据传递的params不同,选择拉起不同的页面
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';

 let selectPage = "";
let currentWindowStage = null; 

export default class CameraAbility extends UIAbility {
  // 如果UIAbility第一次启动,在收到Router事件后会触发onCreate生命周期回调
  onCreate(want, launchParam) {
     // 获取router事件中传递的targetPage参数
    console.info("onCreate want:" + JSON.stringify(want));
    if (want.parameters.params !== undefined) {
      let params = JSON.parse(want.parameters.params);
      console.info("onCreate router targetPage:" + params.targetPage);
      selectPage = params.targetPage;
    } 
  }
  // 如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期回调
  onNewWant(want, launchParam) {
    console.info("onNewWant want:" + JSON.stringify(want));
     if (want.parameters.params !== undefined) {
      let params = JSON.parse(want.parameters.params);
      console.info("onNewWant router targetPage:" + params.targetPage);
      selectPage = params.targetPage;
    }
    // 手动触发onWindowStageCreate实现页面跳转功能
    if (currentWindowStage != null) {
      this.onWindowStageCreate(currentWindowStage);
    } 
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
     let targetPage;
    // 根据传递的targetPage不同,选择拉起不同的页面
    switch (selectPage) {
      case 'funA':
        targetPage = 'pages/FunA';
        break;
      case 'funB':
        targetPage = 'pages/FunB';
        break;
      default:
        targetPage = 'pages/Index';
    }
    // 保存到全局变量,方便从卡片跳转时跳转到对应页面
     if (currentWindowStage === null) {
      currentWindowStage = windowStage;
    } 
    // 通过loadContent 拉起对应页面
    windowStage.loadContent(targetPage, (err, data) => { 
      if (err && err.code) {
        console.info('Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
    });
  }
};

划重点:

  1. 卡片中通过 postCardAction,并且 action设置为router实现拉起应用
  2. 通过params 传递参数,在UIAbilit的 onCreate(首次) 或 onNewWant(非首次)中接收参数
  3. 在onWindowStageCreate 中根据传递的参数 拉起指定的页面

5.4 call事件-拉起应用到后台

场景:如果希望在卡片中调起主应用的UIAbility在后台做一个任务时可以用postCardAction接口的call能力(注意:主应用是后台执行一个任务,用户看不到主应用的界面,是无感的

注意method参数为必选参数,且类型需要为string类型,用于触发UIAbility中对应的方法。

📌任务执行没有时间限制,可以做一个长时任务

  1. 前期准备工作:
    在module.json5中增加后台运行权限(ohos.permission.KEEP_BACKGROUND_RUNNING)才可以正常调用
{
  "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
  "usedScene": {
    "abilities": [
      "EntryAbility"
    ],
    "when": "inuse"
  }
}
  1. 拉起后台,传入method参数
@Entry
@Component
struct WidgetCard {
  build() {
    Column() {
      Button('功能A')
        .margin('20%')
        .onClick(() => {
          console.info('call EntryAbility funA');
           postCardAction(this, {
            'action': 'call',
            'abilityName': 'EntryAbility', // 只能跳转到当前应用下的UIAbility
            'params': {
              'method': 'funA', // 在EntryAbility中调用的方法名
              'num': 1 // 需要传递的其他参数
            } 
          });
        })
     
    }
    .width('100%')
    .height('100%')
  }
}
  1. 在UIAbility中通过on监听method传入的参数事件,其余数据可以通过readString的方式获取。需要注意的是,UIAbility需要onCreate生命周期中监听所需的方法。
import UIAbility from '@ohos.app.ability.UIAbility';

function  FunACall(context) {
  return function (data) {
    // 获取call事件中传递的所有参数
    let params = JSON.parse(data.readString());
    console.log('FunACall param:' + params);
    let second = params.num;
    console.log('mylog->',params,typeof params,second)
    let formData = {
      title: '博学谷鸿蒙课程-UIAbility',
      desc: '鸿蒙千帆起,我要当舵手-UIAbility'
    };

    let bindData = formBindingData.createFormBindingData(formData)

    setTimeout(() => {
      PreferencesUtil.getInstance().getFormIds(context).then(cards => {
        cards.forEach(cardid => {
          formProvider.updateForm(cardid, bindData)
        })
      })
    }, second)

    return null;
  }
}
 
export default class 
EntryAbility extends UIAbility { 
  
  // 如果UIAbility第一次启动,在收到call事件后会触发onCreate生命周期回调
  onCreate(want, launchParam) {
      try {
          // 监听call事件所需的方法
           this.callee.on('funA', FunACall(this.context)); 
      } catch (error) {
          console.log('register failed with error. Cause: ' + JSON.stringify(error));
      }
  }
   
  // 进程退出时,解除监听
  onDestroy() {
      try {
           this.callee.off('funA'); 
      } catch (error) {
          console.log('register failed with error. Cause: ' + JSON.stringify(error));
      }
  }
};

划重点:

  1. 卡片通过 postCardAction,并且 action设置为 call实现后台拉起应用
  2. 通过params传递参数:
    1. method:方法名
    2. 其他参数根据需求自行选择
  3. onCreate和onDestroy中分别进行监听和移除监听
  4. 需要添加后台运行权限(ohos.permission.KEEP_BACKGROUND_RUNNING)

5.5 message事件-拉起FormExtensionAbility

场景:如果希望在卡片中调起卡片的FormExtensionAbility在后台做一个任务时可以用postCardAction接口的message能力,然后由在FormExtensionAbility对应的生命周期函数onFormEvent中编写逻辑,常用于刷新卡片数据,接下来提供一个简单的例子:

📌FormExtensionAbility的生命周期为5秒,所以如果超过5秒的长时任务不能在用message事件,请改用call事件调起主应用的Ability去执行

  1. 在卡片页面通过注册Button的onClick点击事件回调,并在回调中调用postCardAction接口触发message事件拉起FormExtensionAbility。
let storage = new LocalStorage();

@Entry(storage)
@Component
struct WidgetCard {
  @LocalStorageProp('title') title: string = 'init';
  @LocalStorageProp('detail') detail: string = 'init';

  build() {
    Column() {
      Button('刷新')
        .onClick(() => {
           postCardAction(this, {
            'action': 'message',
            'params': {
              'msgTest': 'messageEvent'
            }
          }); 
        })
      Text(`${this.title}`)
      Text(`${this.detail}`)
    }
    .width('100%')
    .height('100%')
  }
}
  1. 在FormExtensionAbility的onFormEvent生命周期中编写对应的逻辑即可

📌由于FormExtensionAbility的生命周期是5秒,演示代码中setTimeout如果超过5秒则不会执行


import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formProvider from '@ohos.app.form.formProvider';


export default class EntryFormAbility extends FormExtensionAbility {
   onFormEvent(formId, message) {
    // Called when a specified message event defined by the form provider is triggered.
    console.info(`FormAbility onEvent, formId = ${formId}, message: ${JSON.stringify(message)}`);
    // 其他逻辑。。。
    let formData = {
      title: '博学谷鸿蒙课程-EntryFormAbility',
      desc: '鸿蒙千帆起,我要当舵手-EntryFormAbility'
  };

    let bindData = formBindingData.createFormBindingData(formData)

    setTimeout(()=>{
      console.log('mylog->','输出文本setTimeout')
      formProvider.updateForm(formId,bindData)
    },5000)   //由于FormExtensionAbility的生命周期是5秒,演示代码中setTimeout如果超过5秒则不会执行
  } 
  ...
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值