OpenHarmony开发实战:计步器卡片(JS)

本篇Codelab基于Stage模型实现带有卡片的计步应用,用于介绍卡片的开发及生命周期实现。需要完成以下功能:

  1. 消息通知栏,通知用户今天所行走步数。
  2. 元服务卡片,在桌面上添加2x2或2x4规格元服务卡片,能看到步数变化,也能看到当天所行走的进度。
  3. 关系型数据库,用于查询,添加用户行走的数据。

相关概念

  • 消息通知:提供通知管理的能力,包括发布、取消发布通知,创建、获取、移除通知通道,订阅、取消订阅通知,获取通知的使能状态、角标使能状态,获取通知的相关信息等。
  • 关系型数据库:关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。
  • 元服务卡片开发:卡片是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达、减少体验层级的目的。
  • 卡片提供方:显示卡片内容,控制卡片布局以及控件点击事件。
  • 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。
  • 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。

环境搭建

软件要求

  • DevEco Studio版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. 获取OpenHarmony系统版本:标准系统解决方案(二进制)。以3.2 Release版本为例:

  2. 搭建烧录环境。

    1. 完成DevEco Device Tool的安装
    2. 完成RK3568开发板的烧录
  3. 搭建开发环境。

    1. 开始前请参考工具准备,完成DevEco Studio的安装和开发环境配置。
    2. 开发环境配置完成后,请参考使用工程向导创建工程(模板选择“Empty Ability”)。
    3. 工程创建完成后,选择使用真机进行调测

代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在gitee中提供。

├──entry/src/main/ets            // 代码区     
│  ├──common  
│  │  ├──constants
│  │  │  └──CommonConstants.ets  // 常量类
│  │  ├──database
│  │  │  ├──Form.ets             // 数据库卡片操作
│  │  │  └──SensorData.ets       // 数据库行走步数操作 
│  │  └──utils
│  │     ├──ChartDataUtils.ets   // 图表数据操作工具类  
│  │     ├──DatabaseUtils.ets    // 数据库工具类
│  │     ├──DateUtils.ets        // 日期工具类
│  │     ├──GlobalContext.ets    // 项目工具类
│  │     └──Logger.ets           // 日志打印工具类
│  ├──entryability
│  │  └──EntryAbility.ets        // 程序入口类
│  ├──entryformability
│  │  └──EntryFormAbility.ets    // 卡片创建,更新,删除操作类
│  ├──pages
│  │   └──MainPage.ets           // 主界面
│  └──viewmodel
│      ├──ChartPoint.ets         // 图表点类
│      ├──ChartValues.ets        // 图表值类
│      ├──FormData.ets           // 表单数据类
│      └──PointStyle.ets         // 图表点样式类
├──entry/src/main/js             // js代码区
│  ├──card2x2                    // 2x2卡片目录
│  ├──card2x4                    // 2x4卡片目录
│  ├──common                     // 卡片资源目录
│  └──i18n                       // 卡片国际化目录
└──entry/src/main/resources      // 资源文件目录

关系型数据库

元服务卡片需要用数据库保存不同时间、不同卡片的数据,而且在添加多张卡片情况下,需要保持数据同步刷新。因此需要创建两张表,一张是保存卡片信息,另一张是记录当天行走步数。

  1. 数据库创建使用的SQLite。
  // CommonConstants.ets
  // 表单SQLite
  static readonly CREATE_TABLE_FORM: string = 'CREATE TABLE IF NOT EXISTS Form ' +
    '(id INTEGER PRIMARY KEY AUTOINCREMENT, formId TEXT NOT NULL, formName TEXT NOT NULL, dimension INTEGER)';
  // 行走步数SQLite
  static readonly CREATE_TABLE_SENSOR_DATA: string = 'CREATE TABLE IF NOT EXISTS SensorData ' +
    '(id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, stepsValue INTEGER)';
  1. 在EntryAbility的onCreate方法通过DatabaseUtils.createRdbStore方法创建数据库,并创建相应的表。
  // EntryAbility.ets
  onCreate(want: Want, param: AbilityConstant.LaunchParam): void {
    GlobalContext.getContext().setObject('abilityWant', want);
    GlobalContext.getContext().setObject('abilityParam', param);
    DatabaseUtils.createRdbStore(this.context).then((rdbStore: Object | undefined) => {
      // 添加前三天行走模拟数据
      DatabaseUtils.addSimulationData(rdbStore as DataRdb.RdbStore);
    }).catch((error: Error) => {
      ...
    });
  }

消息通知

需要在MainPage的aboutToAppear调用requestNotification方法申请通知栏权限,效果如图所示:

// MainPage.ets
aboutToAppear() {
  // 申请通知栏权限
  this.requestNotification();
  ...
}

requestNotification() {
  Notification.requestEnableNotification().then(() => {
    ...
  }).catch((err: Error) => {
    ...
  });
}

通过aboutToAppear的setInterval方法开启定时器,当定时器到10秒后,通过DatabaseUtils.sendNotifications方法发送消息到通知栏。效果如图所示:

// DatabaseUtils.ets
// 发送通知
sendNotifications(stepsValue: string, notificationId: number) {
  // 获取当前系统语言
  let notificationBarTitle: string;
  let Language: string = I18n.System.getSystemLanguage();
  // 判断是否为中文
  if (Language.match(CommonConstants.CHINESE_LANGUAGE)) {
    notificationBarTitle = CommonConstants.NOTIFICATIONS_TITLE_GONE_TODAY_ZH +
        stepsValue + CommonConstants.NOTIFICATIONS_TITLE_STEPS_ZH;
  } else {
    notificationBarTitle = CommonConstants.NOTIFICATIONS_TITLE_GONE_TODAY_EN +
        stepsValue + CommonConstants.NOTIFICATIONS_TITLE_STEPS_EN;
  }
  // 发布NotificationRequest.
  Notification.publish({
    id: CommonConstants.NOTIFICATIONS_ID,
    content: {
        contentType: Notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: notificationBarTitle,
          text: ''
        }
    }
  }).then(() => {
    ...
  });
}

元服务卡片

使用元服务卡片分为四步:创建、初始化、更新、删除。

创建元服务卡片目录

  1. 在main目录下,点击鼠标右键 > New > Service Widget。

  2. 然后选择第一个选项下面带有Hello World字样,点击下一步Next。

  3. 填写卡片名字(Service widget name)、卡片介绍(Description)、是否开启低代码开发(Enable Super Visual)、开发语言(ArkTS和JS)、支持卡片规格(Support dimension)、关联表单(Ability name)点击Finish完成创建。如需创建多个卡片目录重新按照步骤1执行。

  4. 创建完卡片后,同级目录出现js目录,然后开发者在js目录下使用hml+css+json开发js卡片页面。

初始化元服务卡片

应用选择添加元服务卡片到桌面后,在EntryFormAbility的onAddForm方法进行卡片初始化操作,效果如图所示:

// EntryFormAbility.ets
onAddForm(want: Want) {
  let formId: string = want.parameters !== undefined ?
    want.parameters[CommonConstants.FORM_PARAM_IDENTITY_KEY] as string : '';
  let formName: string = want.parameters !== undefined ?
    want.parameters[CommonConstants.FORM_PARAM_NAME_KEY] as string : '';
  let dimensionFlag: number = want.parameters !== undefined ?
    want.parameters[CommonConstants.FORM_PARAM_DIMENSION_KEY] as number : 0;
  // 创建数据库
  DatabaseUtils.createRdbStore(this.context).then((rdbStore: Object | undefined) => {
    // 存储卡片信息
    let form: Form = new Form();
    form.formId = formId;
    form.formName = formName;
    form.dimension = dimensionFlag;
    ...
    DatabaseUtils.insertForm(form, rdbStore as DataRdb.RdbStore);
    getToDaySteps(rdbStore as DataRdb.RdbStore, dimensionFlag, formId);
  }).catch((error: Error) => {
    ...
  });
  ...
  // 初始化卡片数据
  let formData: FormData = new FormData();
  formData.percent = 0;
  formData.steps = 0;
  return FormBindingData.createFormBindingData(formData);
};

更新元服务卡片

  1. 初始化加载主页面布局之前,在MainPage的aboutToAppear方法中,调用setInterval方法开启定时器。时间到则先通过DatabaseUtils.insertValues方法把步数插入到数据库,再通过DatabaseUtils.updateForms方法更新卡片步数。
// MainPage.ets
aboutToAppear() {
  ...
  DatabaseUtils.getSensorData(rdbStoreValue, DateUtils.getDate(0))
    .then((sensorData: SensorData) => {
      if (sensorData) {
        this.stepsValue = sensorData.stepsValue;
      }
        // 开启定时器
        this.intervalId = setInterval(() => {
          ...
          DatabaseUtils.insertValues(this.stepsValue, rdbStoreValue);
          DatabaseUtils.updateForms(this.stepsValue, rdbStoreValue);
        }, CommonConstants.INTERVAL_DELAY_TIME);
      ...
  });
}
  
// DatabaseUtils.ets
updateForms(stepValue: number, rdbStore: DataRdb.RdbStore) {
  let predicates: DataRdb.RdbPredicates =
    new DataRdb.RdbPredicates(CommonConstants.TABLE_FORM);
  // 查询卡片
  rdbStore.query(predicates).then((resultSet: DataRdb.ResultSet) => {
    ...
    // 查询第一行
    resultSet.goToFirstRow();
    do {
      let formId: string = resultSet.getString(resultSet.getColumnIndex(CommonConstants.FIELD_FORM_ID));
      let dimension: number = resultSet.getLong(resultSet.getColumnIndex(CommonConstants.FIELD_DIMENSION));
      ChartDataUtils.getFormData(formId, stepValue, dimension, rdbStore)
        .then((formData: FormData) => {
          // 更新多张卡片
          FormProvider.updateForm(formData.formId, FormBindingData.createFormBindingData(formData))
            .catch((error: Error) => {
              ...
            });
        }).catch((error: Error) => {
          ...
        }); 
    } while (resultSet.goToNextRow());
    resultSet.close();
  }).catch((error: Error) => {
    ...
  });
}
  1. 卡片添加到桌面后,在EntryFormAbility的onAddForm方法中,调用formProvider.setFormNextRefreshTime方法设置倒计时。时间到了则通过updateSensorData方法更新卡片步数。
  // EntryFormAbility.ets
  onAddForm(want: Want) {
    ...
    // 五分钟倒计时
    formProvider.setFormNextRefreshTime(formId, CommonConstants.FIVE_MINUTES, (error, data) => {
      ...
    });
  }
  
  onUpdateForm(formId: string) {
    // 更新步数
    this.updateSensorData();
    ...
  }
  
  updateSensorData() {
    DatabaseUtils.createRdbStore(this.context).then((rdbStore: Object | undefined) => {
      ...
      // 获取今天步数
      let getSensorData: Promise<SensorData> =
      DatabaseUtils.getSensorData(rdbStore as DataRdb.RdbStore, DateUtils.getDate(0));
      getSensorData.then((sensorData: SensorData) => {
        let stepValue: number = 0;
        if (sensorData) {
          stepValue = sensorData.stepsValue;
        }
        // 更新卡片数据
        DatabaseUtils.updateForms(stepValue, rdbStore);
      }).catch((error: Error) => {
        ...
      });
    }).catch((error: Error) => {
      ...
    });
  }
  1. 通过src/main/resources/base/profile/form_config.json配置文件,根据updateDuration或者scheduledUpdateTime字段配置刷新时间。updateDuration优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。当配置的刷新时间到了,系统调用onUpdateForm方法进行更新。
  // form_config.json
  {
    // 卡片的类名
    "name": "card2x2",
    // 卡片的描述
    "description": "This is a service widget.",
    // 卡片对应完整路径 
    "src": "./js/card2x2/pages/index/index",
    // 定义与显示窗口相关的配置
    "window": {
      "designWidth": 720,
      "autoDesignWidth": true
    },
    // 卡片的主题样式
    "colorMode": "auto",
    // 是否为默认卡片
    "isDefault": true,
    // 卡片是否支持周期性刷新
    "updateEnabled": true,
    // 采用24小时制,精确到分钟
    "scheduledUpdateTime": "00:00",
    // 当取值为0时,表示该参数不生效,当取值为正整数N时,表示刷新周期为30*N分钟。
    "updateDuration": 1,
    // 卡片默认外观规格
    "defaultDimension": "2*2",
    // 卡片支持外观规格
    "supportDimensions": [
      "2*2"
    ]
  }
  
  // EntryFormAbility.ets
  onUpdateForm(formId: string) {
    // 更新步数
    updateSensorData();
    ...
  }

删除元服务卡片

当用户需要删除元服务卡片时,可以在EntryFormAbility的onRemoveForm方法中,通过DatabaseUtils.deleteFormData方法删除数据库中对应的卡片信息。

// EntryFormAbility.ets
onRemoveForm(formId: string) {
  DatabaseUtils.createRdbStore(this.context).then((rdbStore: Object | undefined) => {
    ...
    // 删除数据库中对应的卡片信息
    DatabaseUtils.deleteFormData(formId, rdbStore as DataRdb.RdbStore);
  }).catch((error: Error) => {
    ...
  });
}

// DatabaseUtils.ets
deleteFormData(formId: string, rdbStore: DataRdb.RdbStore) {
  let predicates: DataRdb.RdbPredicates = new DataRdb.RdbPredicates(CommonConstants.TABLE_FORM);
  predicates.equalTo(CommonConstants.FIELD_FORM_ID, formId);
  rdbStore.delete(predicates).catch((error: Error) => {
    ...
  });
}

最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料

 获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(HarmonyOS NEXT)最新学习路线

  •  HarmonOS基础技能

  • HarmonOS就业必备技能 
  •  HarmonOS多媒体技术

  • 鸿蒙NaPi组件进阶

  • HarmonOS高级技能

  • 初识HarmonOS内核 
  • 实战就业级设备开发

 有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

《鸿蒙生态应用开发V2.0白皮书》

图片

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

图片

 《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

图片

 《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

图片

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

图片

 获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值