《HarmonyOS实战—万字长文读懂服务卡片开发过程》

Posted 宾有为

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《HarmonyOS实战—万字长文读懂服务卡片开发过程》相关的知识,希望对你有一定的参考价值。


【本文正在参与“有奖征文| HarmonyOS征文大赛”活动】


目录

服务卡片是什么?

服务卡片是HarmonyOS原子化服务的载体,是FA(Feature Ability)的一种界面展示形式,将FA(Feature Ability)重要信息或操作前置到卡片,以此达到服务直达、减少层级体验的目的。在服务中心可以轻松获取!随时分享!无需下载!无需安装!一步到位获取各种服务。

本文将讲解服务卡片的使用,至于创建服务卡片这些官方文档已写了无人能超越的服务卡片入门文档,不再多说,不了解可查看文档:如何开发服务卡片

服务卡片的类型

服务卡片的种类有16种,若服务卡片不足16种,便是创建项目时Compatible API Version版本选小了,改大点好啦。

接下来我们看看这16种服务卡片分别是什么吧!

模板名称模板描述开发语言
Grid Pattern(宫格卡片模板)宫格卡片模板在大尺寸的卡片上特征较为明显,能够有规律进行布局排列。例如展示多排应用图标,每个热区独立可点击,或展示影视海报等信息,以凸显图片为主,描述文本为辅。Java、JS
Image With Infomation(图文卡片模板)图文卡片模板主要在于展现图片和一定数量文本的搭配,在这种布局下,图片和文本属于同等重要的信息。在不同尺寸下,图片大小和文本数量会发生一定变化,用于凸显关键信息。Java、JS
Immersive Pattern(沉浸布局卡片模板)图片内容是更能够吸引用户的展现形式,因此,沉浸式的布局能够拥有更好的代入感和展现形式。相比较图文和宫格类,这种布局在造型上的制约会更小,设计形式上的发挥空间更大,但在不同设备下的适配需要注意展示效果。Java、JS
List Pattern(列表卡片模板)列表卡片模板是展示信息时的常用界面组件,通常会在列表的左侧或右侧带有图片或点缀元素。这类布局的优势在于可以集中的展示较多信息量,并遵循有序的排列。常用于新闻类、搜索类应用,方便用户获取关键的文本信息。Java、JS
Circular Data(环形数据模板)环形数据卡片模板主要用于展示自定义内容数据,卡片主体由环形数据图和文本描述组成,用于凸显关键数据的所占比例。JS
Immersive Data(沉浸式数据模板)此类型卡片是在沉浸式图片上呈现数据信息,可以使用不同的图标搭配信息进行呈现,强调使用场景与数据之间的关系,开发者可以发挥图文搭配的优势,创造出独特风格的卡片样式。JS
Immersive Information(沉浸式图文模板)沉浸式卡片的装饰性较强,能够较好的提升卡片品质感并起到装饰桌面的作用,合理的去布局信息与背景图片之间的空间比例,可以提升用户的个性化使用体验。JS
Multiple Contacts(多个联系人模板)多个联系人信息融合在一张卡片中,用户能够快捷的查找到最近通话的联系人。也可以通过赋予卡片编辑能力,为用户提供动态可自定义的联系人卡片。JS
Multiple Functions(多功能模板)开发者可以定义此卡片不同热区位置的点击事件,可以执行某一指令或者不同功能界面的跳转。权衡多个功能之间重要程度,将较大的空间位置留给主要的信息,搭配图片使用,使卡片内容看起来更加丰富。JS
Music Player(音乐播放器卡片模板)音乐播放器卡片模板主要用于在桌面展示一个音乐播放的控制界面,通过点击卡片上的对应功能按钮,能够实现对音乐播放的控制。JS
Schedule(行程卡片模板)行程卡片模板布局主要用于在卡片上展示行程关键信息,并带有功能图标,可通过点击功能图标查看详细行程信息。JS
Shortcuts(捷径卡片模板)捷径卡片模板布局主要用于在桌面展示多个快捷功能图标,在这种布局下,每个热区独立可点击,可快速进入相关功能。但请提供对用户有价值、有服务场景的功能,不要滥用卡片的入口位置。JS
Social Call(通话卡片模板)通话卡片模板主要用于在桌面显示自定义的联系人图片和通话按钮,在这种场景下,可以直接点击卡片上的通话按钮进行快速呼叫。JS
Standard Image(标准图文模板)标准图片类型的卡片使用场景较广,图片和文字信息类型展示基本都可以使用此卡片,但仍然是呈现图片为主,例如展示二维码信息、乘车路线图、卡片信息预览等。JS
Standard List(标准列表模板)此卡片的优势在于强调标题信息,且有序排列,可以明确的呈现主副信息内容。列表类型的卡片需要克制的使用,避免整张卡片全是文字信息。JS
Timer Progress(标准时间进度模板)此卡片主要突出时间数据,配合标题及正文对数据信息进行解释。可以使用不同的色彩来强调信息的重要性,突出核心的内容。JS

为了让大家避免我所遇到的一些开发到一半发现功能无法使用Java完成的囧境,贴上官方的卡片选型指导,供大家选择正确的开发方式。

场景Java卡片JS卡片
实时刷新Java使用ComponentProvider做实时刷新代价比较大JS可以做到端侧刷新,但是需要定制化组件
开发方式Java UI在卡片提供方需要同时对数据和组件进行处理,生成ComponentProvider远端渲染JS卡片在使用方加载渲染,提供方只要处理数据、组件和逻辑分离
组件支持Text、Image、DirectionalLayout、PositionLayout、DependentLayoutdiv、list、list-item、swiper、stack、image、text、span、progress、button(定制:chart 、clock、calendar)
卡片动效不支持暂不开放
阴影模糊不支持支持
动态适应布局不支持支持
自定义卡片跳转页面不支持支持

选好类型了?项目建起来了?好,下一步,是时候创建卡片了!

创建服务卡片

新建项目后,选中src文件夹→点击鼠标右键→New→Service Widget


List Pattern是16种服务卡片中最基本的服务卡片之一,这里使用List Pattern进行讲解

这里 ↓ 有个BUG,Description输入框不能输入中文,在哪改成中文的Description后面会讲到


紧接着便会生成这些文件

点击运行,长按运行后安装的App,选择服务卡片。看,它是长这样的

修改服务卡片

上图中, entry_MainAbility 是指服务卡片的App名称,This is a service widget 是服务卡片的 Description,这里两个参数可在 config.json 文件夹进行修改。

打开config.json,找到创建服务卡片时起的名字,description 值变了,对服务卡片的描述也就变了

entry_MainAbility 改哪里呢?entry_MainAbility 改的是包含了entitiesactions key对象的 label


按住ctrl+鼠标左键点击label会出现三个文件,手机系统会根据手机系统当前在使用的语言选择使用哪一个文件里面的值,我用的系统是中文版,且不考虑开发英文版,因此只改最下面那个即可。


这样一波操作,效果就出来了。

那服务卡片里的内容怎么改呢?

当你创建完一个服务卡片后,在layout文件夹下会根据你创建的布局尺寸生成对应尺寸的xml文件,我们就在这里开始 改-布-局-数-据

修改布局数据需要在创建服务卡片时生成的WidgetAbility类上修改,在这个类上面的操作便是对服务卡片数据的操作,在官方文档 卡片开发说明Java卡片开发指导 有提到相关接口、方法

类名接口名描述
Ability
ProviderFormInfo onCreateForm(Intent intent)卡片提供方接收创建卡片通知接口。
void onUpdateForm(long formId)卡片提供方接收更新卡片通知接口。
void onDeleteForm(long formId)卡片提供方接收删除卡片通知接口。
void onTriggerFormEvent(long formId, String message)卡片提供方处理卡片事件接口(JS卡片使用)。
boolean updateForm(long formId, ComponentProvider component)卡片提供方主动更新卡片(Java卡片使用)。
boolean updateForm(long formId, FormBindingData formBindingData)卡片提供方主动更新卡片(JS卡片使用),仅更新formBindingData中携带的信息,卡片中其余信息保持不变。
void onCastTempForm(long formId)卡片提供方接收临时卡片转常态卡片通知。
void onEventNotify(Map<Long, Integer> formEvents)卡片提供方接收到事件通知,其中Ability.FORM_VISIBLE表示卡片可见通知,Ability.FORM_INVISIBLE表示卡片不可见通知。
ProviderFormInfoProviderFormInfo(int resId, Context context)Java卡片返回对象构造函数。
ProviderFormInfo()JS卡片返回对象构造函数。
void mergeActions(ComponentProvider componentProviderActions)在提供方侧调用该接口,将开发者在ComponentProvider中设置的actions配置数据合并到当前对象中。
void setJsBindingData(FormBindingData data)设置JS卡片的内容信息(JS卡片使用)。

在onCreateForm(Intent intent)中,当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用onCreateForm(Intent intent)回调,intent中会带有卡片ID,卡片名称,临时卡片标记和卡片外观规格信息,分别通过AbilitySlice.PARAM_FORM_IDENTITY_KEY、AbilitySlice.PARAM_FORM_NAME_KEY、AbilitySlice.PARAM_FORM_TEMORARY_KEY和AbilitySlice.PARAM_FORM_DIMENSION_KEY按需获取。

看了官方文档的提示,接下来便是我大显身手的时候了

打开WidgetAbility类(创建服务卡片时选择Select Ability / New Ability自动生成),在其onCreateForm方法编写以下代码,即可实现修改服务卡片界面数据

@Override
protected ProviderFormInfo onCreateForm(Intent intent) 
    // 获得卡片ID
    long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
    // 获取卡片名称
    String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
    // 获取卡片外观规格
    int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
    // 根据卡片的名称以及外观规格获取对应的xml布局并构造卡片对象
    ProviderFormInfo formInfo = new ProviderFormInfo(ResourceTable.Layout_form_list_pattern_widget_2_2, this);
    // 设置卡片信息
    ComponentProvider componentProvider = new ComponentProvider();
    componentProvider.setText(ResourceTable.Id_title, "这是标题啊~");
    componentProvider.setText(ResourceTable.Id_title1, "这是item标题1~");
    componentProvider.setText(ResourceTable.Id_title2, "这是item标题2~");
    componentProvider.setText(ResourceTable.Id_content1, "这是content1~");
    componentProvider.setText(ResourceTable.Id_content2, "这是content2~");
    formInfo.mergeActions(componentProvider);
    return formInfo;

代码效果图:

添加到桌面效果图:

看到这里,还没完!写到这里万字都不够,岂能乱起标题欺骗读者?这事我可干不来。

那么,接下来的功能是:点击服务卡片跳转到App一个界面上。

在包名下面创建继承自AbilitySliceWidgetSlice类,重写onStart方法添加super.setUIContent(ResourceTable.Layout_form_list_pattern_widget_2_2);以绑定服务卡片布局,接着,打开WidgetAbility类,在onStart加上super.setMainRoute(WidgetSlice.class.getName());,运行~


 ̄□ ̄||,标题看不懂就算了,界面还错乱

不过没关系,不符合我们的需求就改嘛

打开config.json界面,这次修改的是服务卡片的label喔,可不能搞错了


找到服务卡片的label,没错,就是改这个key里面的内容就可以了!

其中,"forms"模块中的name为卡片名,即在onCreateForm中根据AbilitySlice.PARAM_FORM_NAME_KEY可取到的值。

forms对象的内部结构说明:

属性名称子属性名称含义数据类型是否可缺省
name-表示卡片的类名。字符串最大长度为127字节。字符串
description-表示卡片的描述。取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。字符串最大长度为255字节。字符串可缺省,缺省为空。
isDefault-表示该卡片是否为默认卡片,每个Ability有且只有一个默认卡片。true:默认卡片。false:非默认卡片。布尔值
type-表示卡片的类型。取值范围如下:Java:Java卡片。JS:JS卡片。字符串
colorMode-表示卡片的主题样式,取值范围如下:auto:自适应。dark:深色主题。light:浅色主题。字符串可缺省,缺省值为“auto”。
supportDimensions-表示卡片支持的外观规格,取值范围:1*2:表示1行2列的二宫格。2*2:表示2行2列的四宫格。2*4:表示2行4列的八宫格。4*4:表示4行4列的十六宫格。字符串数组
defaultDimension-表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中。字符串
landscapeLayouts-表示卡片外观规格对应的横向布局文件,与supportDimensions中的规格一一对应。仅当卡片类型为Java卡片时,需要配置该标签。字符串数组
portraitLayouts-表示卡片外观规格对应的竖向布局文件,与supportDimensions中的规格一一对应。仅当卡片类型为Java卡片时,需要配置该标签。字符串数组
updateEnabled-表示卡片是否支持周期性刷新,取值范围:true:表示支持周期性刷新,可以在定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)两种方式任选其一,优先选择定时刷新。false:表示不支持周期性刷新。布尔类型
scheduledUpdateTime-表示卡片的定点刷新的时刻,采用24小时制,精确到分钟。字符串可缺省,缺省值为“0:0”。
updateDuration-表示卡片定时刷新的更新周期,单位为30分钟,取值为自然数。当取值为0时,表示该参数不生效。当取值为正整数N时,表示刷新周期为30*N分钟。数值可缺省,缺省值为“0”。
formConfigAbility-表示卡片的配置跳转链接,采用URI格式。字符串可缺省,缺省值为空。
jsComponentName-表示JS卡片的Component名称。字符串最大长度为127字节。仅当卡片类型为JS卡片时,需要配置该标签。字符串
metaData-表示卡片的自定义信息,包含customizeData数组标签。对象可缺省,缺省值为空。
customizeData-表示自定义的卡片信息。对象数组可缺省,缺省值为空。
customizeDataname表示数据项的键名称。字符串最大长度为255字节。字符串可缺省,缺省值为空。
customizeDatavalue表示数据项的值。字符串最大长度为255字节。字符串可缺省,缺省值为空。

界面的Text为什么会下沉对最底下?而不是展示在 标题在这~ 稍微下面一点的位置呢?来,我们一起看它的xml布局是如何写的。

通过xml布局文件可知,布局的内容被包裹在DependentLayout里面,DirectionalLayout布局又使用了ohos:align_parent_bottom="true"将自己牢牢的吸附在父布局的底部。


想要做出改变,那就得在这里修改,删除ohos:align_parent_bottom="true"并在DirectionalLayout添加以下代码即可:

<!-- 将上边缘与另一个子组件的上边缘对齐 -->
ohos:align_top="$id:title"
ohos:top_margin="30vp"

点击运行,就是这样子咯

看了效果图,心细的你,也许就会发现一件事,文本的值与点击进来前的服务卡片值不对应

点击服务卡片,进入到的界面是绑定了当前xml布局、且继承了AbilitySlice的类(即上面有提到的WidgetSlice类),目前我们还没有在这里进行赋值,展示出来的界面又岂会有数据?

打开WidgetSlice类,在onStart加上以下代码:

((Text)findComponentById(ResourceTable.Id_title)).setText("服务卡片页面标题");
((Text)findComponentById(ResourceTable.Id_title1)).setText("服务卡片页面小标题1");
((Text)findComponentById(ResourceTable.Id_title2)).setText("服务卡片页面小标题2");
((Text)findComponentById(ResourceTable.Id_content1)).setText("服务卡片页面内容1");
((Text)findComponentById(ResourceTable.Id_content2)).setText("服务卡片页面内容2");

添加代码后,就变成这样啦

更新服务卡片数值

根据官方文档的提示去进行更新卡片数据时,出了些小问题,不知道这个onUpdate方法它是何时会去执行,打开父类查看源码却发现还没下载,debug该方法更是无响应。


查看了官方代码示例ClockFACardDemo项目后得知是在onStart时就已经启动了一个定时器,每一秒钟执行一次

既然不知道onUpdateForm方法何时调用,那为何不像代码示例那样做一个定时器,然后更新数据呢?

onCreateForm方法加上定时更新代码:

// 卡片更新定时器,每秒更新一次
Timer timer = new Timer();
final int[] i = 0;
timer.schedule(new TimerTask() 
    @Override
    public void run() 
        onUpdate(formId, i[0]++);
    
,0, 1000L);
/**
 * @param formId 设备id
 * @param frequency 执行次数
 */
private void onUpdate(long formId,int frequency) 
    ComponentProvider componentProvider = new ComponentProvider(ResourceTable.Layout_form_list_pattern_widget_2_2, this);
    // 设置卡片的更新信息
    componentProvider.setText(ResourceTable.Id_title, "数据变了"+frequency+"次");
    componentProvider.setText(ResourceTable.Id_title1, "数据发生了变化~");
    componentProvider.setText(ResourceTable.Id_title2, "数据发生了变化~");
    componentProvider.setText(ResourceTable.Id_content1, "content1数据发生了变化~");
    componentProvider.setText(ResourceTable.Id_content2, "content2数据发生了变化~");
    try 
        updateForm(formId, componentProvider);
     catch (FormException e) 
        e.printStackTrace();
    

这样就大功告成啦!

服务卡片开发注意事项

  • 对于同一个Page ability,在config.json中最多支持配置16张卡片。
  • config.json配置自定义跳转URI的格式如下:ability://单个ability名字。
  • Java开发不支持卡片内动效、阴影模糊、动态适应布局、自定义卡片跳转页面。
  • JS卡片页面与普通FA类似通过hml+css+js开发,但不支持复杂js语法。详情请参考JS API
  • 部分机型受性能限制,不支服务卡片持背景模糊。

本文代码已上传至:HarmonyOS服务卡片开发


【本文正在参与“有奖征文| HarmonyOS征文大赛”活动】


以上是关于《HarmonyOS实战—万字长文读懂服务卡片开发过程》的主要内容,如果未能解决你的问题,请参考以下文章

HarmonyOS 实战——万字分析并学习 JsFACard 项目

腾讯开发工程师写万字长文:读懂微服务编排利器Zeebe,超详细

HarmonyOS 实战——认识服务卡片及运行第一个服务卡片

我的HarmonyOS实战 — 一篇文章讲明白什么是鸿蒙2.0服务卡片

HarmonyOS实战—卡片的样式设计

一文读懂zookeeper--万字长文肝就完了