龙玄-HarmonyOS服务卡片学习与自发探究|自学笔记 原创 精华
龙玄-HarmonyOS服务卡片学习与自发探究|自学笔记
时间:2021年7月13日08:32:07
本文就社区里原子化卡片的相关文章进行学习,对原子化卡片进行自发探究
一、一文看懂HarmonyOS服务卡片运行原理和开发方法
(一)卡片是啥?卡片在哪?卡片特征
卡片是啥:即服务卡片、HarmonyOS卡片。是HarmonyOS 2推出的,为达到服务直达的,FA的一种展现形式。==(新推出、服务直达)==
卡片在哪:凡是图标下方显示一条横线的应用,均可以上滑图标呼出服务卡片。==(横向应用、上滑呼出)==
卡片特征:
易用可见(很明显的特征)
智能可选:信息可变,支持自定义
==多端可变==:多端自适应
对于多端可变,HarmonyOS应用开发系列课(进阶篇)-第4期 HarmonyOS应用程序架构揭秘-多设备场景下应用开发的挑战和解决策略-第一个挑战:差异化多端显示、差异化交互方式,讲解到其解决策略:
- UI信息结构的抽象(多态控件、动态布局、工程模板)
- 交互事件归一(差异化反馈、一致化接口)
(二)卡片设计规范
- 【数量选择】
-
每个应用均可配置、每个PA最多16个
根据后文:每个PA最多 16*4=64个卡片
-
卡片有四种尺寸可配置
-
卡片可以实现多个实例
- 【尺寸选择】
服务卡片尺寸分为:微(1×2)、小( 2×2 )、中( 2×4 )、大(4×4)4种尺寸,其中==小尺寸为必选尺寸==。
- 【内容构成】
服务卡片由多种设计元素组合而成,以下7种常见信息元素可以作为内容选择:
==图标、数据、文本、按钮、图片、宫格、列表==
尺寸旁边的数字,表示建议选择元素的数量
大家留意,有个小更新。==目前微卡片也支持按钮了,和小尺寸一样了==-来自原文回复,根据2021年7月13日20:39:44的直播微卡片也支持按钮
(三)服务卡片运行机制
- 【服务卡片整体框架】
三方部署,卡片使用方、卡片管理服务、卡片提供方
- 卡片使用方:实例创建、决定位置
- 卡片管理服务:对象的管理使用、卡片的周期性刷新
- 卡片提供方:显示内容、控件布局、点击事件
对图的自理解:使用方创建实例并触发事件通过Java kit请求卡片对象,根据提供方的kit进映射服务,其中管理服务宿主代管(定时刷新模块、缓存管理模块)。(数据流看箭头!)
- 主动刷新具体流程
自理解那里没有注意到拉起这个环节,没有缓存则提供方kit+使用方数据=卡片对象,存在则直接返回。
注:卡片使用方和提供方不要求常驻运行,在需要添加/删除/请求更新卡片时,卡片管理服务会拉起卡片提供方获取卡片信息。
- 定时/定点刷新具体流程
对图的自理解:有解绑与绑定的环节,关系到使用方的数据安全问题!数据安全是红线!
-
关于Java服务卡片与JS服务卡片的区别
- Java服务卡片返回的实例采用ComponentProvider方式显示在服务卡片使用方上
- JS服务卡片是通过ACE引擎加载和解析hap包的卡片资源,如hml、css和config.json文件,解析引擎解析完hml和css后进行渲染,结合数据生成InstantView显示在服务卡片使用方指定的位置上
使用约束(from开发文档)
- 只有Phone、Tablet和Wearable设备的FA支持服务卡片。
- JS卡片不支持调试。
- 【服务卡片生命周期回调函数】
- 关键回调函数
- 回调函数时序
注:卡片管理服务不负责卡片应用进程保活,卡片管理服务在相应的时机拉起卡片提供方进程,调用创建、更新、删除卡片等回调。
(四)服务卡片开发过程
JS服务卡片开发为例
- JS卡片场景能力(from 开发文档)
Java卡片:适合作为一个直达入口,没有复杂的页面和事件。
JS卡片:适合有复杂界面的卡片。
场景 | JS卡片 | 支持的版本 |
---|---|---|
实时刷新(类似时钟) | JS可以做到端侧刷新,但是需要定制化组件 | HarmonyOS 2.0及以上 |
开发方式 | JS卡片在使用方加载渲染,提供方只要处理数据、组件和逻辑分离 | HarmonyOS 2.0及以上 |
组件支持 | div、list、list-item、swiper、stack、image、text、span、progress、button(定制:chart 、clock、calendar) | HarmonyOS 2.0及以上 |
卡片内动效 | 暂不开放 | HarmonyOS 2.0及以上 |
阴影模糊 | 支持 | HarmonyOS 2.0及以上 |
动态适应布局 | 支持 |
1. 【服务卡片开发环境】
- 创建服务卡片项目步骤:
(1)新建一个Project,根据项目需要选择JS或者Java的项目模板,建议选择JS项目工程。
选择Empty Ability(JS)或Empty Ability(Java)就行,当然这里就选择Empty Ability(JS)
(2)在“Configure your project”中,打开“Show In Service Center”,表示在服务中心中展示。
(3)Finish之后就能创建一个带有服务卡片的项目。
-
详细创建项目过程可以参考官网资料【开发服务卡片】。
这部分该文没有做很清晰的讲解,下面是直接==从开发文档进行学习==:
开发文档-工具-HUAWEI DevEco Studio使用指南-应用开发-开发服务卡片
- 使用约束:
只有Phone、Tablet和Wearable设备的FA支持服务卡片
==JS卡片不支持调试=
- 创建服务卡片
关键在:Show in Service这个按钮
"Show in Service Center”,该参数表示是否在服务中心露出。如果Project Type为Service,则会同步创建一个2 * 2的服务卡片模板,同时还会创建入口卡片;如果Project Type为Application,则只会创建一个2*2的服务卡片模板。
EntryCard为服务卡片的包
immersion_widget.xml为插件的配置文件
- 在已有工程中添加新模块,也可以添加服务卡片和EntryCard,只需在创建模块时,勾选==“Show in Service Center”==即可。创建出来的服务卡片和EntryCard,同创建新工程生成的一致。
- 在已有工程中,只添加EntryCard,只能通过==手工方式==,按照上图中的EntryCard目录创建对应的文件夹和图片。
- 在已有工程中,新添加服务卡片,可以通过如下方法进行创建。
选择模块(如entry模块)下的任意文件,点击菜单栏File > New > Service Widget创建服务卡片。
选择模块(如entry模块)下的任意文件,点击右键 > New > Service Widget创建服务卡片。
打开HUAWEI DevEco Studio,单击File> Open选择此StepsCard • 单击Build> Build App(s)/Hap(s)>Build Debug Hap(s)以==编译hap软件包== • 单击Run> Run 'entry’以运行hap包.然后通过远程虚拟机就可以看到服务卡片。
注意 • 您可以选择在==模拟器或真机上==运行hap软件包。 • 如果在真机上运行它,则需要在项目的File> Project Structure> Modules> Signing Configs中配置签名和证书信息。 • 由于目前暂无办法保证卡片服务不被系统销毁,需要通过手机管家> 应用启动管理> 计步器服务卡片> 点击右侧滑块> 选择开启“允许后台活动”开启后台运行权限,如需要也可开启“运行自启动”。
2. 【JS服务卡片开发过程】
-
JS服务卡片开发主要分为四步:
(1)配置config.json
(2)实现生命周期回调方法
(3)开发卡片界面元素
(4)实现卡片界面交互
- 配置config.json
- 工程目录
common目录主要存放公共资源;
i18n目录是存放国际化资源;
pages.index目录(实际是pages/index)存放服务卡片界面开发主要文件,里面采用hml+css+json文件组合,构成JS服务卡片界面。
-
config.json中abilities配置forms细节
为了更好的阅读体验进行了代码粘贴及注释,其中既包含本文的也包含开发文档里的
"abilities": [
{
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
],
"name": "com.example.mysevercard.MainAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"formsEnabled": true,
//formsEnable的值若设置为true,则表示该Ability支持服务卡片显示
"label": "$string:entry_MainAbility",
"type": "page",
//forms的值是一个json 数组,表示可配置多张服务卡片
"forms": [
{
"jsComponentName": "widget",
//JS服务卡片名字、其名字需要和js配置的name一致,表示js组件实例
"isDefault": true,
//表示该卡片是一个默认卡片
"scheduledUpdateTime": "10:30",
//卡片定时/定点触发更新的时间,采用24小时制,精确到分钟。
"defaultDimension": "2*2",
//表示默认卡片的尺寸是22,其他的可选12,24,44尺寸。对于需要在服务中心露出的卡片,默认规格必须设置成2*2。
"name": "widget",
//表示卡片的类名
"description": "This is a service widget",
//表示卡片的描述。取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。
"colorMode": "auto",
//表示卡片的主题样式,取值范围:auto:自适应;dark:深色主题;light:浅色主题。
"type": "JS",
//服务卡片类型,有JS和Java两种,本次配置选择为JS。
"supportDimensions": [
"2*2"
],
//表示卡片支持的外观规格(1*2、2*2、2*4、4*4)
"updateEnabled": true,
"updateDuration": 1
// updateDuration表示服务卡片刷新的时间间隔,当取值为0时,表示该参数不生效。以30min作为单位,1表示30min,2表示1个小时,以此类推
}
],
"launchType": "standard"
}
],
metaData - 表示卡片的自定义信息,包含customizeData数组标签。 customizeData - 表示自定义的卡片信息。 name 表示数据项的键名称。字符串最大长度为255字节。 value 表示数据项的值。字符串最大长度为255字节 jsComponentName 表示JS卡片的Component名称。字符串最大长度为127字节。 仅当卡片类型为JS卡片时,需要配置该标签。 formConfigAbility 表示卡片的配置==跳转链接==,采用==URI格式==。 landscapeLayouts 表示卡片外观规格对应的==横向布局==文件,与supportDimensions中的规格一一对应。 portraitLayouts 表示卡片外观规格对应的==竖向布局==文件,与supportDimensions中的规格一一对应。
- 配置卡片编辑功能(可选功能)
有些服务卡片需要具备可编辑能力,如天气App需要编辑所在城市。方法如下:在config.json中,对某一个form的配置增加formConfigAbility的属性配置,可实现编辑功能。若不配置formConfigAbility,则不显示编辑菜单。
3. 【实现生命周期回调方法】
根据开发文档进行补充!因为文章直接给出应用,阅读起来还是很吃劲
● onCreateForm(Intent intent)// 由开发人员自行实现,将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用
● onUpdateForm(long formId) //若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要覆写该方法以支持数据更新
● onDeleteForm(long formId)//由开发人员自行实现,删除卡片实例数据
● onCastTempForm(long formId) // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
● onEventNotify(Map<Long,Integer>formEvents) //使用方发起可见或者不可见通知触发,提供方需要做相应的处理
●onTriggerFormEvent(long formId, String message) // 若卡片支持触发事件,则需要覆写该方法并实现对事件的触发
//MainAbility.java
@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);
HiLog.info(LABEL_LOG, "onCreateForm: " + formId + " " + formName + " " + specificationId);
FormBindingData formBindingData = new FormBindingData("{\"temperature\": \"60°\"}");
ProviderFormInfo formInfo = new ProviderFormInfo();
formInfo.setJsBindingData(formBindingData);
return formInfo;
}
@Override
protected void onDeleteForm(long formId) {
// 删除卡片实例数据
super.onDeleteForm(formId);
......
}
@Override
protected void onUpdateForm(long formId) {
// 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要覆写该方法以支持数据更新
super.onUpdateForm(formId);
......
}
@Override
protected void onTriggerFormEvent(long formId, String message) {
// 若卡片支持触发事件,则需要覆写该方法并实现对事件的触发
super.onTriggerFormEvent(formId, message);
......
}
@Override
protected void onCastTempForm(long formId) {
//使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
super.onCastTempForm (formId);
......
}
@Override
protected void onEventNotify(Map<Long, Integer> formEvents) {
//使用方发起可见或者不可见通知触发,提供方需要做相应的处理
super.onEventNotify(formEvents);
......
}
@Override
protected FormState onAcquireFormState(Intent intent) {
ElementName elementName = intent.getElement();
if (elementName == null) {
HiLog.info(LABEL_LOG, "onAcquireFormState bundleName and abilityName are not set in intent");
return FormState.UNKNOWN;
}
String bundleName = elementName.getBundleName();
String abilityName = elementName.getAbilityName();
String moduleName = intent.getStringParam(AbilitySlice.PARAM_MODULE_NAME_KEY);
String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);
if ("form_name2".equals(formName)) {
return FormState.DEFAULT;
}
return FormState.READY;
}
}
(from 开发文档)当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用onCreateForm(Intent intent)回调,intent中会带有卡片ID、卡片名称和卡片外观规格信息,可按需获取使用。
开发JS卡片时,FormAbility可以继承AceAbility或Ability,继承Ability时,需在onStart()方法中额外设置路由信息
- 通过Log查找查看自动回调的onCreateForm()
- 通过src/main/java/com/example/mysevercard/MainAbility.java(自己的路径哈)查看具体的实现
所有的生命周期函数代码都在MainAbility里面我就不贴图和贴代码了,大家可以去实操。
需要注意的是:开发JS卡片时,FormAbility可以继承AceAbility或Ability,继承Ability时,需在onStart()方法中额外设置路由信息。示例分别如下:
- FormAbility继承AceAbility的代码示例
public class FormAbility extends AceAbility {
......
public static long formId = -1;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
}
....
- FormAbility继承Ability的代码示例
因为跟上面的就这样很细小的区别就只贴区别
public class FormAbility extends Ability {
......
public static long formId = -1;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(FormAbilitySlice.class.getName()); //设置路由
}
....
可以通过intent变量获取到卡片相关的信息,intent中携带的信息如下表:
(五)实现卡片界面交互
具体的自己的实现在后续的笔记进行,先把文中看完总结完
服务卡片json文件提供数据绑定和界面交互。
卡片仅支持click触发的事件,事件的定义只能是直接命令式,事件定义必须包含action字段,用以说明事件类型。卡片支持两种事件类型:==跳转事件(router)==和==消息事件(message)==。
- 跳转事件可以跳转到卡片提供方的组件;
- 当卡片应用需要更新数据时(如触发了定时更新或定点更新),卡片应用获取最新数据,并调用updateForm接口更新卡片。消息事件可以将开发者自定义信息传递给卡片提供方。
@Override
protected void onUpdateForm(long formId) {
super.onUpdateForm(formId);
ZSONObject zsonObject = new ZSONObject();
zsonObject.put("temperature", "90°");
FormBindingData formBindingData = new FormBindingData(zsonObject);
// 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变
if (!updateForm(formId, formBindingData)) {
// err process
}
}
(六)服务卡片模板
DevEco Studio中推出了服务卡片模板,分为“Basic”和“Advance”两类
模板名称 | 模板描述 |
---|---|
Grid Pattern(宫格卡片模板) | 宫格卡片模板在大尺寸的卡片上特征较为明显,能够有规律进行布局排列。例如展示多排应用图标,每个热区独立可点击,或展示影视海报等信息,以==凸显图片==为主,描述文本为辅。 |
Image With Infomation(图文卡片模板) | 图文卡片模板主要在于展现图片和一定数量文本的搭配,在这种布局下,图片和文本属于同等重要的信息。在不同尺寸下,图片大小和文本数量会发生一定变化,用于==凸显关键信息==。 |
Immersive Pattern(沉浸布局卡片模板) | 图片内容是更能够吸引用户的展现形式,因此,沉浸式的布局能够拥有更好的代入感和展现形式。相比较图文和宫格类,这种布局在造型上的制约会更小,设计形式上的发挥空间更大,但在==不同设备下的适配==需要注意展示效果。 |
List Pattern(列表卡片模板) | 列表卡片模板是展示信息时的常用界面组件,通常会在列表的左侧或右侧带有图片或点缀元素。这类布局的优势在于可以集中的展示较多信息量,并遵循有序的排列。==常用于新闻类、搜索类应用==,方便用户获取关键的文本信息。 |
Circular Data(环形数据模板) | 环形数据卡片模板主要用于展示自定义内容数据,卡片主体由环形数据图和文本描述组成,用于==凸显关键数据的所占比例==。 |
Immersive Data(沉浸式数据模板) | 此类型卡片是在沉浸式图片上呈现数据信息,可以使用不同的图标搭配信息进行呈现,==强调使用场景与数据之间的关系==,开发者可以发挥图文搭配的优势,创造出独特风格的卡片样式。 |
Immersive Information(沉浸式图文模板) | 沉浸式卡片的==装饰性较强==,能够较好的提升卡片品质感并起到装饰桌面的作用,合理的去布局信息与背景图片之间的空间比例,可以提升用户的个性化使用体验。 |
Multiple Contacts(多个联系人模板) | 多个联系人信息融合在一张卡片中,用户能够==快捷的查找到最近通话的联系人==。也可以通过赋予卡片编辑能力,为用户提供动态可自定义的联系人卡片。 |
Multiple Functions(多功能模板) | 开发者可以==定义此卡片不同热区位置的点击事件==,可以执行某一指令或者不同功能界面的跳转。权衡多个功能之间重要程度,将较大的空间位置留给主要的信息,搭配图片使用,使卡片内容看起来更加丰富。 |
Music Player(音乐播放器卡片模板) | 音乐播放器卡片模板主要用于在桌面展示一个音乐播放的控制界面,通过点击卡片上的对应功能按钮,能够实现对==音乐播放的控制==。 |
Schedule(行程卡片模板) | 行程卡片模板布局主要用于在卡片上==展示行程关键信息==,并带有功能图标,可通过点击功能图标查看详细行程信息。 |
Shortcuts(捷径卡片模板) | 捷径卡片模板布局主要用于在桌面展示==多个快捷功能图标==,在这种布局下,每个热区独立可点击,可快速进入相关功能。但请提供对用户有价值、有服务场景的功能,不要滥用卡片的入口位置。 |
Social Call(通话卡片模板) | 通话卡片模板主要用于在==桌面显示自定义的联系人图片和通话按钮==,在这种场景下,可以直接点击卡片上的通话按钮进行快速呼叫。 |
Standard Image(标准图文模板) | 标准图片类型的卡片使用场景较广,图片和文字信息类型展示基本都可以使用此卡片,但仍然是呈现图片为主,例如展示==二维码信息、乘车路线图、卡片信息预览==等。 |
Standard List(标准列表模板) | 此卡片的优势在于==强调标题信息==,且有序排列,可以明确的呈现主副信息内容。列表类型的卡片需要克制的使用,避免整张卡片全是文字信息。 |
Timer Progress(标准时间进度模板) | 此卡片主要==突出时间数据==,配合标题及正文对数据信息进行解释。可以使用不同的色彩来强调信息的重要性,突出核心的内容。 |
(七)服务卡片部署
服务卡片代码和资源,可与App一起打包成一个entry hap进行部署。同时,服务卡片还可以独立成一个Module,作为HarmonyOS应用的feature进行独立编译和部署。无论是打包部署还是独立部署,两种方式构建的服务卡片都可在 AGC和HAG市场上架和更新。
调试安装时,需要和Entry一起安装,参考以下命令行:
二、【张荣超老师】鸿蒙卡片开发超细致总结
原文出处:【张荣超老师】鸿蒙卡片开发超细致总结
(一)什么是卡片
对于卡片的理解,补充:卡片是应用内页面的展现形式,将页面的重要信息或者操作前置到卡片上,以达到服务直达、==减少体验层级==的目的。
(二)卡片的数量和尺寸
对于卡片的补充:可以在config.json中为每个Page Ability配置0 ~ 16个,而配置的每个卡片可以有1 ~ 4个尺寸,因此,每个Page Ability对应的卡片数是0 ~ 64。
(三)卡片与原子化服务
- 卡片、原子化服务和服务中心的关系
工程的类型是”原子化服务”
创建工程之后就会有卡片模板
由于在创建工程时选择了”在服务中心进行展示”,因此,打开服务中心,就看到了相应的入口卡片
(四)卡片的整体框架
- 简化了整体框架
卡片使用方是桌面或服务中心。之所以将两者称之为卡片使用方,是因为用户通过桌面或服务中心来使用卡片。
卡片管理服务,他是卡片的大管家,是卡片提供方和卡片使用方的中介和桥梁。
❶ timer事件会通知卡片管理服务;
❷ 卡片管理服务会去卡片提供方的对象管理模块中找到对应的卡片提供方;
❸ 卡片提供方回调卡片的生命周期刷新方法;
❹ 卡片提供方将刷新数据返回给卡片管理服务;
❺ 卡片管理服务根据卡片名称查找卡片使用方;
❻ 卡片管理服务刷新卡片使用方的卡片。
(五) 使用JS开发卡片
对各个方法做了具体的讲解,跟第一篇文章讲的是大同小异的。
会在大致了解完后单独一章进行练习
5.1 使用模板创建卡片
5.2 卡片的初始化
5.3 卡片的定点/定时刷新
5.4 卡片的跳转事件
5.5 卡片的消息事件
(六)使用Java开发卡片
因为根据卡片的特征,和一些文章的阅读,发现用JS进行开发更好,所以JAVA部分的文章就进行了阅读,并没有做笔记
(七)开发卡片到底该使用JS还是使用Java
三、对于一些资料的总结
- 对于卡片开发了解和入门(前者为直播课、后者为文章)
【开发教程】一文看懂HarmonyOS服务卡片运行原理和开发方法
- 三个现有我觉得比较有意思的卡片
第一个是华为官方网站上的开发案例
第二个是一种图片的体验比较有时代意义
第三个是车来了的卡片比较有实际意义
- 下面这个是使用JAVA开发卡片
四、卡片的创建、分析实际操作
(一)【卡片的创建】
- previewer自带的工程模板
previewer的三种方法
- View-Tool Windows-Previewer
- 快捷键Alt+3
- 左下角的小窗口-previewer
显示的是:今日美食推荐
(二)【卡片的分析】
关于自带工程模板Widget的源码分析:
//index.css,因为css文件比较冗长且其功能是布局的控制,横线还是纵向呀,图片大小呀,间距呀之类的,就不说弄出来了
//index.xml <div class="container"> <stack> <div class="container-img"> <image src="/common/ic_default_image@3x.png" class="bg-img"></image> </div> <div class="container-inner"> <text class="title">{{ $t('strings.title') }}</text> <text class="detail_text" onclick="routerEvent">{{ $t('strings.detail') }}</text> </div> </stack> </div>
- 一个container大容器里有一个Stack堆叠器,堆叠器里面有三个组件,一个图片+两个文本
//index.json
{
"data": {
"title": "Title",
"detail": "Text",
"iconTitle": "Picture"
},
"actions": {
"routerEvent": {
"action": "router",
"bundleName": "com.example.mysevercard",
"abilityName": "com.example.mysevercard.MainAbility",
"params": {
"message": "add detail"
}
}
},
}
-
data数据绑定,routerEvent设置跳转事件,如果是router(路由器)事件action属性值应设置为router,bundleName (捆绑名称)和abilityName见下图。
-
params与massage见下图
当点击组件触发message事件时,卡片应用的onTriggerFormEvent方法被触发,params属性的值将作为参数被传入,解析使用即可。
五、计步器JS卡片开发样例
原文出处:计步器JS卡片开发样例
(一)代码结构解读
对象关系映射型数据库的使用,卡片的创建、更新、删除,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.js:包含页面默认值以及相关方法。
- card2X4:存放2*4卡片页面的资源文件,目录结构同card2X2。
- card2X2:存放2*2卡片页面的资源文件。
(二)配置文件
-
配置config.json文件中forms模块
//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 form card", "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 form card", "colorMode": "auto", "type": "JS", "supportDimensions": [ "2*4" ], "updateEnabled": true, "updateDuration": 1 } ]
- 对照图
- 配置config.json中的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" ], "when": "inuse" } } ]
关于reqPermissions是在哪一个内部结构的问题。经过开发文档的查询,rePermissions是属于module的。
(三)开发卡片布局
由于2 * 2布局内容相对简单,下面以2 * 4布局为例,进行详细介绍。整个==2*4卡片==展示的内容分为左右两个部分,其中左边内容从上到下分别显示描述、行走的里程、步数,主要以文字的方式展示;右边从上到下分别显示进度条(步数进度百分比)和近四天步数的线型图,主要以图表的方式展示。
- hml代码示例如下:
//hml代码<div class="card_root_layout" on:click="routerEvent"> <div class="div_basic_container"> <!-- 左侧布局 --> <div class="left_items"> <!-- 左侧主要以文字方式显示步数,开发者可自行编写 --> </div> <!-- 右侧布局 --> <div class="right_items"> <!-- 进度条部分 --> <div class="progress"> <!-- 进度值 --> <div class="progressText"> <text id="progressValue" class="text">{{ percent }}</text> <text class="text">%</text> </div> <div class="progressDiv"> <!-- 进度条 --> <progress type="horizontal" id="progress" percent="{{ percent }}"></progress> </div> </div> <!-- 线型图布局 --> <div class="chartArea"> <chart type="line" id="chart" datasets="{{ datasets }}" options="{{ options }}"></chart> </div> </div> </div></div>
- css文件中主要配置各组件的样式,代码示例如下:
.card_root_layout { width: 100%; height: 100%; position: relative; } .div_basic_container { background-image: url('common/backgroundImg1.JPG'); background-size: 100% 100%; background-repeat: no-repeat; position: absolute; top: 0px; left: 0px; flex-direction: row; width: 100%; height: 100%; } .left_items { width: 40%; height: 100%; align-content: flex-start; flex-direction: column; position: absolute; left: 20; } .right_items { align-content: flex-start; width: 50%; height: 100%; position: absolute; right: 20px; } #stepImg { align-content: center; margin-top: 10px; width: 30px; height: 30px; } #chart { font-size: 10px; lineColor: #7CFC00; } .chartArea { justify-content: flex-end; width: 100%; height: 50%; position: absolute; bottom: 10px; } .text { font-size: 20px; font-family: SourceHanSansSC-Regular; color: #CDCACA; } .progress { width: 100%; height: 40%; flex-direction: row; margin-top: 18px; align-content: flex-start; } .progressDiv { flex-direction: row; } #progress { position: absolute; width: 80%; stroke-width: 20px; background-color: #CDCACA; color: #324846; margin-top: 2px; } .progressText { flex-direction: row; width: 40px; }
//js文件中配置了hml中需要用到的默认数据和action配置,代码示例如下:
export default { 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: 50, 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。
==背景图片开发者可自行准备。==
(四)创建卡片
本篇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 * * @param 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==的时候,需要向用户申请计步器传感器的权限,示例代码如下:
@Override 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); }
(五)更新卡片
-
创建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]); } }; // 获取计步器对象 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"); } } }
- 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;
}
(六)代码运行
- 用户指南 • 下载此项目 • 打开HUAWEI DevEco Studio,单击File> Open选择此StepsCard • 单击Build> Build App(s)/Hap(s)>Build Debug Hap(s)以编译hap软件包 • 单击Run> Run 'entry’以运行hap包
(七)总结此项目
- 教程中widget-page.index文件下有js文件,但现行DevEco没有js!只有json,所以有很多action都要自己写
- 根据这个案例也确实向该案例中最后的恭喜一样学会了:
- JS卡片开发部分接口的使用。
- 卡片开发如何去配置config.json文件。
- JS progress组件的使用。
- JS chart组件的使用。
- 自身需要加强java和js的学习,
六、一点的想法
- 关于鸿蒙服务卡片的想法:
JS开发更适合
卡片不宜过多
远程虚拟机真的慢
微机也可以添加按钮了
工程模板太多了暂时没有办法逐一体验
工程创建的流程和运行的机制都已经熟悉
老版本与新版本DevEco对于Wigget的JS开发有不一样的地方(只有json没有JS)
感觉很对应广大人民的生活需求,适合于生活中各个场景,原子化+卡片的前景值得期待!
补充:从开发到上架需要官方给的规范,开发者务必遵守。不过这不影响原子化服务卡片的体验
-
看了很多博主的原子化服务卡片开发!接下来的时间,准备自己做一个原创的服务卡片出来!已经有想法了!感觉很棒哈哈哈!
-
再就是今天2021年7月13日23:40:58 听了李洋老师的直播课,更加明确了原子化服务卡片的规范和UX的基本要求。也对鸿蒙更加有了信心!
-
总结一下本文:
- 总结鸿蒙官方博主的两篇,提取关键,进行对比。进行实际查询,部分实操,阐述了自己的观点。
- 对官方的Codelab给的计步器的JS开发的项目进行实际配置和运行。并阐述了自己的观点。
- 对鸿蒙原子化服务卡片有了初步体验,并阐述了自己的观点。
跟着楼主的文章走一遍,自己的理解也深了不少。
老师,我想在代码里主动的添加或删除 自己的服务中心中的卡片。不知道有啥方法可以实现