鸿蒙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 ability
    hilog.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 ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    //存储窗口实例
    currentWindow = windowStage
    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) ?? '');
    });
  }

}

创建了一个变量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中取出卡片的唯一标识formId
    let formId:string = want.parameters[formInfo.FormParam.IDENTITY_KEY];
      //把formId保存到首选项中
    ;(async ()=>{
      let pre = await dataPreferences.getPreferences(this.context,'formIds');
      // @ts-ignore
      let 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 = 1
  async aboutToAppear() {

    setInterval(async ()=>{
      let formData={time: ++this.t};
      //读取首选项中的id集合
      let pre = await dataPreferences.getPreferences(getContext(this),'formIds');
      // @ts-ignore
      let 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-ignore
          let 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 加油💪🏻💪🏻💪🏻

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【大数据】Hadoop学习笔记

基本概念 Hadoop组成 HDFS: Hadoop分布式文件存储系统, 在Haddop中处于底层/核心地位YARN: 分布式通用的集群资源管理系统和任务调度平台, 支撑各种计算引擎执行MapReduce: 第一代分布式计算引擎, 但因为部分原因, 许多企业都不直接使用MapReduce, 但许多底层软件仍然在使用Ma…

SQL Server - ROLLUP、GROUPING、CUBE、GROUPING SET

文章目录 SQL Server - ROLLUP、GROUPING、CUBE、GROUPING SETROLLUP函数GROUPING函数GROUPING SET函数CUBE函数网上例子 写在前面&#xff1a;如果我们想要对分组之后的数据进行类似小计的计算&#xff0c;那么就需要使用到下面的函数 SQL Server - ROLLUP、GROUPING、CUBE、G…

PR模板 | RGB特效视频标题模板Titles | MOGRT

RGB特效视频标题模板mogrt免费下载 4K分辨率&#xff08;38402160&#xff09; 支持任何语言 友好的界面 输入和输出动画 快速渲染 视频教程 免费下载&#xff1a;https://prmuban.com/39055.html 更多pr模板视频素材下载地址&#xff1a;https://prmuban.com

【网络协议】精讲TCP通信原理!图解超赞超详细!!!

亲爱的用户&#xff0c;打开微信&#xff0c;搜索公众号&#xff1a;“风云说通信”&#xff0c;即可免费阅读该文章~~ 目录 1. 建立连接 2. 数据传输 3. 断开连接 4. 抓包分析 前言 TCP 把连接作为最基本的对象&#xff0c;每一条 TCP 连接都有两个端点&#xff0c;这种端…

【Day02】0基础微信小程序入门-学习笔记

文章目录 模板与配置学习目标WXML 模板语法1.数据绑定&#xff08;类似于 Vue2 &#xff09;2. 事件绑定3. 条件渲染4.列表渲染 WXSS模板样式1. rpx尺寸单位2.样式导入3. 全局样式和局部样式 全局配置1. window2. tabBar 页面配置网络数据请求总结 持续更新~ 模板与配置 学习目…

《第一行代码 第3版》学习笔记——第十一章 网络技术

1 webview用法 class MainActivity : ComponentActivity() {SuppressLint("SetJavaScriptEnabled")override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {NetWorkDemoTheme {// A surface container using the bac…

GandCrab5.2勒索病毒复现

GandCrab第一代勒索病毒首次出现于2018年1月&#xff0c;后面经历了五个大版本的更新迭代&#xff0c;该系列病毒特征是采用RSAAES加密算法&#xff0c;从算法上分析解密难度较大&#xff0c;会将系统中的大部分文件加密为随机后缀名的文件&#xff0c;然后对用户进行勒索。本实…

国内邮件推送如何避免拦截?内容优化技巧?

国内邮件推送的平台怎么选择&#xff1f;如何提高邮件推送效果&#xff1f; 邮件营销是企业与客户沟通的重要方式&#xff0c;但在国内邮件推送过程中&#xff0c;邮件被拦截的问题屡见不鲜。为了确保邮件能够顺利送达目标用户&#xff0c;AokSend将探讨一些有效的策略&#x…

【亲测好用】神级PSAI插件大揭秘:三款创成式修图神器,让你解放双手

PsBeta被停用后&#xff0c;小编一直想找到能够平替PsBeta创成式填充功能的插件。 功夫不负有心&#xff0c;终于被我找到啦&#xff0c;现在就给大家揭秘这三款宝藏修图神器&#xff0c;希望能够帮到大家。 1.插件名称&#xff1a;Starai 无需科学上网&#xff0c;还自带提示…

wireshark常用过滤命令

wireshark常用过滤命令 wireshark抓包介绍单机单点&#xff1a;单机多点&#xff1a;双机并行&#xff1a; wireshark界面认识默认布局调整布局(常用)显示FCS错误 wireshark常见列Time回包数据报对应网络模型 wireshark基本操作结束抓包再次开始抓包 **wireshark常用过滤命令**…

乐鑫云方案研讨会回顾|ESP RainMaker® 引领创业潮,赋能科创企业

近日&#xff0c;乐鑫信息科技 (688018.SH) ESP RainMaker 云生态方案线下研讨会和技术沙龙在深圳成功举办&#xff0c;吸引了众多来自照明电工、新能源、安防、宠物等垂类领域的客户与合作伙伴。活动现场&#xff0c;与会嘉宾围绕产品研发、测试认证、品牌构建、跨境电商等多维…

揭秘循环购:消费即收益,如何助力商家月销百万?

大家好&#xff0c;我是吴军&#xff0c;今天要和大家分享一种颠覆性的商业模式——循环购。你是否听说过“消费1000送2000”这样的促销活动&#xff1f;是不是觉得太不可思议&#xff0c;商家岂不是在“送钱”&#xff1f;别急&#xff0c;让我为你揭开这背后的秘密。 循环购&…

路由器ARP和ARP-proxy(华为)

#交换设备 路由器ARP和ARP-proxy(华为) 当一个广播域中的主机想要访问另外一个广播域的主机时&#xff0c;会广播ARP报文&#xff0c;询问目标IP地址所对应的MAC地址&#xff0c;默认情况下&#xff0c;arp记录是设备自动生成的&#xff0c;但是这样会容易受到ARP欺骗攻击&am…

基础入门篇 | YOLOv10 项目【训练】【验证】【推理】最简单教程 | YOLOv10必看 | 最新更新,直接打印 FPS,mAP50,75,95

文章目录 训练 --train.py推理 --detect.py验证 --val.py不训练,只查看模型结构/参数量/计算量 --test.pyYOLOv10 是基于 YOLOv8 项目的改进版本,目前已经被 YOLOv8 项目合并,所以两个算法使用方法完全一致~ 今天我给大家展示一种非常方便的使用过程,包含【训练】【验证】…

盘立方期货Kdj幅图指标公式源码

盘立方期货Kdj幅图指标公式源码&#xff1a; N:250; WR1:100-100*(HHV(HIGH,N)-CLOSE)/(HHV(HIGH,N)-LLV(LOW,N)),DOT,COLORLIGHTGREEN; EW:EMA(WR1,5); STICKLINE(WR1<20,WR1,20,1,0),COLORYELLOW; STICKLINE(WR1>80,WR1,80,1,0),COLORYELLOW; RSV:(CLOSE-LLV(LOW…

2024CICE电博会·以“链主”企业为核心,推动产业链协同发展

随着全球科技日新月异的发展&#xff0c;消费电子产业已成为推动经济增长、改善人民生活的重要力量。中国国际消费电子博览会&#xff08;简称CICE电博会&#xff09;自2001年创办以来&#xff0c;已经走过了二十余个春秋&#xff0c;凭借其独特的地理位置优势、专业的组织策划…

MAC Address

文章目录 1. 前言2. MAC Address2.1 MAC 地址格式2.2 Locally Administered MAC Address2.3 MAC 单播 和 多播 3. 参考资料 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. MAC Address 2.1 MA…

《2024云安全资源池 能力指南》

《2024云安全资源池 能力指南》这份报告不仅梳理了云安全资源池的发展历程,还深入探讨了其在当前云计算环境下的重要性和必要性。报告详细分析了云安全资源池的市场需求、技术架构、关键技术以及行业应用案例,为政企用户提供了全面的云安全解决方案。通过资料收集、问卷调研、企…

0624_ARM1

思维导图&#xff1a;