1. 介绍 本篇Codelab主要介绍如何在HarmonyOS上开发一个JS卡片提供方,帮助开发者快速上手JS卡片类应用开发。该卡片包含2*2、2*4两种布局样式,具体效果如下:
卡片的基本概念:
- 卡片使用方
显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。 - 卡片提供方
提供卡片显示内容的HarmonyOS应用或原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。
详细介绍请参考服务卡片开发指导。
2. 搭建HarmonyOS环境我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
- 安装DevEco Studio,详情请参考下载和安装软件。
- 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
- 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
- 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
- 开发者可以参考以下链接,完成设备调试的相关配置:
本篇Codelab使用的DevEco Studio版本为DevEco Studio 2.1 Beta4,使用的SDK版本为API Version 5。
3. 代码结构解读
本篇Codelab内容包括计步器传感器的使用,对象关系映射型数据库的使用,卡片的创建、更新、删除,JS中progress组件和chart组件的使用,DevEco Studio工程代码结构如下:
- cardEntity:存放chart图表相关对象的目录。
- ChartPoint:chart图表线型图中的点对象,用于存储点的值,描述,样式等信息。
- ChartValues:chart图表线型图中的数据对象,用于存储数据点集合,样式信息。
- PointStyle:chart图表线型图中的点的样式,用于存储点的样式。
- database:存放对象关系映射数据库相关对象的目录。
- Form:卡片表对象,用于存储卡片id,卡片名称以及卡片规格。
- FormDatabase:卡片数据库对象,用于创建卡片数据库。
- SensorData:传感器数据对象,用于存储步数及日期。
- slice:存放slice的目录
- MainAbilitySlice:主页面,卡片router跳转的页面。
- StepFormAbilitySlice:卡片页面。
- utils:存放工具类的目录。
- ChartDataUtils:提供图表数据相关方法。
- DatabaseUtils:提供对数据库相关操作的方法。
- DateUtils:提供日期相关操作的方法。
- LogUtils:日志工具类。
- PermissionBridge:权限回调。
- MainAbility:主程序入口,DevEco Studio生成,需要加入向用户申请计步器传感器权限代码。
- MyApplication:DevEco Studio生成,不需变更。
- StepFormAbility:卡片功能入口。
- StepSensorService:计步器传感器service,提供步数的采集,数据的存储,卡片更新等功能。
- js:存放js资源文件。
- card2X2:存放2*2卡片页面的资源文件。
common:存放背景图片等资源。
backgroud.jpg:背景图片,用户可根据需要自行选择背景图片。
pages.index:存放页面,样式资源文件。
index.css:样式文件。
index.hml:页面布局文件。
index.json:包含页面默认值以及相关方法。 - card2X4:存放2*4卡片页面的资源文件,目录结构同card2X2。
4. 配置文件 卡片应用是一款特殊的元能力服务,需要在配置文件中声明。以本工程为例,config.json文件中"abilities"配置forms模块的细节如下:
- "forms": [
- {
- "jsComponentName": "step_form_card",
- "isDefault": true,
- "scheduledUpdateTime": "10:30",
- "defaultDimension": "2*2",
- "name": "step_form_card",
- "description": "This is a step service widget",
- "colorMode": "auto",
- "type": "JS",
- "supportDimensions": [
- "2*2"
- ],
- "updateEnabled": true,
- "updateDuration": 1
- },
- {
- "jsComponentName": "card2X4",
- "isDefault": false,
- "scheduledUpdateTime": "10:30",
- "defaultDimension": "2*4",
- "name": "card2X4",
- "description": "This is a step service widget",
- "colorMode": "auto",
- "type": "JS",
- "supportDimensions": [
- "2*4"
- ],
- "updateEnabled": true,
- "updateDuration": 1
- }
- ]
复制代码配置JS模块的细节如下:
- "js": [
- {
- "pages": [
- "pages/index/index"
- ],
- "name": "step_form_card",
- "window": {
- "designWidth": 720,
- "autoDesignWidth": true
- },
- "type": "form"
- },
- {
- "pages": [
- "pages/index/index"
- ],
- "name": "card2X4",
- "window": {
- "designWidth": 720,
- "autoDesignWidth": true
- },
- "type": "form"
- }
- ]
复制代码配置文件中,应注意如下配置:
- "js"模块中的name字段要与"forms"模块中的jsComponentName字段的值一致,为js资源的实例名。
- "forms"模块中的name为卡片名,即在onCreateForm中根据AbilitySlice.PARAM_FORM_NAME_KEY可取到的值。
- 除此之外,卡片的Ability中还需要配置"visible": true和"formsEnabled": true。
- 定时刷新和定点刷新都配置的情况下,定时刷新优先。
- defaultDimension是默认规格,必须设置。
- 详细配置可参考JS卡片开发指导。
为了保证程序的正常运行,我们需要在配置文件中声明应用使用的权限,同时也要向用户申请授权,具体代码会在创建卡片章节体现,config.json文件中"reqPermissions"模块的细节如下:
- "reqPermissions": [
- {
- "name": "ohos.permission.ACTIVITY_MOTION",
- "reason": "get step count",
- "usedScene": {
- "ability": [
- ".MainAbility",
- ".StepSensorService"
- ],
- "when": "always"
- }
- },
- {
- "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
- "reason": "keep service ability backgroud running",
- "usedScene": {
- "ability": [
- ".StepSensorService"
- ],
- "when": "always"
- }
- }
- ]
复制代码 5. 开发卡片布局本篇Codelab为卡片应用设计了2*2和2*4两种布局风格,效果图如下:
由于2*2布局内容相对简单,下面以2*4布局为例,进行详细介绍。整个2*4卡片展示的内容分为左右两个部分,其中左边内容从上到下分别显示描述、行走的里程、步数,主要以文字的方式展示;右边从上到下分别显示进度条(步数进度百分比)和近四天步数的线型图,主要以图表的方式展示。
hml代码示例如下: index.json文件中配置了hml中需要用到的默认数据和action配置,代码示例如下:
- {
- "data": {
- "datasets": [
- {
- "strokeColor": "#CDCACA",
- "fillColor": "#CDCACA",
- "data": [
- {
- "value": 0,
- "description": "0",
- "textLocation": "top",
- "textColor": "#CDCACA",
- "pointStyle": {
- "shape": "circle",
- "size": 5,
- "fillColor": "#FF9C28",
- "strokeColor": "#FF9C28"
- }
- }
- // 开发者可参考上面数据结构自行设计其他三天的默认数据
- ],
- "gradient": true
- }
- ],
- "options": {
- "xAxis": {
- "min": 0,
- "max": 3,
- "display": false,
- "axisTick": 4
- },
- "yAxis": {
- "min": 0,
- "max": 1000
- }
- },
- "steps": 0,
- "percent": 0,
- "mileage": 0
- },
- "actions": {
- "routerEvent": {
- "action": "router",
- "bundleName": "com.huawei.cookbook",
- "abilityName": "com.huawei.cookbook.MainAbility"
- }
- }
- }
复制代码数据说明:
datasets:线型图点集;
options:线型图X轴,Y轴显示;
详情参考JS chart组件开发指导了解相关参数意义;
steps:步数;
percent:步数进度百分比,为方便演示,本篇codelab以1000步为目标步数;mileage:里程(米),计算方式为steps*0.6。
背景图片开发者可自行准备。
6. 创建卡片创建卡片数据库本篇Codelab使用对象关系映射数据库来对卡片的信息和步数进行存储,创建了一个数据库(FormDatabase),两个表(Form和SensorData)分别存储卡片信息和每日行走的步数。
定义数据库类FormDatabase.java,数据库包含了"Form","SensorData"两个表,版本号为 "1",示例代码如下:
- /**
- * form database
- */
- @Database(entities = {Form.class, SensorData.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;
-
- /**
- * parametric constructor
- *
- * [url=home.php?mod=space&uid=3142012]@param[/url] formId form Id
- * @param formName form Name
- * @param dimension form dimension
- */
- public Form(Long formId, String formName, Integer dimension) {
- this.formId = formId;
- this.formName = formName;
- this.dimension = dimension;
- }
- // 开发者自行添加字段的getter和setter 方法。
- }
复制代码定义实体类SensorData.java,对应数据库内的表名"sensorData",包含了日期"date"作为主键和步数"stepValue"两个字段,示例代码如下:
- /**
- * Sensor Data Storage Table
- */
- @Entity(tableName = "sensorData")
- public class SensorData extends OrmObject {
- @PrimaryKey()
- private String date;
- private Integer stepsValue;
- /**
- * parametric constructor
- *
- * @param date date
- * @param stepsValue stepsValue
- */
- public SensorData(String date, Integer stepsValue) {
- this.date = date;
- this.stepsValue = stepsValue;
- }
- // 开发者自行添加字段的getter和setter 方法。
- }
复制代码关于对象关系映射数据库的开发可参考对象关系映射数据库开发指导。
卡片应用初始化
卡片程序安装启动后,进入MainAbility,在onStart的时候,需要向用户申请计步器传感器的权限,示例代码如下:
- [url=home.php?mod=space&uid=2735960]@Override[/url]
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setMainRoute(MainAbilitySlice.class.getName());
- requestPermission();
- }
- // 向用户申请相关权限的授权
- private void requestPermission() {
- String[] permissions = {
- // 计步器权限
- SystemPermission.ACTIVITY_MOTION
- };
- List<String> permissionFiltereds = Arrays.stream(permissions)
- .filter(permission -> verifySelfPermission(permission) != IBundleManager.PERMISSION_GRANTED)
- .collect(Collectors.toList());
- if (permissionFiltereds.isEmpty()) {
- PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_GRANTED);
- return;
- }
- // 向用户申请相关权限的授权
- requestPermissionsFromUser(permissionFiltereds.toArray(new String[permissionFiltereds.size()]),
- PERMISSION_REQUEST_CODE);
- }
-
- @Override
- public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) {
- if (permissions == null || permissions.length == 0 || grantResults == null || grantResults.length == 0) {
- return;
- }
- for (int grantResult : grantResults) {
- if (grantResult != IBundleManager.PERMISSION_GRANTED) {
- PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_DENIED);
- terminateAbility();
- return;
- }
- }
- // 授权回调
- PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_GRANTED);
- }
- 当用户授权后,会拉起计步器service,示例代码如下:
- @Override
- public void onPermissionGranted() {
- Intent intentService = new Intent();
- Operation operation = new Intent.OperationBuilder()
- .withDeviceId("")
- .withBundleName(getBundleName())
- .withAbilityName(StepSensorService.class.getSimpleName())
- .build();
- intentService.setOperation(operation);
- startAbility(intentService);
- }
复制代码当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用onCreateForm回调函数,完成卡片信息的初始化,在StepFormAbility中有如下示例代码:
- @Override
- protected ProviderFormInfo onCreateForm(Intent intent) {
- ProviderFormInfo providerFormInfo = new ProviderFormInfo();
- IntentParams params = intent.getParams();
- if (params == null) {
- return providerFormInfo;
- }
- // 获取卡片id
- long formId = parseFormId(params);
- // 获取卡片名称
- String formName = parseFormName(params);
- // 获取卡片规格
- int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
- connect = helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class);
- // 存储卡片信息
- Form form = new Form(formId, formName, dimension);
- DatabaseUtils.insertForm(form, connect);
- // 获取当天的步数
- SensorData sensorData = DatabaseUtils.getSensorData(connect, DateUtils.getDate(0));
- String stepValue = "";
- if (sensorData != null) {
- stepValue = sensorData.getStepsValue() + "";
- } else {
- stepValue = "0";
- }
- // 获取卡片页面需要的数据
- ZSONObject zsonObject = ChartDataUtils.getZsonObject(stepValue, dimension, connect);
- providerFormInfo.setJsBindingData(new FormBindingData(zsonObject));
- return providerFormInfo;
- }
复制代码当卡片被删除时,需要重写onDeleteForm方法,根据卡片id删除卡片实例数据:
- @Override
- protected void onDeleteForm(long formId) {
- LogUtils.info(TAG, "onDeleteForm():formId=" + formId);
- super.onDeleteForm(formId);
- // 删除数据库中的卡片信息
- DatabaseUtils.deleteFormData(formId, connect);
- }
复制代码 7. 更新卡片创建StepSensorService
为了方便接收和处理计步器传递过来的数据,新建了一个Service类来接收、存储数据和更新卡片。
在StepSensorService的onStart方法中我们会创建计步器对象,示例代码如下:
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- connect = helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class);
- myHandler = new MyEventHandler(EventRunner.getMainEventRunner());
- categoryMotionDataCallback = new ICategoryMotionDataCallback() {
- // 接收数据
- @Override
- public void onSensorDataModified(CategoryMotionData categoryMotionData) {
- float[] values = categoryMotionData.getValues();
- // 处理数据
- handleSensorData(values[0]);
- // 设置前台service通知
- notice();
- }
- };
- // 获取计步器对象
- categoryMotion = categoryMotionAgent.getSingleSensor(CategoryMotion.SENSOR_TYPE_PEDOMETER);
- if (categoryMotion != null) {
- // 设置传感器数据回调
- categoryMotionAgent.setSensorDataCallback(categoryMotionDataCallback, categoryMotion, INTERVAL);
- }
- }
复制代码其中handleSensorData方法包含存储数据、更新卡片和更新页面的操作,示例代码如下:
- // 处理传感器数据
- private void handleSensorData(float value) {
- SensorData realSensorData =
- DatabaseUtils.getRealSensorData(value, connect,
- DateUtils.getDate(0), DateUtils.getDate(1));
- float realValue = realSensorData.getStepsValue();
- String stringValue = String.valueOf((int) realValue);
- myHandler.postTask(new Runnable() {
- @Override
- public void run() {
- // 存储数据,开发者可自行实现
- DatabaseUtils.insertValues(realValue, connect);
- // 更新页面,开发者可自行实现
- MainAbilitySlice.updatePage(stringValue);
- // 更新卡片
- updateForms(stringValue);
- }
- });
- }
-
- // 更新卡片
- private void updateForms(String value) {
- OrmPredicates ormPredicates = new OrmPredicates(Form.class);
- List<Form> forms = connect.query(ormPredicates);
- for (Form form : forms) {
- ZSONObject result = ChartDataUtils.getZsonObject(value, form.getDimension(), connect);
- try {
- updateForm(form.getFormId(), new FormBindingData(result));
- } catch (FormException e) {
- connect.delete(form);
- LogUtils.error("updateForms", "formid not exit");
- }
- }
- }
- 前台service,为了保持service不被系统销毁,需要使用前台service配合手机管家中的相关配置来达到目的。示例代码如下:
- // 前台service
- private void notice() {
- // 创建通知
- NotificationRequest request = new NotificationRequest(NOTICE_ID);
- request.setAlertOneTime(true);
- NotificationRequest.NotificationNormalContent content = new NotificationRequest.NotificationNormalContent();
- SensorData sensorData = DatabaseUtils.getSensorData(connect, DateUtils.getDate(0));
- if (sensorData != null) {
- content.setText("今天走了" + sensorData.getStepsValue() + "步");
- }
- NotificationRequest.NotificationContent notificationContent = new NotificationRequest.NotificationContent(content);
- request.setContent(notificationContent);
- // 绑定通知
- keepBackgroundRunning(NOTICE_ID, request);
- }
复制代码关于前台service的详细介绍,可参考前台service开发指导。
手机管家配置步骤如下:
手机管家> 应用启动管理> 计步器服务卡片> 点击右侧滑块> 选择开启"允许后台活动"开启后台运行权限
chart线型图数据获取及样式设置,后面代码将按照下图设置图表样式:
获取卡片更新数据,示例代码如下:
- // 获取卡片更新的数据
- public static ZSONObject getZsonObject(String value, int dimension, OrmContext connect) {
- ZSONObject result = new ZSONObject();
- // 计算进度条进度,百分比
- int round = (int) Math.round(Double.parseDouble(value) / TARGET_STEPS * PROGRESS_PERCENT);
- result.put("percent", round);
- // 步数
- result.put("steps", value);
- if (dimension == DIMENSION_2X4) {
- // 组装chartdatasets
- List<ChartValues> datasets = new ArrayList<>(1);
- ChartValues chartValues = ChartDataUtils.getChartValues(value, connect);
- // 获取点集数据
- datasets.add(chartValues);
- // chart图表数据
- result.put("datasets", datasets);
- // 里程
- result.put("mileage", Math.round(Integer.parseInt(value) * METER_PER_STEP));
- }
- return result;
- }
复制代码获取chart线型图点集,示例代码如下:
- // 获取chart组件需要的数据点集
- public static ChartValues getChartValues(String value, OrmContext connect) {
- ChartValues chartValues = new ChartValues();
- // 设置填充色颜色,即上图紫色框内的样式
- chartValues.setFillColor(GRAY_COLOR);
- // 设置线的颜色,即上图黄色框内线条的颜色
- chartValues.setStrokeColor(GRAY_COLOR);
- chartValues.setGradient(true);
- // 获取点的集合
- // 获取前三天的点的集合,开发者可自行实现
- List<ChartPoint> chartPoints = ChartDataUtils.getChartPoints(connect);
- ChartPoint noewChartPoint = ChartDataUtils.getChartPoint(Integer.parseInt(value));
- // 加入今天的步数点
- chartPoints.add(noewChartPoint);
- chartValues.setData(chartPoints);
- return chartValues;
- }
复制代码根据步数获取某一点数据:
- // 获取chart组件点数据
- public static ChartPoint getChartPoint(int value) {
- ChartPoint chartPoint = new ChartPoint();
- // 点的数值
- chartPoint.setValue(value);
- // 点的描述
- chartPoint.setDescription(value + "");
- // 点的描述显示位置,此处设置为点的上面
- chartPoint.setTextLocation(ChartPoint.TextLocation.top.toString());
- // 设置描述字体的颜色
- chartPoint.setTextColor(GRAY_COLOR);
- PointStyle pointStyle = new PointStyle();
- // 设置点的大小
- pointStyle.setSize(POINT_SIZE);
- // 设置点的颜色
- pointStyle.setFillColor(ORANGE_COLOR);
- // 设置点的外框颜色
- pointStyle.setStrokeColor(ORANGE_COLOR);
- // 设置点的样式,此处设置为圆形
- pointStyle.setShape(PointStyle.PointShape.CIRCLE.toString()
- .toLowerCase(Locale.ROOT));
- chartPoint.setPointStyle(pointStyle);
- return chartPoint;
- }
复制代码 8. 恭喜你恭喜你已经完成计步器JS卡片应用的开发,并且学到了:
- JS卡片开发部分接口的使用。
- 卡片开发如何去配置config.json文件。
- JS progress组件的使用。
- JS chart组件的使用。
9. 参考