鸿蒙HarmonyOS服务卡片实战

引言

在现代开发中,服务卡片是不可或缺的一部分,比如音乐,天气类等应用,官网的介绍中写道:卡片让您便捷地预览服务信息,例如查看天气或日历日程等内容。您可将卡片添加到屏幕上,让这类信息触手可及。您还可按喜好选取不同样式和排列方式,打造个性化桌面。

大体思路

  1. 创建服务卡片
  2. 应用与服务卡片的交互
  3. 刷新卡片内容
  4. 应用端主动更新卡片信息

创建服务卡片

首先,我们需要打开Edit Configurations,选中Deploy Multi Hap,将DEploy Muliti Hap Packages 的框打上勾

然后应用就可以愉快的进行服务卡片的开发了:

创建步骤:选中Entry,new,Service Widget

在弹出框中选选择想要创建的服务卡片类型,本文中选中默认的文本卡片

创建完成后除了serviceCard页面还会生成一个EntryFormAbility文件,和EntryAbility不同的是,EntryFormAbility继承了FormExtensionAbility,而EntryAbility继承了UIAbility。可以理解他们是2个进程。

创建完服务卡片,先运行起来后效果:

我们可以观察到,创建的服务卡片,在点击卡片后有如下代码

postCardAction(this, {action: this.ACTION_TYPE,abilityName: this.ABILITY_NAME,params: {message: this.MESSAGE}});

postCardAction是给卡片添加意图,this代表当前卡片,action是类型,abilityName是打开哪个Ability,params是需要给应用进程传递的数据(服务卡片在另外一个进程),点击完服务卡片后发现,跳转到了应用首页,那能否跳转到指定页面呢?

应用与服务卡片的交互

在卡片上添加2 个按钮,去掉原来的整个卡片的onClick,添加2个按钮

Button('首页').onClick(()=>{postCardAction(this,{'action':'router','abilityName':'EntryAbility','params':{targetPage:"Index"},})})
Button('我的').onClick(()=>{postCardAction(this,{'action':'router','abilityName':'EntryAbility','params':{targetPage:"MinePage"},})})}

在点击按钮的时候把我们需要跳转的page名称传给应用,那如何接受这targetPage呢?

我们在EntryAbility里看到很多空方法,其实就是Ability的生命周期,有过原生安卓开发经验的,可以将它理解为application的生命周期回调函数。

//要访问的页面
let selectPage =''
export default class EntryAbility extends UIAbility {onCreate(want, launchParam) {//启动程序的生命周期if(want.parameters.params!==undefined){let params = JSON.parse(want.parameters.params);console.log('onCreate router targetPage:'+params.targetPage);selectPage = params.targetPage;}}onWindowStageCreate(windowStage: window.WindowStage) {//默认进入首页let target = selectPage || 'Index';target = 'pages/'+target;// Main window is created, set main page for this abilityhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');//存储窗口实例windowStage.loadContent(target, (err, data) => {if (err.code) {hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');return;}hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');});}}

我们可以设置一个参数selectPage,通过onCreate方法里的want.parameters.params获取到我们上面点击服务卡片的时候传递的params对象。通过JSON解析后拿到targetPage,在onWindowStageCreate中组合拼装一下路由地址,currentWindow.loadContent(tatgetPage)执行后,有值就根据参数拼接rute地址跳转,否则就默认首页。

运行后当kill掉应用的进程,能够正常跳转到首页和我的页面,但是有个问题,如果只是把应用推到后台,再点击我的,发现无效无法跳转,这是什么原因呢?有些眼尖的同学可能就发现了,我们接受和打开的方法都放在了onCreate里。只有创建的时候才会接收到,如果Ability已经创建了,就无法接收到卡片的通知回调。

那么改如何做呢?答案是通过onNewWant函数,这个函数的意思是,如果UIAbility已在后台运行,在收到Router事件后会触发,改写后的代码:

//要访问的页面
let selectPage =''
//当前的windown对象
let currentWindow = null
export default class EntryAbility extends UIAbility {onCreate(want, launchParam) {//启动程序的生命周期if(want.parameters.params!==undefined){let params = JSON.parse(want.parameters.params);console.log('onCreate router targetPage:'+params.targetPage);selectPage = params.targetPage;}}//如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期的回调onNewWant(want, launchParam){if(want.parameters.params!==undefined){let params = JSON.parse(want.parameters.params);console.log('onCreate router targetPage:'+params.targetPage);selectPage = params.targetPage;}//进入对应页面let target = selectPage || 'AddressPage';target = 'pages/'+target;currentWindow.loadContent(target, (err, data) => {if (err.code) {hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');return;}hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');});}onWindowStageCreate(windowStage: window.WindowStage) {//默认进入首页let target = selectPage || 'AddressPage';target = 'pages/'+target;// Main window is created, set main page for this abilityhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');//存储窗口实例currentWindow = windowStagecurrentWindow.loadContent(target, (err, data) => {if (err.code) {hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');return;}hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');});}}

创建了一个变量currentWindow用以存储onWindowStageCreate中的windown对象,然后在onNewWant中获取相应的信息,再执行跳转。

刷新卡片内容

到目前为止卡片上的信息都是静态展示的,那我们如何更新卡片上的信息呢?

首先需要在EntryFormAbility的onAddForm里创建一对要更新的数据,通过formBindingData.createFormBindingData创建一个FormBindingData的实力返回回去

 onAddForm(want: Want) {let formData = {title : 'jay'}return formBindingData.createFormBindingData(formData);}

此时卡片作为接收方需要通过LocalStorage获取传递的数据,具体做法如下:

let storage = new LocalStorage();
@Entry(storage)
@Component
struct MessageCardCard {
@LocalStorageProp('title') title:string='zhoujielun'build() {Row() {Column({space:20}) {Text('服务卡片:'+this.title).fontSize($r('app.float.font_size')).fontWeight(FontWeight.Medium).fontColor($r('app.color.item_title_font'))}.width('100%')}.height('100%  ')}
}

创建一个LocalStorage实例,然后在Entry中传入storage,在通过@LocalStorageProp('title') title:string='zhoujielun'读取数据,需要注意:1必须要有默认值,2LocalStorageProp包裹的字符串名需要和onAddForm函数中的formData的key保持一致,才能正确接受到数据的更新。

运行后发现,卡片上的数据已经变成了jay

卡片方主动更新服务卡片信息

依然是调用postCardAction 方法,action改为message

   Button('更新数据') .onClick(()=>{postCardAction(this,{action:'message',params:{}})})

这时候就需要用到EntryFormAbility的onFormEvent函数了

 //对应卡片的message事件onFormEvent(formId: string, message: string) {let fromData={'title':'黑色毛衣'};//构建传入的数据对象let fromInfo  = formBindingData.createFormBindingData(fromData)//指定卡片id,传入最新的数据formProvider.updateForm(formId,fromInfo)}

此时运行后点击卡片上的更新数据的按钮,数据已经更新过来了

应用方更新服务卡片信息

因为应用方无法知道服务卡片的formId,那么我就要在服务卡片创建的时候把fromId存起来,然后统一发送事件更新信息。

// @ts-nocheck
import formInfo from '@ohos.app.form.formInfo';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import Want from '@ohos.app.ability.Want';
import formProvider from '@ohos.app.form.formProvider';
import dataPreferences from '@ohos.data.preferences';
export default class EntryFormAbility extends FormExtensionAbility {onAddForm(want: Want) {//在want中取出卡片的唯一标识formIdlet formId:string = want.parameters[formInfo.FormParam.IDENTITY_KEY];//把formId保存到首选项中;(async ()=>{let pre = await dataPreferences.getPreferences(this.context,'formIds');// @ts-ignorelet formIds:string = await pre.get('formIds','[]');let formIdsArray = JSON.parse(formIds);formIdsArray.push(formId);await pre.put('formIds',JSON.stringify(formIdsArray));await pre.flush();})()let formData = {title : 'jay'}return formBindingData.createFormBindingData(formData);}onCastToNormalForm(formId: string) {// Called when the form provider is notified that a temporary form is successfully// converted to a normal form.}onUpdateForm(formId: string) {// Called to notify the form provider to update a specified form.}onChangeFormVisibility(newStatus: Record<string, number>) {// Called when the form provider receives form events from the system.}//对应卡片的message事件onFormEvent(formId: string, message: string) {// Called when a specified message event defined by the form provider is triggered.let fromData={'title':'黑色毛衣'};//构建传入的数据对象let fromInfo  = formBindingData.createFormBindingData(fromData)//指定卡片id,传入最新的数据formProvider.updateForm(formId,fromInfo)}onRemoveForm(formId: string) {// Called to notify the form provider that a specified form has been destroyed.}onAcquireFormState(want: Want) {// Called to return a {@link FormState} object.return formInfo.FormState.READY;}
};

在Page页面根据取出首选项中的formId,循环调用updateForm更新数据:

import router from '@ohos.router'
import dataPreferences from '@ohos.data.preferences';
import formBindingData from '@ohos.app.form.formBindingData';
import formProvider from '@ohos.app.form.formProvider';@Entry
@Component
struct AddressPage {store: any = {}@State city: string = ''t:number = 1async aboutToAppear() {setInterval(async ()=>{let formData={time: ++this.t};//读取首选项中的id集合let pre = await dataPreferences.getPreferences(getContext(this),'formIds');// @ts-ignorelet formIds:string = await pre.get('formIds','[]');let formMsg = formBindingData.createFormBindingData(formData)//遍历集合,将新数据传到当前应用的所有卡片JSON.parse(formIds).forEach((currentFormId)=>{formProvider.updateForm(currentFormId,formMsg)});},1000)}build() {Row() {Column() {Text('首页')Button('更新卡片').onClick(async ()=>{let formData={title:'听妈妈的话'};//读取首选项中的id集合let pre = await dataPreferences.getPreferences(getContext(this),'formIds');// @ts-ignorelet formIds:string = await pre.get('formIds','[]');let formMsg = formBindingData.createFormBindingData(formData)//遍历集合,将新数据传到当前应用的所有卡片JSON.parse(formIds).forEach((currentFormId)=>{formProvider.updateForm(currentFormId,formMsg)});})}.width('100%')}.height('100%')}
}

上面的例子中,我直接通过定时间,每隔一秒更新卡片上的数字,模拟充电或者歌曲播放进度效果,运行后:

总结

服务卡片的创建交互,以及刷新卡片信息的内容到此结束,相信应该能满足大部分业务需求,差别无非就是在UI上。

今天是HDC2024华为开发者大会,谨借此篇博客提前庆祝纯血鸿蒙的到来,华为加油,HarmonyOS 加油💪🏻💪🏻💪🏻

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/858498.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[C++][设计模式][观察者模式]详细讲解

目录 1.动机2.模式定义3.要点总结4.代码感受1.代码一1.FileSplitter.cpp2.MainForm.cpp 2.代码二1.FileSplitter.cpp2.MainForm.cpp 1.动机 在软件构建过程中&#xff0c;需要为某些对象建立一种“通知依赖关系” 一个对象(目标对象)的状态发生改变&#xff0c;所有的依赖对象…

2024.6.22刷题记录-力扣周赛402跟练记录(未完)

目录 一、跟练视频 二、3184. 构成整天的下标对数目 I 暴力 三、3185. 构成整天的下标对数目 II 不会&#xff0c;来自视频。 一、跟练视频 【值域打家劫舍 树状数组【力扣周赛 402】-哔哩哔哩】 https://b23.tv/iDc49pt 二、3184. 构成整天的下标对数目 I 暴力 class …

C语言 将“China”译成密码

将“China”译成密码&#xff0c;密码规律是&#xff1a;用原来的字母后面的第4个字母代替原来的字母。例如&#xff0c;字母“A”后面的第4个字母是“E”&#xff0c;用“E”代替“A”。因此&#xff0c;“China”应译为“Glmre”。编译程序用付赋初值的方法使c1&#xff0c;c…

Clickhouse 的性能优化实践总结

文章目录 前言性能优化的原则数据结构优化内存优化磁盘优化网络优化CPU优化查询优化数据迁移优化 前言 ClickHouse是一个性能很强的OLAP数据库&#xff0c;性能强是建立在专业运维之上的&#xff0c;需要专业运维人员依据不同的业务需求对ClickHouse进行有针对性的优化。同一批…

9、PHP 实现调整数组顺序使奇数位于偶数前面

题目&#xff1a; 调整数组顺序使奇数位于偶数前面 描述&#xff1a; 输入一个整数数组&#xff0c;实现一个函数来调整该数组中数字的顺序&#xff0c;使得所有的奇数位于数组的前半部分&#xff0c; 所有的偶数位于位于数组的后半部分&#xff0c;并保证奇数和奇数&#xff…

基于深度学习的旋转包围盒检测

基于深度学习的旋转包围盒检测 旋转包围盒检测是一种高级目标检测方法&#xff0c;旨在识别图像中目标的精确位置和方向。与传统的轴对齐矩形框&#xff08;水平包围盒&#xff09;不同&#xff0c;旋转包围盒&#xff08;Rotated Bounding Box, RBB&#xff09;允许检测框随目…

速盾:视频cdn和网站cdn的区别

在互联网的发展过程中&#xff0c;视频的重要性越来越被人们所重视&#xff0c;视频内容的传播和观看需求也越来越大。为了提供更好的用户体验和满足视频内容的高负载需求&#xff0c;加快视频的加载速度&#xff0c;视频CDN&#xff08;Content Delivery Network&#xff0c;内…

一、企业级架构设计-archimate基础概念

目录 一、标准 二、实现工具 1、Archimate 1、Archimate 基本概念 1、通用元模型 2、结构关系 3、依赖关系 1、服务关系 2、访问关系 3、影响关系 1、影响方式 2、概念 3、关系线 4、案例 4、关联关系 4、动态、节点和其他关系 1、时间或因果关系 2、信息流 …

缓存层持久化

** 读缓存** 分布式缓存 先将所有的缓存数据集中存储在同一个地方&#xff0c;而非重复保存到各个服务器节点中&#xff0c;然后所有的服务器节点都从这个地方读取数据 使用MongoDB的公司最少&#xff0c;目前&#xff0c;Redis比Memcached更流行&#xff1a; &#xff08;1&…

MySQL——Insert语句详解

语法&#xff1a; INSERT INTO 表名&#xff08;[字段名1&#xff0c;字段名2&#xff0c;字段名3]&#xff09;VALUES(值1),(值2),(值3), 注意事项&#xff1a; 字段和字段之间&#xff0c;使用英文逗号隔开 字段是可以省略的&#xff0c;但是后面的值必须一一对应&…

采集数据类型和方法

采集的类型: 命名实体&#xff1a;命名实体包括人名、地点、组织、时间表达式、数量等。这些实体通常对理解和分类文本内容至关重要。 关键词和短语&#xff1a;这些是文本中的核心概念&#xff0c;可能代表了主题或中心思想。 句子或段落&#xff1a;根据需求&#xff0c;可…

【操作系统】信号Signal超详解|捕捉函数

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​ 如何触发信号 …

【深度学习】Position Wise 到底是什么,有什么用

1. 遇到的问题 今天在看 Transformers 的前生今世 的时候&#xff0c;又一次看到了 Position Wise &#xff0c;经常看到但老是一知半解&#xff0c;故索性查了一下&#xff0c;发现网上的都没怎么细讲其缘由&#xff0c;有点差强人意&#xff0c;于是我又用咱们最喜欢的 GPT-…

Django 路由系统详解

Django 路由系统详解 引言 Django 是一个高级 Python Web 框架,它鼓励快速开发和干净、实用的设计。在 Django 中,路由系统是其核心组件之一,负责将用户的请求映射到相应的视图函数或类。本文将深入探讨 Django 的路由系统,包括其工作原理、配置方式以及高级功能。 目录…

vue2和vue3数据代理的区别

前言&#xff1a; vue2 的双向数据绑定是利⽤ES5的⼀个 API &#xff0c;Object.defineProperty( )对数据进行劫持结合发布订阅模式的方式来实现的。 vue3 中使⽤了 ES6的Proxy代理对象&#xff0c;通过 reactive() 函数给每⼀个对象都包⼀层Proxy&#xff0c;通过 Proxy监听属…

【剖析】为什么说RBF神经网络的误差为0

本文来自《老饼讲解-BP神经网络》https://www.bbbdata.com/ 机器学习中的模型非常的多&#xff0c;但如果要问有没有这样的一个模型&#xff0c;它的训练误差为0&#xff0c;那么就非RBF神经网络莫属了&#xff01;下面我们来聊聊&#xff0c;为什么RBF神经网络的训练误差为0。…

WDF驱动开发-特定于KMDF的技术(一)

这部分的技术是一些零散的记录知识点&#xff0c;它们主要是在WDF框架中特定于KMDF的部分。 将内核模式驱动程序框架和非 PnP 驱动程序配合使用 如果要为不支持 即插即用 (PnP) 的设备编写驱动程序&#xff0c;则驱动程序必须&#xff1a; 在 WDF_DRIVER_CONFIG 结构的 Driv…

了解请求参数与响应参数的区别:初学者指南

在 Web 的开发领域&#xff0c;无论你是前端开发还是后端开发人员&#xff0c;把握请求与响应参数的核心差异是极其重要的。这些参数在客户端和服务器之间的互动中扮演着关键角色。 请求参数的定义及类别 定义 当客户端向服务器提交信息时所使用的数据被称为请求参数。这些参…

【Docker】Docker下载安装_使用阿里云加速配置

1、下载安装 1.1前提条件 安装环境&#xff1a; 目前&#xff0c;CentOS 仅发行版本中的内核支持 Docker。Docker 运行在 CentOS 7 上&#xff0c;要求系统为64位、系统内核版本为 3.10 以上。Docker 运行在 CentOS-6.5 或更高的版本的 CentOS 上&#xff0c;要求系统为64位…

STM32上实现spwm调制原理分析

在STM32微控制器上实现SPWM&#xff08;正弦脉宽调制&#xff0c;Sinusoidal Pulse Width Modulation&#xff09;调制的核心是利用高频载波&#xff08;三角波&#xff09;与低频基波&#xff08;正弦波&#xff09;作比较得出。 那么在STM32里三角波和正弦波分别是什么&…