1. 介绍 服务卡片是FA的一种界面展示形式,将FA重要信息或操作前置到卡片,以此达到服务直达、减少层级体验的目的。本篇Codelab主要介绍如何在HarmonyOS上开发一个时钟类FA卡片应用,帮助开发者快速上手卡片类应用开发。该卡片包含2*2、2*4两种布局样式,卡片应用在桌面上的显示效果如下:
2. 搭建HarmonyOS环境我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
- 安装DevEco Studio,详情请参考下载和安装软件。
- 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
- 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
- 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
- 开发者可以参考以下链接,完成设备调试的相关配置:
说明
本篇文章使用的DevEco Studio版本为DevEco Studio 2.1 Beta4,使用的SDK版本为API Version 5。
3. 代码结构 时钟FA卡片应用主要介绍了如何创建、更新和删除卡片,对象关系映射型数据库的使用以及如何启动计时器服务,整个工程的代码结构如下:
- database:存放对象关系映射数据库相关对象的目录。
- Form:卡片表对象,用于存储卡片id、卡片名称以及卡片规格。
- FormDatabase:卡片数据库对象,用于创建卡片数据库。
- slice:存放应用FA的目录。
- utils:存放工具类的目录。
- ComponentProviderUtils:提供获取ComponentProvider对象的方法,用于卡片组件的更新。
- DatabaseUtils:提供对数据库相关操作的方法。
- DateUtils:提供日期相关操作的方法。
- LogUtils:日志工具类。
- MainAbility:主程序入口,由DevEco Studio生成,开发者需要重写创建、删除卡片等方法。
- MyApplication:DevEco Studio生成,无需变更。
- TimerAbility:时钟更新Service Ability。
4. 配置文件 卡片应用是一款特殊的元能力服务,其配置文件config.json中声明以下几项,系统能够识别该应用为一款卡片应用,并与系统进行绑定。以本工程为例,config.json文件中"abilities"配置forms模块的细节如下:
- "forms": [
- {
- "landscapeLayouts": [
- "$layout:form_image_with_info_date_card_2_2",
- "$layout:form_image_with_info_date_card_2_4"
- ],
- "isDefault": true,
- "scheduledUpdateTime": "10:30",
- "defaultDimension": "2*2",
- "name": "DateCard",
- "description": "This is a service widget",
- "colorMode": "auto",
- "type": "Java",
- "supportDimensions": [
- "2*2",
- "2*4"
- ],
- "portraitLayouts": [
- "$layout:form_image_with_info_date_card_2_2",
- "$layout:form_image_with_info_date_card_2_4"
- ],
- "updateEnabled": true,
- "updateDuration": 1,
- "formVisibleNotify": true
- }
- ]
复制代码
说明
5. 卡片布局 本篇Codelab为卡片应用设计了2*2和2*4两种布局风格,效果如下图:
下面以2*2布局为例,详细进行介绍。整个2*2卡片展示的内容从上到下分别为日期、时间、星期,整体由DependentLayout布局内嵌套四个DirectionalLayout构成,每个DirectionalLayout 内均使用Text组件进行展示,部分代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <DependentLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:background_element="#6A9F99"
- ohos:remote="true">
- <DirectionalLayout
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:orientation="vertical"
- >
- <Text...>
- </DirectionalLayout>
- <DirectionalLayout
- ohos:id="$+id:title"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:alignment="horizontal_center"
- ohos:orientation="horizontal"
- ohos:top_margin="35fp"
- >
- <Text...>
- <Text...>
- <Text...>
- </DirectionalLayout>
-
- <DirectionalLayout
- ohos:id="$+id:time"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:alignment="horizontal_center"
- ohos:below="$id:title"
- ohos:orientation="horizontal"
- ohos:top_margin="0.5fp"
- >
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- </DirectionalLayout>
-
- <DirectionalLayout
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:alignment="center"
- ohos:below="$id:time"
- ohos:margin="20fp"
- ohos:orientation="horizontal"
- >
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- </DirectionalLayout>
- </DependentLayout>
复制代码 6. 创建卡片创建卡片数据库本篇codelab使用对象关系映射数据库来对卡片ID,卡片名字等信息进行存储,我们创建了一个数据库(FormDatabase)和一张表(Form)。
首先定义了数据库类FormDatabase.java,数据库包含"Form"表,版本号为 "1",示例代码如下:
- @Database(
- entities = {Form.class},
- version = 1)
- public abstract class FormDatabase extends OrmDatabase {}
复制代码 定义实体类Form.java,对应数据库内的表名为"form",包含了卡片id"formId"(主键),卡片名称"formName"和卡片规格"dimension"三个字段,示例代码如下:
- @Entity(tableName = "form")
- public class Form extends OrmObject {
- @PrimaryKey()
- private Long formId;
- private String formName;
- private Integer dimension;
-
- public Form(Long formId, String formName, Integer dimension) {
- this.formId = formId;
- this.formName = formName;
- this.dimension = dimension;
- }
- // 开发者自行添加字段的getter和setter方法或者参考完整代码
- }
复制代码 卡片应用初始化卡片程序安装启动后,会进入MainAbility,在onStart时,会首先启动卡片定时器服务TimerAbility,以便刷新时钟卡片,部分示例代码如下:
- [url=home.php?mod=space&uid=2735960]@Override[/url]
- public void onStart(Intent intent) {
- super.onStart(intent);
- connect = helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class);
- // 启动TimerAbility
- Intent intentService = new Intent();
- Operation operation =
- new Intent.OperationBuilder()
- .withDeviceId("")
- .withBundleName("com.huawei.cookbooks")
- .withAbilityName("com.huawei.cookbooks.TimerAbility")
- .build();
- intentService.setOperation(operation);
- startAbility(intentService);
- super.setMainRoute(ClockCardSlice.class.getName());
- }
复制代码 当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用onCreateForm回调函数,完成卡片信息的初始化,在MainAbility中有如下示例代码:
- @Override
- protected ProviderFormInfo onCreateForm(Intent intent) {
- if (intent == null) {
- return new ProviderFormInfo();
- }
- // 获取卡片id
- formId = INVALID_FORM_ID;
- if (intent.hasParameter(AbilitySlice.PARAM_FORM_IDENTITY_KEY)) {
- formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
- } else {
- return new ProviderFormInfo();
- }
- // 获取卡片名称
- String formName = EMPTY_STRING;
- if (intent.hasParameter(AbilitySlice.PARAM_FORM_NAME_KEY)) {
- formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
- }
- // 获取卡片规格
- int dimension = DEFAULT_DIMENSION_2X2;
- if (intent.hasParameter(AbilitySlice.PARAM_FORM_DIMENSION_KEY)) {
- dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
- }
- int layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_2;
- if (dimension == DEFAULT_DIMENSION_2X4) {
- layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_4;
- }
- formInfo = new ProviderFormInfo(layoutId, this);
- // 存储卡片信息
- Form form = new Form(formId, formName, dimension);
- ComponentProvider componentProvider = ComponentProviderUtils.getComponentProvider(form, this);
- formInfo.mergeActions(componentProvider);
- if (connect == null) {
- connect =
- helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class);
- }
- try {
- DatabaseUtils.insertForm(form, connect);
- } catch (Exception e) {
- DatabaseUtils.deleteFormData(form.getFormId(), connect);
- }
- return formInfo;
- }
复制代码说明
- 卡片使用方
显示卡片内容的宿主应用,控制卡片在宿主中展示的位置 - 卡片提供方
提供卡片显示内容的HarmonyOS应用或原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。
当卡片被删除时,需要重写onDeleteForm方法,根据卡片id删除卡片实例数据:
- @Override
- protected void onDeleteForm(long formId) {
- super.onDeleteForm(formId);
- // 删除数据库中的卡片信息
- DatabaseUtils.deleteFormData(formId, connect);
- }
复制代码
7. 更新卡片卡片数据服务为了方便处理时钟卡片刷新的定时任务,我们创建了一个Service Ability,定时去更新卡片信息,在TimerAbility.java中有如下部分参考代码:
- @Override
- public void onStart(Intent intent) {
- connect = helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class);
- startTimer();
- super.onStart(intent);
- }
- // 卡片更新定时器,每秒更新一次
- private void startTimer() {
- Timer timer = new Timer();
- timer.schedule(
- new TimerTask() {
- @Override
- public void run() {
- updateForms();
- notice();
- }
- },
- 0,SEND_PERIOD);
- }
- private void updateForms() {
- // 从数据库中获取卡片信息
- OrmPredicates ormPredicates = new OrmPredicates(Form.class);
- List<Form> formList = connect.query(ormPredicates);
- // 更新时分秒
- if (formList.size() > 0) {
- for (Form form : formList) {
- // 遍历卡片列表更新卡片
- ComponentProvider componentProvider = ComponentProviderUtils.getComponentProvider(form, this);
- try {
- Long updateFormId = form.getFormId();
- updateForm(updateFormId, componentProvider);
- } catch (FormException e) {
- // 删除不存在的卡片
- DatabaseUtils.deleteFormData(form.getFormId(), connect);
- HiLog.error(LABEL_LOG, "onUpdateForm updateForm error");
- }
- }
- }
- }
复制代码 前台service为了保持service不被系统销毁,需要使用前台service配合手机管家中的相关配置来达到目的。示例代码如下:
- private void notice() {
- // 创建通知
- NotificationRequest request = new NotificationRequest(NOTICE_ID);
- request.setAlertOneTime(true);
- NotificationRequest.NotificationNormalContent content = new NotificationRequest.NotificationNormalContent();
- content.setText(DateUtils.getCurrentDate("yyyy-MM-dd HH:mm:ss"));
- NotificationRequest.NotificationContent notificationContent = new NotificationRequest.NotificationContent(content);
- request.setContent(notificationContent);
- // 绑定通知
- keepBackgroundRunning(NOTICE_ID, request);
- }
复制代码 说明
关于前台service的详细介绍,可参考前台service开发指导。
手机管家配置步骤如下:
手机管家> 应用启动管理> 时钟服务卡片> 点击右侧滑块> 选择开启"允许后台活动"开启后台运行权限
卡片组件更新
有关卡片组件的更新,我们封装了ComponentProviderUtils这个类,在卡片更新时候,通过调用updateForm方法,传入参数formId和componentProvider,以达到日期、时间和星期实时更新的效果,部分代码和效果如下:
- public static ComponentProvider getComponentProvider(Form form, Context context) {
- int layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_2;
- if (form.getDimension() == DIM_VERSION) {
- layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_4;
- }
- ComponentProvider componentProvider = new ComponentProvider(layoutId, context);
- setComponentProviderValue(componentProvider);
- return componentProvider;
- }
-
- // 为时钟各个组件赋值
- private static void setComponentProviderValue(ComponentProvider componentProvider) {
- Calendar now = Calendar.getInstance();
- int hour = now.get(Calendar.HOUR_OF_DAY);
- int min = now.get(Calendar.MINUTE);
- int second = now.get(Calendar.SECOND);
- String hourString = int2String(hour);
- String minString = int2String(min);
- String secondString = int2String(second);
- componentProvider.setText(ResourceTable.Id_date, DateUtils.getCurrentDate("yyyy-MM-dd"));
- componentProvider.setText(ResourceTable.Id_hour, hourString);
- componentProvider.setText(ResourceTable.Id_min, minString);
- componentProvider.setText(ResourceTable.Id_sec, secondString);
-
- // 获取当前星期
- int weekDayId = getWeekDayId();
- componentProvider.setTextColor(weekDayId, nowWeekColor);
-
- // 将前一天的星期改回原色
- int lastWeekId = getLastWeekDayId();
- componentProvider.setTextColor(lastWeekId, primaryWeekColor);
- }
复制代码
8. 恭喜你 恭喜你已经完成时钟卡片应用的开发,并且学到了:
- Java卡片开发部分接口的使用。
- 卡片开发如何去配置config.json文件。
9. 参考