鸿蒙服务卡片
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"
]
}
]
}
临时卡片预览图如下:
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();
}