其他可选功能开发指引
使用DevEco Studio创建卡片工程。
卡片应用是一款特殊的元能力服务,其配置文件config.json中声明以下几项,系统能够识别该应用为一款卡片应用,并与系统进行绑定。
config.json文件"abilities"配置forms模块细节如下,各属性详情可见表1。
"forms": [ { "name": "Form_Java", "description": "form_description", "type": "Java", "colorMode": "auto", "isDefault": true, "updateEnabled": true, "scheduledUpateTime": "10:30", "updateDuration": 1, "defaultDimension": "2*2", "formVisibleNotify": true, "supportDimensions": [ "1*2", "2*2", "2*4", "4*4" ], "landscapeLayouts": [ "$layout:form_ability_layout_1_2", "$layout:form_ability_layout_2_2", "$layout:form_ability_layout_2_4", "$layout:form_ability_layout_4_4" ], "portraitLayouts": [ "$layout:form_ability_layout_1_2", "$layout:form_ability_layout_2_2", "$layout:form_ability_layout_2_4", "$layout:form_ability_layout_4_4" ], "formConfigAbility": "ability://SecondFormAbility", "metaData": { "customizeData": [ { "name": "originWidgetName", "value": "com.huawei.weather.testWidget" } ] } }]
说明
"forms"模块中的name为卡片名,即在onCreateForm中根据AbilitySlice.PARAM_FORM_NAME_KEY可取到的值。
除此之外,在卡片所在的"abilities"中还需要配置"visible": true和"formsEnabled": true。
属性名称 | 子属性名称 | 含义 | 数据类型 | 是否可缺省 |
---|---|---|---|---|
name | - | 表示卡片的类名。字符串最大长度为127字节。 | 字符串 | 否 |
description | - | 表示卡片的描述。取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。字符串最大长度为255字节。 | 字符串 | 可缺省,缺省为空。 |
isDefault | - | 表示该卡片是否为默认卡片,每个Ability有且只有一个默认卡片。true:默认卡片。false:非默认卡片。 | 布尔值 | 否 |
type | - | 表示卡片的类型。取值范围如下:Java:Java卡片。JS:JS卡片。 | 字符串 | 否 |
colorMode | - | 表示卡片的主题样式,取值范围如下:auto:自适应。dark:深色主题。light:浅色主题。 | 字符串 | 可缺省,缺省值为“auto”。 |
supportDimensions | - | 表示卡片支持的外观规格,取值范围:12:表示1行2列的二宫格。22:表示2行2列的四宫格。24:表示2行4列的八宫格。44:表示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
formEvents)
在onCreateForm(Intent intent)中,当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用onCreateForm(Intent intent)回调,intent中会带有卡片ID,卡片名称,临时卡片标记和卡片外观规格信息,分别通过AbilitySlice.PARAM_FORM_IDENTITY_KEY、AbilitySlice.PARAM_FORM_NAME_KEY、AbilitySlice.PARAM_FORM_TEMORARY_KEY和AbilitySlice.PARAM_FORM_DIMENSION_KEY按需获取。
提供方可以通过AbilitySlice.PARAM_FORM_CUSTOMIZE_KEY获取卡片使用方设置的自定义数据。
public class FormAbility extends Ability { ...... @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); // 获取自定义数据 IntentParams intentParams = intent.getParam(AbilitySlice.PARAM_FORM_CUSTOMIZE_KEY); HiLog.info(LABEL_LOG, "onCreateForm: " + formId + " " + formName + " " + specificationId); // 开发者需要根据卡片的名称以及外观规格获取对应的xml布局并构造卡片对象,此处ResourceTable.Layout_form_ability_layout_2_2仅为示例 ProviderFormInfo formInfo = new ProviderFormInfo(ResourceTable.Layout_form_ability_layout_2_2, this); // 获取卡片信息 String formData = getInitFormData(formName, specificationId); ComponentProvider componentProvider = new ComponentProvider(); componentProvider.setText(ResourceTable.Id_title, "formData-" + formData); formInfo.mergeActions(componentProvider); ...... HiLog.info(LABEL_LOG, "onCreateForm finish......."); return formInfo; } @Override protected void onDeleteForm(long formId) { super.onDeleteForm(formId); // 删除卡片实例数据,需要由开发者实现 deleteFormInfo(formId); ...... } @Override // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要覆写该方法以支持数据更新 protected void onUpdateForm(long formId) { super.onUpdateForm(formId); // 更新卡片信息,由开发者实现 ...... } @Override protected void onCastTempForm(long formId) { // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理,将数据持久化。 super.onCastTempForm (formId); ...... } @Override protected void onEventNotify(Map<Long, Integer> formEvents) { // 使用方发起可见或者不可见通知触发,提供方需要做相应的处理,比如卡片可见时刷新卡片,仅系统应用能收到该回调。 super.onEventNotify(formEvents); ...... }}
卡片信息持久化。
因大部分卡片提供方都不是常驻服务,只有在需要使用时才会被拉起获取卡片信息。且卡片框架支持对卡片进行多实例管理,卡片ID对应实例ID,因此若卡片提供方支持对卡片数据进行配置,则需要提供方对卡片的业务数据按照卡片ID进行持久化管理,以便在后续获取、更新以及拉起时能获取到正确的卡片业务数据。
同时,需要适配onDeleteForm(int formId)卡片删除通知接口,在其中实现卡片实例数据的删除。和JS卡片相同,需要注意卡片使用方在请求卡片时传递给提供方应用的Intent数据中存在临时标记字段,表示此次请求的卡片是否为临时卡片,由于临时卡片的数据不会持久化的特殊性,某些场景比如卡片服务框架死亡重启,此时临时卡片数据在卡片服务框架中已经删除,且对应的卡片ID不会通知到提供方,所以卡片提供方需要自己负责清理长时间未删除的临时卡片数据。同时对应的卡片使用方可能会将之前请求的临时卡片转换为常态卡片。如果转换成功,卡片提供方也需要对对应的临时卡片ID进行处理,把卡片提供方记录的临时卡片数据转换为常态卡片数据,防止提供方在清理长时间未删除的临时卡片时,把已经转换为常态卡片的临时卡片信息删除,导致卡片信息丢失。
@Overrideprotected ProviderFormInfo onCreateForm(Intent intent) { long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_ID_KEY, -1L); String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY); int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0); boolean tempFlag = params.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;}@Overrideprotected void onDeleteForm(long formId) { super.onDeleteForm(formId); // 由开发人员自行实现,删除卡片实例数据 deleteFormInfo(formId); ......}@Overrideprotected void onCastTempForm(long formId) { // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理 super.onCastTempForm (formId); ......}
卡片数据更新。
当需要卡片提供方更新数据时(如触发了定时更新,定点更新,或者卡片使用方主动请求更新),卡片提供方获取最新数据,并调用updateForm接口更新卡片。示例如下:
@Overrideprotected void onUpdateForm(long formId) { super.onUpdateForm(formId); ComponentProvider componentProvider = new ComponentProvider(ResourceTable.Layout_form_ability_layout_2_2, this); // 获取卡片实例需要更新的卡片数据,需要由开发者实现 String formData = getUpdateFormData(formId); componentProvider.setText(ResourceTable.Id_title, "update formData-" + formData); updateForm(formId, componentProvider); ......}
卡片使用方点击拉起卡片页面,会在onStart(Intent intent)中携带formId(通过AbilitySlice.PARAM_FORM_IDENTITY_KEY获取),若需要在AbilitySlice中更新,也可以使用updateForm接口进行更新,示例如下:
public class FormAbilitySlice extends AbilitySlice { ...... @Override public void onStart(Intent intent) { super.onStart(intent); ...... Button button = new Button(this); button.setText("Update form data"); button.setClickedListener(component -> { ...... if (intent.hasParameter(AbilitySlice.PARAM_FORM_IDENTITY_KEY)) { int formId = intent.getIntParam(AbilitySlice.PARAM_FORM_ID_KEY, -1); ComponentProvider componentProvider = new ComponentProvider(ResourceTable.Layout_form_ability_layout_2_2, context); String formData = getUpdateFormData(formId); componentProvider.setText(ResourceTable.Id_modifylayout, "update formData-" + formData); getAbility().updateForm(formId, componentProvider); } }); ...... }}
Java卡片控制事件。
Java卡片当前通过IntentAgent能力支持对卡片控制设置事件,例如可以使用START_ABILITY、START_SERVICE这两类能力,在点击整张卡片时,跳转到提供卡片的ability。
示例如下:
@Overrideprotected ProviderFormInfo onCreateForm(Intent intent) { ...... ProviderFormInfo formInfo = new ProviderFormInfo(ResourceTable.Layout_form_ability_layout_2_2, this); ComponentProvider componentProvider = new ComponentProvider(); // 针对title控件设置事件 componentProvider.setIntentAgent(ResourceTable.Id_title, startAbilityIntentAgent()); formInfo.mergeActions(componentProvider); ...... return formInfo;} // 设置触发的事件为系统预置的HarmonyOS betaApp应用private IntentAgent startAbilityIntentAgent() { Intent intent = new Intent(); Operation operation = new Intent.OperationBuilder() .withDeviceId("") .withBundleName("com.huawei.ohos.betaapp.link") .withAbilityName("com.huawei.ohos.betaapp.link.MainAbility") .build(); intent.setOperation(operation); List<Intent> intentList = new ArrayList<>(); intentList.add(intent); List<Flags> flags = new ArrayList<>(); flags.add(Flags.UPDATE_PRESENT_FLAG); IntentAgentInfo paramsInfo = new IntentAgentInfo(200, IntentAgentConstant.OperationType.START_ABILITY, flags, intentList, null); IntentAgent intentAgent = IntentAgentHelper.getIntentAgent(this, paramsInfo); return intentAgent;}
开发Java卡片布局。
在使用DevEco Studio创建模块时会生成对应的Java UI xml布局文件,具体规则请参考《XML创建布局》,需要注意设置ohos:remote="true"。
以下是天气卡片xml布局示例,供参考:
<?xml version="1.0" encoding="utf-8"?><DependentLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:width="match_parent" ohos:height="match_parent" ohos:id="$+id:background" ohos:orientation="vertical" ohos:background_element="$media:weather" ohos:remote="true"> <Text ohos:id="$+id:title" ohos:text="天气1" ohos:text_size="39px" ohos:text_color="#b0c4de" ohos:top_margin="42px" ohos:left_margin="20px" ohos:width="match_content" ohos:height="match_content"/> <Text ohos:id="$+id:temperature" ohos:text="35°" ohos:text_size="100px" ohos:text_color="#b0c4de" ohos:top_margin="25px" ohos:left_margin="20px" ohos:below="$id:title" ohos:width="match_content" ohos:height="match_content"/> <Text ohos:id="$+id:location" ohos:text="上海" ohos:text_size="39px" ohos:text_color="#b0c4de" ohos:top_margin="24px" ohos:left_margin="20px" ohos:below="$id:temperature" ohos:width="match_content" ohos:height="match_content"/> <Text ohos:id="$+id:textView4" ohos:text="9月4号 星期五" ohos:text_size="39px" ohos:text_color="#b0c4de" ohos:top_margin="10px" ohos:left_margin="20px" ohos:below="$id:location" ohos:width="match_content" ohos:height="match_content"/> <Text ohos:id="$+id:textView5" ohos:text="多云" ohos:text_size="39px" ohos:text_color="#b0c4de" ohos:top_margin="10px" ohos:left_margin="150px" ohos:below="$id:location" ohos:end_of="$id:textView4" ohos:align_parent_end="true" ohos:width="match_content" ohos:height="match_content"/> <Image ohos:id="$+id:imageView" ohos:width="160px" ohos:height="150px" ohos:top_margin="20px" ohos:left_margin="150px" ohos:below="$id:title" ohos:end_of="$id:temperature" ohos:image_src="$media:clouds"/></DependentLayout>