[文章]基于HarmonyOS Java UI 实现简易的时钟卡片应用

阅读量0
0
2
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的目录。
    • ClockCardSlice:应用主页面。
  • utils:存放工具类的目录。
    • ComponentProviderUtils:提供获取ComponentProvider对象的方法,用于卡片组件的更新。
    • DatabaseUtils:提供对数据库相关操作的方法。
    • DateUtils:提供日期相关操作的方法。
    • LogUtils:日志工具类。
  • MainAbility:主程序入口,由DevEco Studio生成,开发者需要重写创建、删除卡片等方法。
  • MyApplication:DevEco Studio生成,无需变更。
  • TimerAbility:时钟更新Service Ability。

4. 配置文件      
卡片应用是一款特殊的元能力服务,其配置文件config.json中声明以下几项,系统能够识别该应用为一款卡片应用,并与系统进行绑定。以本工程为例,config.json文件中"abilities"配置forms模块的细节如下:
  1. "forms": [
  2.   {
  3.         "landscapeLayouts": [
  4.           "$layout:form_image_with_info_date_card_2_2",
  5.           "$layout:form_image_with_info_date_card_2_4"
  6.         ],
  7.         "isDefault": true,
  8.         "scheduledUpdateTime": "10:30",
  9.         "defaultDimension": "2*2",
  10.         "name": "DateCard",
  11.         "description": "This is a service widget",
  12.         "colorMode": "auto",
  13.         "type": "Java",
  14.         "supportDimensions": [
  15.           "2*2",
  16.           "2*4"
  17.         ],
  18.         "portraitLayouts": [
  19.           "$layout:form_image_with_info_date_card_2_2",
  20.           "$layout:form_image_with_info_date_card_2_4"
  21.         ],
  22.         "updateEnabled": true,
  23.         "updateDuration": 1,
  24.         "formVisibleNotify": true
  25.   }
  26. ]
复制代码

说明
有关forms对象的内部结构说明可以参考Java卡片开发指导

5. 卡片布局      本篇Codelab为卡片应用设计了2*2和2*4两种布局风格,效果如下图:         
         
下面以2*2布局为例,详细进行介绍。整个2*2卡片展示的内容从上到下分别为日期、时间、星期,整体由DependentLayout布局内嵌套四个DirectionalLayout构成,每个DirectionalLayout 内均使用Text组件进行展示,部分代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <DependentLayout
  3.     xmlns:ohos="http://schemas.huawei.com/res/ohos"
  4.     ohos:height="match_parent"
  5.     ohos:width="match_parent"
  6.     ohos:background_element="#6A9F99"
  7.     ohos:remote="true">
  8.     <DirectionalLayout
  9.         ohos:height="match_content"
  10.         ohos:width="match_parent"
  11.         ohos:orientation="vertical"
  12.         >
  13.         <Text...>
  14.      </DirectionalLayout>
  15.      <DirectionalLayout
  16.          ohos:id="$+id:title"
  17.          ohos:height="match_content"
  18.          ohos:width="match_parent"
  19.          ohos:alignment="horizontal_center"
  20.          ohos:orientation="horizontal"
  21.          ohos:top_margin="35fp"
  22.          >
  23.         <Text...>
  24.         <Text...>
  25.         <Text...>
  26.       </DirectionalLayout>

  27.      <DirectionalLayout
  28.          ohos:id="$+id:time"
  29.          ohos:height="match_content"
  30.          ohos:width="match_parent"
  31.          ohos:alignment="horizontal_center"
  32.          ohos:below="$id:title"
  33.          ohos:orientation="horizontal"
  34.          ohos:top_margin="0.5fp"
  35.          >
  36.          <Text...>
  37.          <Text...>
  38.          <Text...>
  39.          <Text...>
  40.          <Text...>
  41.      </DirectionalLayout>

  42.      <DirectionalLayout
  43.          ohos:height="match_content"
  44.          ohos:width="match_parent"
  45.          ohos:alignment="center"
  46.          ohos:below="$id:time"
  47.          ohos:margin="20fp"
  48.          ohos:orientation="horizontal"
  49.       >
  50.          <Text...>
  51.          <Text...>
  52.          <Text...>
  53.          <Text...>
  54.          <Text...>
  55.          <Text...>
  56.          <Text...>
  57.      </DirectionalLayout>
  58. </DependentLayout>
复制代码
6. 创建卡片创建卡片数据库本篇codelab使用对象关系映射数据库来对卡片ID,卡片名字等信息进行存储,我们创建了一个数据库(FormDatabase)和一张表(Form)。
首先定义了数据库类FormDatabase.java,数据库包含"Form"表,版本号为 "1",示例代码如下:

  1. @Database(
  2.     entities = {Form.class},
  3.     version = 1)
  4. public abstract class FormDatabase extends OrmDatabase {}
复制代码
定义实体类Form.java,对应数据库内的表名为"form",包含了卡片id"formId"(主键),卡片名称"formName"和卡片规格"dimension"三个字段,示例代码如下:
  1. @Entity(tableName = "form")
  2. public class Form extends OrmObject {
  3.     @PrimaryKey()
  4.     private Long formId;
  5.     private String formName;
  6.     private Integer dimension;
  7.    
  8.     public Form(Long formId, String formName, Integer dimension) {
  9.         this.formId = formId;
  10.         this.formName = formName;
  11.         this.dimension = dimension;
  12.     }
  13.     // 开发者自行添加字段的getter和setter方法或者参考完整代码
  14. }
复制代码
卡片应用初始化卡片程序安装启动后,会进入MainAbility,在onStart时,会首先启动卡片定时器服务TimerAbility,以便刷新时钟卡片,部分示例代码如下:
  1. [url=home.php?mod=space&uid=2735960]@Override[/url]
  2. public void onStart(Intent intent) {
  3.     super.onStart(intent);
  4.     connect = helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class);
  5.     // 启动TimerAbility
  6.     Intent intentService = new Intent();
  7.     Operation operation =
  8.             new Intent.OperationBuilder()
  9.                     .withDeviceId("")
  10.                     .withBundleName("com.huawei.cookbooks")
  11.                     .withAbilityName("com.huawei.cookbooks.TimerAbility")
  12.                     .build();
  13.     intentService.setOperation(operation);
  14.     startAbility(intentService);
  15.     super.setMainRoute(ClockCardSlice.class.getName());
  16. }
复制代码
当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用onCreateForm回调函数,完成卡片信息的初始化,在MainAbility中有如下示例代码:
  1. @Override
  2. protected ProviderFormInfo onCreateForm(Intent intent) {
  3.     if (intent == null) {
  4.         return new ProviderFormInfo();
  5.     }
  6.     // 获取卡片id
  7.     formId = INVALID_FORM_ID;
  8.     if (intent.hasParameter(AbilitySlice.PARAM_FORM_IDENTITY_KEY)) {
  9.         formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
  10.     } else {
  11.         return new ProviderFormInfo();
  12.     }
  13.     // 获取卡片名称
  14.     String formName = EMPTY_STRING;
  15.     if (intent.hasParameter(AbilitySlice.PARAM_FORM_NAME_KEY)) {
  16.         formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
  17.     }
  18.     // 获取卡片规格
  19.     int dimension = DEFAULT_DIMENSION_2X2;
  20.     if (intent.hasParameter(AbilitySlice.PARAM_FORM_DIMENSION_KEY)) {
  21.         dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
  22.     }
  23.     int layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_2;
  24.     if (dimension == DEFAULT_DIMENSION_2X4) {
  25.         layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_4;
  26.     }
  27.     formInfo = new ProviderFormInfo(layoutId, this);
  28.     // 存储卡片信息
  29.     Form form = new Form(formId, formName, dimension);
  30.     ComponentProvider componentProvider = ComponentProviderUtils.getComponentProvider(form, this);
  31.     formInfo.mergeActions(componentProvider);
  32.     if (connect == null) {
  33.         connect =
  34.                 helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class);
  35.     }
  36.     try {
  37.         DatabaseUtils.insertForm(form, connect);
  38.     } catch (Exception e) {
  39.         DatabaseUtils.deleteFormData(form.getFormId(), connect);
  40.     }
  41.     return formInfo;
  42. }
复制代码
说明
  • 卡片使用方
    显示卡片内容的宿主应用,控制卡片在宿主中展示的位置
  • 卡片提供方
    提供卡片显示内容的HarmonyOS应用或原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。

当卡片被删除时,需要重写onDeleteForm方法,根据卡片id删除卡片实例数据:
  1. @Override
  2. protected void onDeleteForm(long formId) {
  3.     super.onDeleteForm(formId);
  4.     // 删除数据库中的卡片信息
  5.     DatabaseUtils.deleteFormData(formId, connect);
  6. }
复制代码

7. 更新卡片卡片数据服务为了方便处理时钟卡片刷新的定时任务,我们创建了一个Service Ability,定时去更新卡片信息,在TimerAbility.java中有如下部分参考代码:

  1. @Override
  2. public void onStart(Intent intent) {
  3.     connect = helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class);
  4.     startTimer();
  5.     super.onStart(intent);
  6. }
  7. // 卡片更新定时器,每秒更新一次
  8. private void startTimer() {
  9.     Timer timer = new Timer();
  10.     timer.schedule(
  11.             new TimerTask() {

  12.                 @Override

  13.                 public void run() {

  14.                     updateForms();
  15.                     notice();

  16.                 }
  17.             },

  18.             0,SEND_PERIOD);
  19. }

  20. private void updateForms() {
  21.     // 从数据库中获取卡片信息
  22.     OrmPredicates ormPredicates = new OrmPredicates(Form.class);
  23.     List<Form> formList = connect.query(ormPredicates);
  24.     // 更新时分秒
  25.     if (formList.size() > 0) {
  26.         for (Form form : formList) {
  27.             // 遍历卡片列表更新卡片
  28.             ComponentProvider componentProvider = ComponentProviderUtils.getComponentProvider(form, this);
  29.             try {

  30.                 Long updateFormId = form.getFormId();

  31.                 updateForm(updateFormId, componentProvider);
  32.             } catch (FormException e) {
  33.                 // 删除不存在的卡片
  34.                 DatabaseUtils.deleteFormData(form.getFormId(), connect);
  35.                 HiLog.error(LABEL_LOG, "onUpdateForm updateForm error");
  36.             }
  37.         }
  38.     }
  39. }
复制代码
前台service为了保持service不被系统销毁,需要使用前台service配合手机管家中的相关配置来达到目的。示例代码如下:

  1. private void notice() {
  2.         // 创建通知
  3.         NotificationRequest request = new NotificationRequest(NOTICE_ID);
  4.         request.setAlertOneTime(true);
  5.         NotificationRequest.NotificationNormalContent content = new NotificationRequest.NotificationNormalContent();
  6.         content.setText(DateUtils.getCurrentDate("yyyy-MM-dd HH:mm:ss"));
  7.         NotificationRequest.NotificationContent notificationContent = new NotificationRequest.NotificationContent(content);
  8.         request.setContent(notificationContent);
  9.         // 绑定通知
  10.         keepBackgroundRunning(NOTICE_ID, request);
  11. }
复制代码
说明
关于前台service的详细介绍,可参考前台service开发指导。
手机管家配置步骤如下:
手机管家> 应用启动管理> 时钟服务卡片> 点击右侧滑块> 选择开启"允许后台活动"开启后台运行权限


卡片组件更新

有关卡片组件的更新,我们封装了ComponentProviderUtils这个类,在卡片更新时候,通过调用updateForm方法,传入参数formId和componentProvider,以达到日期、时间和星期实时更新的效果,部分代码和效果如下:

  1. public static ComponentProvider getComponentProvider(Form form, Context context) {
  2.     int layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_2;
  3.     if (form.getDimension() == DIM_VERSION) {
  4.         layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_4;
  5.     }
  6.     ComponentProvider componentProvider = new ComponentProvider(layoutId, context);
  7.     setComponentProviderValue(componentProvider);
  8.     return componentProvider;
  9. }

  10. // 为时钟各个组件赋值
  11. private static void setComponentProviderValue(ComponentProvider componentProvider) {
  12.     Calendar now = Calendar.getInstance();
  13.     int hour = now.get(Calendar.HOUR_OF_DAY);
  14.     int min = now.get(Calendar.MINUTE);
  15.     int second = now.get(Calendar.SECOND);
  16.     String hourString = int2String(hour);
  17.     String minString = int2String(min);
  18.     String secondString = int2String(second);
  19.     componentProvider.setText(ResourceTable.Id_date, DateUtils.getCurrentDate("yyyy-MM-dd"));
  20.     componentProvider.setText(ResourceTable.Id_hour, hourString);
  21.     componentProvider.setText(ResourceTable.Id_min, minString);
  22.     componentProvider.setText(ResourceTable.Id_sec, secondString);

  23.     // 获取当前星期
  24.     int weekDayId = getWeekDayId();
  25.     componentProvider.setTextColor(weekDayId, nowWeekColor);

  26.     // 将前一天的星期改回原色
  27.     int lastWeekId = getLastWeekDayId();
  28.     componentProvider.setTextColor(lastWeekId, primaryWeekColor);
  29. }
复制代码

8. 恭喜你      
恭喜你已经完成时钟卡片应用的开发,并且学到了:
  • Java卡片开发部分接口的使用。
  • 卡片开发如何去配置config.json文件。
   
9. 参考      

回帖

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
链接复制成功,分享给好友