HarmonyOS-鸿蒙app开发 —基于java_服务卡片开发指导_基于JS UI实现的Java卡片开发指导

HarmonyOS-鸿蒙app开发 —基于java_服务卡片开发指导_基于JS UI实现的Java卡片开发指导

使用hml+css+json开发JS卡片页面,支持的语法详见JS API参考中“服务卡片开发”部分

使用DevEco Studio创建卡片工程

创建成功后,在config.json的module中会生成js模块,用于对应卡片的js相关资源,配置示例如下:“js”: [

{

“name”: “card”,

“pages”: [

“pages/index/index”

],

“window”: {

“designWidth”: 720,

“autoDesignWidth”: true

},

“type”: “form”

}

]

config.json文件“abilities”配置forms模块细节如下,各属性详情可见表1

“forms”: [

{

“name”: “Form_Js”,

“description”: “form_description”,

“type”: “JS”,

“jsComponentName”: “card”,

“formConfigAbility”: “ability://com.huawei.demo.SecondFormAbility”,

“colorMode”: “auto”,

“isDefault”: true,

“updateEnabled”: true,

“scheduledUpdateTime”: “10:30”,

“updateDuration”: 1,

“defaultDimension”: “2*2”,

“supportDimensions”: [

“2*2”,

“2*4”,

“4*4”

],

“metaData”: {

“customizeData”: [

{

“name”: “originWidgetName”,

“value”: “com.huawei.weather.testWidget”

}

]

}

}

]

说明

配置文件中,应注意如下配置:

  • “js”模块中的name字段要与“forms”模块中的jsComponentName字段的值一致,为js资源的实例名。
  • “forms”模块中的name为卡片名,即在onCreateForm中根据AbilitySlice.PARAM_FORM_NAME_KEY可取到的值。
  • 卡片的Ability中还需要配置”visible”: true和”formsEnabled”: true。
  • 定时刷新和定点刷新都配置的情况下,定时刷新优先。
  • defaultDimension是默认规格,必须设置。

表1 forms对象的内部结构说明

属性名称

子属性名称

含义

数据类型

是否可缺省

name

表示卡片的类名。字符串最大长度为127字节。

字符串

description

表示卡片的描述。取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。字符串最大长度为255字节。

字符串

可缺省,缺省为空。

isDefault

表示该卡片是否为默认卡片,每个Ability有且只有一个默认卡片。

  • true:默认卡片。
  • false:非默认卡片。

布尔值

type

表示卡片的类型。取值范围如下:

  • Java:Java卡片。
  • JS:JS卡片。

字符串

colorMode

表示卡片的主题样式,取值范围如下:

  • auto:自适应。
  • dark:深色主题。
  • light:浅色主题。

字符串

可缺省,缺省值为“auto”。

supportDimensions

表示卡片支持的外观规格,取值范围:

  • 1*2:表示1行2列的二宫格。
  • 2*2:表示2行2列的四宫格。
  • 2*4:表示2行4列的八宫格。
  • 4*4:表示4行4列的十六宫格。

字符串数组

defaultDimension

表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中。

字符串

landscapeLayouts

表示卡片外观规格对应的横向布局文件,与supportDimensions中的规格一一对应。

仅当卡片类型为Java卡片时,需要配置该标签。

字符串数组

portraitLayouts

表示卡片外观规格对应的竖向布局文件,与supportDimensions中的规格一一对应。

仅当卡片类型为Java卡片时,需要配置该标签。

字符串数组

updateEnabled

表示卡片是否支持周期性刷新,取值范围:

  • true:表示支持周期性刷新,可以在定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)两种方式任选其一,优先选择定时刷新。
  • false:表示不支持周期性刷新。

布尔类型

scheduledUpdateTime

表示卡片的定点刷新的时刻,采用24小时制,精确到分钟。

字符串

可缺省,缺省值为“0:0”。

updateDuration

表示卡片定时刷新的更新周期,单位为30分钟,取值为自然数。

  • 当取值为0时,表示该参数不生效。
  • 当取值为正整数N时,表示刷新周期为30*N分钟。

数值

可缺省,缺省值为“0”。

formConfigAbility

表示卡片的配置跳转链接,采用URI格式。

字符串

可缺省,缺省值为空。

jsComponentName

表示JS卡片的Component名称。字符串最大长度为127字节。

仅当卡片类型为JS卡片时,需要配置该标签。

字符串

metaData

表示卡片的自定义信息,包含customizeData数组标签。

对象

可缺省,缺省值为空。

customizeData

表示自定义的卡片信息。

对象数组

可缺省,缺省值为空。

name

表示数据项的键名称。字符串最大长度为255字节。

字符串

可缺省,缺省值为空。

value

表示数据项的值。字符串最大长度为255字节。

字符串

可缺省,缺省值为空。

创建一个FormAbility,覆写卡片相关回调函数。

  • onCreateForm(Intent intent)
  • onUpdateForm(long formId)
  • onDeleteForm(long formId)
  • onCastTempForm(long formId)
  • onEventNotify(Map<Long, Integer> formEvents)
  • onTriggerFormEvent(long formId, String message)
  • onAcquireFormState(Intent intent)

当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用onCreateForm(Intent intent)回调,intent中会带有卡片ID、卡片名称和卡片外观规格信息,可按需获取使用。

开发JS卡片时,FormAbility可以继承AceAbility或Ability,继承Ability时,需在onStart()方法中额外设置路由信息。示例分别如下:

FormAbility继承AceAbility的代码示例

public class FormAbility extends AceAbility {

……

public static long formId = –1;

@Override

public void onStart(Intent intent) {

super.onStart(intent);

}

@Override

protected ProviderFormInfo onCreateForm(Intent intent) {

 

long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, 0);

String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);

int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);

boolean tempFlag = intent.getBooleanParam(AbilitySlice.PARAM_FORM_TEMPORARY_KEY, false);

HiLog.info(LABEL_LOG, “onCreateForm: “ + formId + ” “ + formName + ” “ + specificationId);

FormBindingData formBindingData = new FormBindingData(“{\”temperature\”: \”60°\”}”);

ProviderFormInfo formInfo = new ProviderFormInfo();

formInfo.setJsBindingData(formBindingData);

return formInfo;

}

@Override

protected void onDeleteForm(long formId) {

// 删除卡片实例数据

super.onDeleteForm(formId);

……

}

 

@Override

protected void onUpdateForm(long formId) {

// 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要覆写该方法以支持数据更新

super.onUpdateForm(formId);

……

}

 

@Override

protected void onTriggerFormEvent(long formId, String message) {

// 若卡片支持触发事件,则需要覆写该方法并实现对事件的触发

super.onTriggerFormEvent(formId, message);

……

}

 

@Override

protected void onCastTempForm(long formId) {

//使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理

super.onCastTempForm (formId);

……

}

 

@Override

protected void onEventNotify(Map<Long, Integer> formEvents) {

//使用方发起可见或者不可见通知触发,提供方需要做相应的处理

super.onEventNotify(formEvents);

……

}

 

@Override

protected FormState onAcquireFormState(Intent intent) {

ElementName elementName = intent.getElement();

if (elementName == null) {

HiLog.info(LABEL_LOG, “onAcquireFormState bundleName and abilityName are not set in intent”);

return FormState.UNKNOWN;

}

 

String bundleName = elementName.getBundleName();

String abilityName = elementName.getAbilityName();

 

String moduleName = intent.getStringParam(AbilitySlice.PARAM_MODULE_NAME_KEY);

String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);

int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);

if (“form_name2”.equals(formName)) {

return FormState.DEFAULT;

}

return FormState.READY;

}

}

FormAbility继承Ability的代码示例

public class FormAbility extends Ability {

……

public static long formId = –1;

@Override

public void onStart(Intent intent) {

super.onStart(intent);

super.setMainRoute(FormAbilitySlice.class.getName()); //设置路由

}

@Override

protected ProviderFormInfo onCreateForm(Intent intent) {

 

long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, 0);

String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);

int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);

boolean tempFlag = intent.getBooleanParam(AbilitySlice.PARAM_FORM_TEMPORARY_KEY, false);

HiLog.info(LABEL_LOG, “onCreateForm: “ + formId + ” “ + formName + ” “ + specificationId);

FormBindingData formBindingData = new FormBindingData(“{\”temperature\”: \”60°\”}”);

ProviderFormInfo formInfo = new ProviderFormInfo();

formInfo.setJsBindingData(formBindingData);

return formInfo;

}

@Override

protected void onDeleteForm(long formId) {

// 删除卡片实例数据

super.onDeleteForm(formId);

……

}

 

@Override

protected void onUpdateForm(long formId) {

// 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要覆写该方法以支持数据更新

super.onUpdateForm(formId);

……

}

 

@Override

protected void onTriggerFormEvent(long formId, String message) {

// 若卡片支持触发事件,则需要覆写该方法并实现对事件的触发

super.onTriggerFormEvent(formId, message);

……

}

 

@Override

protected void onCastTempForm(long formId) {

// 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理

// 该回调属于预留接口,当前无场景,可以先不实现

super.onCastTempForm (formId);

……

}

 

@Override

protected void onEventNotify(Map<Long, Integer> formEvents) {

// 使用方发起可见或者不可见通知触发,提供方需要做相应的处理

super.onEventNotify(formEvents);

……

}

 

@Override

protected FormState onAcquireFormState(Intent intent) {

ElementName elementName = intent.getElement();

if (elementName == null) {

HiLog.info(LABEL_LOG, “onAcquireFormState bundleName and abilityName are not set in intent”);

return FormState.UNKNOWN;

}

 

String bundleName = elementName.getBundleName();

String abilityName = elementName.getAbilityName();

String moduleName = intent.getStringParam(AbilitySlice.PARAM_MODULE_NAME_KEY);

String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);

int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);

if (“form_name2”.equals(formName)) {

return FormState.DEFAULT;

}

return FormState.READY;

}

}

卡片信息持久化。

因大部分卡片提供方都不是常驻服务,只有在需要使用时才会被拉起获取卡片信息,且卡片管理服务支持对卡片进行多实例管理,卡片ID对应实例ID,因此若卡片提供方支持对卡片数据进行配置,则需要对卡片的业务数据按照卡片ID进行持久化管理,以便在后续获取、更新以及拉起时能获取到正确的卡片业务数据。且需要适配onDeleteForm(long formId)卡片删除通知接口,在其中实现卡片实例数据的删除。

常态卡片:卡片使用方会持久化的卡片;

临时卡片:卡片使用方不会持久化的卡片;

需要注意的是,卡片使用方在请求卡片时传递给提供方应用的Intent数据中存在临时标记字段,表示此次请求的卡片是否为临时卡片,由于临时卡片的数据具有非持久化的特殊性,某些场景比如卡片服务框架死亡重启,此时临时卡片数据在卡片管理服务中已经删除,且对应的卡片ID不会通知到提供方,所以卡片提供方需要自己负责清理长时间未删除的临时卡片数据。同时对应的卡片使用方可能会将之前请求的临时卡片转换为常态卡片。如果转换成功,卡片提供方也需要对对应的临时卡片ID进行处理,把卡片提供方记录的临时卡片数据转换为常态卡片数据,防止提供方在清理长时间未删除的临时卡片时,把已经转换为常态卡片的临时卡片信息删除,导致卡片信息丢失。@Override

protected ProviderFormInfo onCreateForm(Intent intent) {

long formId = intent.getIntParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, –1L);

String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);

int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);

boolean tempFlag = intent.getBooleanParam(AbilitySlice.PARAM_FORM_TEMPORARY_KEY, false);

HiLog.info(LABEL_LOG, “onCreateForm: “ + formId + ” “ + formName + ” “ + specificationId);

 

…….

// 由开发人员自行实现,将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用

storeFormInfo(formId, formName, specificationId, formData);

……

HiLog.info(LABEL_LOG, “onCreateForm finish…….”);

return formInfo;

}

@Override

protected void onDeleteForm(long formId) {

super.onDeleteForm(formId);

// 由开发人员自行实现,删除卡片实例数据

deleteFormInfo(formId);

……

}

@Override

protected void onCastTempForm(long formId) {

// 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理

super.onCastTempForm (formId);

……

}

卡片数据交互。

当卡片应用需要更新数据时(如触发了定时更新或定点更新),卡片应用获取最新数据,并调用updateForm接口更新卡片。示例如下:

@Override

protected void onUpdateForm(long formId) {

super.onUpdateForm(formId);

ZSONObject zsonObject = new ZSONObject();

zsonObject.put(“temperature”, “90°”);

FormBindingData formBindingData = new FormBindingData(zsonObject);

// 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变

if (!updateForm(formId, formBindingData)) {

// err process

}

}

开发JS卡片页面。

JS卡片页面与普通FA类似通过hml+css+json开发。详情请参考JS API参考中“服务卡片开发”部分

hml:

<div class=“container”>

<stack class=“stack_container”>

<image class = “img” src=“common/clouds.png”></image>

<div style=“flex-direction: column;”>

<text class=“txt_city” onclick=“messageEvent”>{{city}}</text>

<text class=“txt_temperature” onclick=“routerEvent”>{{temperature}}</text>

</div>

</stack>

</div>

css:

.container {

flex-direction: column;

justify-content: center;

align-items: center;

}

 

.stack_container {

width: 100%;

height: 100%;

background-image: url(“/common/weather-background-day.png”);

background-size: cover;

}

json:

{

data”: {

“temperature: “35°”,

“city: “hangzhou”

},

“actions: {

“routerEvent: {

“action: “router”,

“abilityName”: “com.example.myapplication.FormAbility”,

params”: {

message”: “weather”

}

},

“messageEvent”: {

“action”: “message”,

params: {

message”: “weather update”

}

}

}

}

开发JS卡片事件和action。

JS卡片支持为组件设置action,包括router事件和message事件,其中router事件用于应用跳转,message事件用于卡片开发人员自定义点击事件。关键步骤说明如下:

  1. 在hml中为组件设置onclick属性,其值对应到json文件的actions字段中。
  2. 若设置router事件,则
    • action属性值为”router”;
    • abilityName为卡片提供方应用的跳转目标Ability名;
    • params中的值按需填写,其值在使用时通过intent.getStringParam(“params”)获取即可;
  3. 若设置message事件,则action属性值为”message”,params为json格式的值。

示例如下:

hml:

<div>

<text class=“txt_city” onclick=“messageEvent”>{{city}}</text>

<text class=“txt_temperature” onclick=“routerEvent”>{{temperature}}</text>

</div>

json:

{

“actions”: {

“routerEvent”: {

“action”: “router”,

“abilityName”: “com.example.myapplication.FormAbility”,

 

“params”: {

“message”: “weather”

}

},

“messageEvent”: {

“action”: “message”,

“params”: {

“message”: “test date”,

}

}

}

}

当点击组件触发message事件时,卡片应用的onTriggerFormEvent方法被触发,params属性的值将作为参数被传入,解析使用即可。

说明

message事件由用户自定义,虽然也可以在收到message事件后实现跳转到其他Ability,但是由于ability在后台,跳转其他页面会有一定延迟,而且卡片使用方定义的动效是不生效的。宿主侧定义的动效仅在router事件的跳转中生效。因此不建议通过message事件进行页面跳转。

如果想要保证动效,使用routerEvent。

routerEvent配置跳转链接时,只能配置到卡片提供方自己的ability中。

通过内存图片方式使用image组件

在卡片上如果想要显示网络的图片资源、数据库中查询读取的图片资源等图片资源,可以通过image组件提供的内存图片能力。具体步骤说明如下:

  1. 获取图片数据:

    内存图片使用byte[]格式的图片数据,可以来自多个途径:比如网络的图片资源、数据库中查询读取的图片资源、本地图片打开后获得的图片资源等。

  2. 调用FormBindingData的addImageData接口传入数据:
    1. 首先创建一个ZSONObject,将{imageSrc,memory:// + picName}的键值对添加到ZSONObject中:
      1. ZSONObject zsonObject = new ZSONObject();
      2. zsonObject.put(“imageSrc”, “memory://logo.png”);

      其中,imageSrc是image组件src属性关联的变量(比如我们在js文件中,image组件的写法是<image src=”{{imageSrc}}”></image>),picName是内存图片的图片名,该命名可以自定义,但是要保证图片格式的后缀名正确。

    2. 使用该ZSONObject去创建一个FormBindingData。
    3. 调用FormBindingData的addImageData接口添加数据:addImageData(“logo.png”, bytes),其中,”logo.png”为picName,必须和第一步里面添加到ZSONObject中的键值对的picName一致,否则1中的内存图片路径(”memory://logo.png”)将读取不到这里添加进去的图片数据。

如果是在卡片的onCreateForm生命周期去更新共享内存图片数据,则只需创建ProviderFormInfo,然后将FormBindingData设置给ProviderFormInfo中,返回ProviderFormInfo即可。

如果是在卡片的其他生命周期去更新内存图片数据,直接调用updateForm去更新指定卡片即可,代码示例在onCreateForm中使用。

<!– xxx.hml –>

<image src=“{{imageSrc}}”></image>

// xxx.xxx.FormAbility

@Override

protected ProviderFormInfo onCreateForm(Intent intent) {

IntentParams params = intent.getParams();

if (params == null) {

return null;

}

formId = (int) params.getParam(AbilitySlice.PARAM_FORM_ID_KEY);

String formName = (String) params.getParam(AbilitySlice.PARAM_FORM_NAME_KEY);

int specificationId = (int) params.getParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY);

 

// ********************* memory image usage section *********************

// Step1: Create [ZSONObject] and set [picName] of [imageSrc] to it

// [imageSrc] is the variable we bind to the [src] attribute of image component in js

// such as <image src=”{{imageSrc}}”></image>

ZSONObject zsonObject = new ZSONObject();

zsonObject.put(“imageSrc”, “memory://logo.png”);

 

// Step2: Construct a [FormbindingData] using the [ZSONObject] we just created

FormBindingData formBindingData = new FormBindingData(zsonObject);

 

// Step3: Add image data to [FormBindingData] via interface: [addImageData]

formBindingData.addImageData(“logo.png”, bytes);

// ********************* memory image usage section *********************

 

ProviderFormInfo formInfo = new ProviderFormInfo();

formInfo.setJsBindingData(formBindingData);

return formInfo;

}

说明

内存图片刷新的触发条件是:内存图片名称的刷新。所以更新内存图片数据时,需要更新内存图片名称,以触发新的图片数据的读取与绘制。

也就是说需要确保,对于同一个imageSrc变量,本次更新的内存图片名称与上一次使用的内存图片名称不同。以下将对两种用法进行举例说明:

  1. 在两个不同的路径之间来回切换:
    • 第一次updateForm使用内存图片路径为”memory://logo.png”:zsonObject.put(“imageSrc”, “memory://logo.png”);
    • 第二次updateForm使用的内存图片路径可以是”memory://logo1.png”:zsonObject.put(“imageSrc”, “memory://logo1.png”);
    • 第三次updateForm使用的内存图片路径可以用回”memory://logo.png”:zsonObject.put(“imageSrc”, “memory://logo.png”);

    这样只要将imageSrc变量的赋值在两个不同的路径之前来回切换即可,该用法适用于每次更新的图源数据没有独特标识的场景,手动管理每次更新的imageSrc与上一次更新不同。

  2. 每次更新时创建一个独一无二的内存图片路径:

    假设我们要轮播一个图片集,该图片集的每张图片以创建时间命名,则图源的命名本身天然具备互斥属性。

    那么我们在更新内存图片路径时,即可将该图片本身的名称加入内存图片路径,比如说”memory://album20210914212111.png”,其中”20210914212111″代表2021-09-14日21点21分11秒创建的图。

🚀 如未找到文章请搜索栏搜素 | Ctrl+D收藏本站 | 联系邮箱:15810050733@qq.com