鸿蒙HarmonyOS服务卡片实战

打印 上一主题 下一主题

主题 988|帖子 988|积分 2966

引言

在当代开发中,服务卡片是不可或缺的一部门,比如音乐,天气类等应用,官网的先容中写道:卡片让您便捷地预览服务信息,例如检察天气或日历日程等内容。您可将卡片添加到屏幕上,让这类信息触手可及。您还可按喜欢选取差别样式和排列方式,打造个性化桌面。
大体思绪


  • 创建服务卡片
  • 应用与服务卡片的交互
  • 刷新卡片内容
  • 应用端主动更新卡片信息
创建服务卡片

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


然后应用就可以愉快的进行服务卡片的开发了:
创建步骤:选中Entry,new,Service Widget

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

创建完成后除了serviceCard页面还会天生一个EntryFormAbility文件,和EntryAbility差别的是,EntryFormAbility继续了FormExtensionAbility,而EntryAbility继续了UIAbility。可以理解他们是2个历程。
创建完服务卡片,先运行起来后效果:
我们可以观察到,创建的服务卡片,在点击卡片后有如下代码
  1. postCardAction(this, {
  2.         action: this.ACTION_TYPE,
  3.         abilityName: this.ABILITY_NAME,
  4.         params: {
  5.           message: this.MESSAGE
  6.         }
  7.       });
复制代码
postCardAction是给卡片添加意图,this代表当前卡片,action是类型,abilityName是打开哪个Ability,params是需要给应用历程传递的数据(服务卡片在另外一个历程),点击完服务卡片后发现,跳转到了应用首页,那能否跳转到指定页面呢?
应用与服务卡片的交互

在卡片上添加2 个按钮,去掉原来的整个卡片的onClick,添加2个按钮
  1. Button('首页').onClick(()=>{
  2.           postCardAction(this,{
  3.             'action':'router',
  4.             'abilityName':'EntryAbility',
  5.             'params':{
  6.               targetPage:"Index"
  7.             },
  8.           })
  9.         })
  10. Button('我的').onClick(()=>{
  11.           postCardAction(this,{
  12.             'action':'router',
  13.             'abilityName':'EntryAbility',
  14.             'params':{
  15.               targetPage:"MinePage"
  16.             },
  17.           })
  18.         })
  19.       }
复制代码
在点击按钮的时间把我们需要跳转的page名称传给应用,那如何担当这targetPage呢?
我们在EntryAbility里看到很多空方法,实在就是Ability的生命周期,有过原生安卓开发履历的,可以将它理解为application的生命周期回调函数。
  1. //要访问的页面
  2. let selectPage =''
  3. export default class EntryAbility extends UIAbility {
  4.   onCreate(want, launchParam) {
  5.     //启动程序的生命周期
  6.     if(want.parameters.params!==undefined){
  7.       let params = JSON.parse(want.parameters.params);
  8.       console.log('onCreate router targetPage:'+params.targetPage);
  9.       selectPage = params.targetPage;
  10.     }
  11.   }
  12.   onWindowStageCreate(windowStage: window.WindowStage) {
  13.     //默认进入首页
  14.     let target = selectPage || 'Index';
  15.     target = 'pages/'+target;
  16.     // Main window is created, set main page for this ability
  17.     hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
  18.     //存储窗口实例
  19.     windowStage.loadContent(target, (err, data) => {
  20.       if (err.code) {
  21.         hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
  22.         return;
  23.       }
  24.       hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
  25.     });
  26.   }
  27. }
复制代码
我们可以设置一个参数selectPage,通过onCreate方法里的want.parameters.params获取到我们上面点击服务卡片的时间传递的params对象。通过JSON分析后拿到targetPage,在onWindowStageCreate中组合拼装一下路由地点,currentWindow.loadContent(tatgetPage)执行后,有值就根据参数拼接rute地点跳转,否则就默认首页。

运行后当kill掉应用的历程,可以或许正常跳转到首页和我的页面,但是有个问题,如果只是把应用推到后台,再点击我的,发现无效无法跳转,这是什么缘故原由呢?有些眼尖的同学可能就发现了,我们担当和打开的方法都放在了onCreate里。只有创建的时间才会接收到,如果Ability已经创建了,就无法接收到卡片的通知回调。
那么改如何做呢?答案是通过onNewWant函数,这个函数的意思是,如果UIAbility已在后台运行,在收到Router事件后会触发,改写后的代码:
  1. //要访问的页面
  2. let selectPage =''
  3. //当前的windown对象
  4. let currentWindow = null
  5. export default class EntryAbility extends UIAbility {
  6.   onCreate(want, launchParam) {
  7.     //启动程序的生命周期
  8.     if(want.parameters.params!==undefined){
  9.       let params = JSON.parse(want.parameters.params);
  10.       console.log('onCreate router targetPage:'+params.targetPage);
  11.       selectPage = params.targetPage;
  12.     }
  13.   }
  14.   //如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期的回调
  15.   onNewWant(want, launchParam){
  16.     if(want.parameters.params!==undefined){
  17.       let params = JSON.parse(want.parameters.params);
  18.       console.log('onCreate router targetPage:'+params.targetPage);
  19.       selectPage = params.targetPage;
  20.     }
  21.     //进入对应页面
  22.     let target = selectPage || 'AddressPage';
  23.     target = 'pages/'+target;
  24.     currentWindow.loadContent(target, (err, data) => {
  25.       if (err.code) {
  26.         hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
  27.         return;
  28.       }
  29.       hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
  30.     });
  31.   }
  32.   onWindowStageCreate(windowStage: window.WindowStage) {
  33.     //默认进入首页
  34.     let target = selectPage || 'AddressPage';
  35.     target = 'pages/'+target;
  36.     // Main window is created, set main page for this ability
  37.     hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
  38.     //存储窗口实例
  39.     currentWindow = windowStage
  40.     currentWindow.loadContent(target, (err, data) => {
  41.       if (err.code) {
  42.         hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
  43.         return;
  44.       }
  45.       hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
  46.     });
  47.   }
  48. }
复制代码
创建了一个变量currentWindow用以存储onWindowStageCreate中的windown对象,然后在onNewWant中获取相应的信息,再执行跳转。
刷新卡片内容

到目前为止卡片上的信息都是静态展示的,那我们如何更新卡片上的信息呢?
起首需要在EntryFormAbility的onAddForm里创建一对要更新的数据,通过formBindingData.createFormBindingData创建一个FormBindingData的气力返回归去
  1. onAddForm(want: Want) {
  2.    
  3.     let formData = {
  4.       title : 'jay'
  5.     }
  6.     return formBindingData.createFormBindingData(formData);
  7.   }
复制代码
此时卡片作为接收方需要通过LocalStorage获取传递的数据,具体做法如下:
  1. let storage = new LocalStorage();
  2. @Entry(storage)
  3. @Component
  4. struct MessageCardCard {
  5. @LocalStorageProp('title') title:string='zhoujielun'
  6.   build() {
  7.     Row() {
  8.       Column({space:20}) {
  9.         Text('服务卡片:'+this.title)
  10.           .fontSize($r('app.float.font_size'))
  11.           .fontWeight(FontWeight.Medium)
  12.           .fontColor($r('app.color.item_title_font'))
  13.       }
  14.       .width('100%')
  15.     }
  16.     .height('100%  ')
  17.   }
  18. }
复制代码
创建一个LocalStorage实例,然后在Entry中传入storage,在通过@LocalStorageProp('title') title:string='zhoujielun'读取数据,需要注意:1必须要有默认值,2LocalStorageProp包裹的字符串名需要和onAddForm函数中的formData的key保持一致,才能正确担当到数据的更新。
运行后发现,卡片上的数据已经变成了jay

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

依然是调用postCardAction 方法,action改为message
  1.    Button('更新数据') .onClick(()=>{
  2.           postCardAction(this,{
  3.             action:'message',
  4.             params:{}
  5.           })
  6.         })
复制代码
这时间就需要用到EntryFormAbility的onFormEvent函数了
  1. //对应卡片的message事件
  2.   onFormEvent(formId: string, message: string) {
  3.     let fromData={'title':'黑色毛衣'};
  4.     //构建传入的数据对象
  5.     let fromInfo  = formBindingData.createFormBindingData(fromData)
  6.     //指定卡片id,传入最新的数据
  7.     formProvider.updateForm(formId,fromInfo)
  8.   }
复制代码
此时运行后点击卡片上的更新数据的按钮,数据已经更新过来了

应用方更新服务卡片信息

因为应用方无法知道服务卡片的formId,那么我就要在服务卡片创建的时间把fromId存起来,然后统一发送事件更新信息。
  1. // @ts-nocheck
  2. import formInfo from '@ohos.app.form.formInfo';
  3. import formBindingData from '@ohos.app.form.formBindingData';
  4. import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
  5. import Want from '@ohos.app.ability.Want';
  6. import formProvider from '@ohos.app.form.formProvider';
  7. import dataPreferences from '@ohos.data.preferences';
  8. export default class EntryFormAbility extends FormExtensionAbility {
  9.   onAddForm(want: Want) {
  10.     //在want中取出卡片的唯一标识formId
  11.     let formId:string = want.parameters[formInfo.FormParam.IDENTITY_KEY];
  12.       //把formId保存到首选项中
  13.     ;(async ()=>{
  14.       let pre = await dataPreferences.getPreferences(this.context,'formIds');
  15.       // @ts-ignore
  16.       let formIds:string = await pre.get('formIds','[]');
  17.       let formIdsArray = JSON.parse(formIds);
  18.       formIdsArray.push(formId);
  19.       await pre.put('formIds',JSON.stringify(formIdsArray));
  20.       await pre.flush();
  21.     })()
  22.     let formData = {
  23.       title : 'jay'
  24.     }
  25.     return formBindingData.createFormBindingData(formData);
  26.   }
  27.   onCastToNormalForm(formId: string) {
  28.     // Called when the form provider is notified that a temporary form is successfully
  29.     // converted to a normal form.
  30.   }
  31.   onUpdateForm(formId: string) {
  32.     // Called to notify the form provider to update a specified form.
  33.   }
  34.   onChangeFormVisibility(newStatus: Record<string, number>) {
  35.     // Called when the form provider receives form events from the system.
  36.   }
  37.   //对应卡片的message事件
  38.   onFormEvent(formId: string, message: string) {
  39.     // Called when a specified message event defined by the form provider is triggered.
  40.     let fromData={'title':'黑色毛衣'};
  41.     //构建传入的数据对象
  42.     let fromInfo  = formBindingData.createFormBindingData(fromData)
  43.     //指定卡片id,传入最新的数据
  44.     formProvider.updateForm(formId,fromInfo)
  45.   }
  46.   onRemoveForm(formId: string) {
  47.     // Called to notify the form provider that a specified form has been destroyed.
  48.   }
  49.   onAcquireFormState(want: Want) {
  50.     // Called to return a {@link FormState} object.
  51.     return formInfo.FormState.READY;
  52.   }
  53. };
复制代码
在Page页面根据取出首选项中的formId,循环调用updateForm更新数据:
  1. import router from '@ohos.router'
  2. import dataPreferences from '@ohos.data.preferences';
  3. import formBindingData from '@ohos.app.form.formBindingData';
  4. import formProvider from '@ohos.app.form.formProvider';
  5. @Entry
  6. @Component
  7. struct AddressPage {
  8.   store: any = {}
  9.   @State city: string = ''
  10.   t:number = 1
  11.   async aboutToAppear() {
  12.     setInterval(async ()=>{
  13.       let formData={time: ++this.t};
  14.       //读取首选项中的id集合
  15.       let pre = await dataPreferences.getPreferences(getContext(this),'formIds');
  16.       // @ts-ignore
  17.       let formIds:string = await pre.get('formIds','[]');
  18.       let formMsg = formBindingData.createFormBindingData(formData)
  19.       //遍历集合,将新数据传到当前应用的所有卡片
  20.       JSON.parse(formIds).forEach((currentFormId)=>{
  21.         formProvider.updateForm(currentFormId,formMsg)
  22.       });
  23.     },1000)
  24.   }
  25.   build() {
  26.     Row() {
  27.       Column() {
  28.         Text('首页')
  29.    
  30.         Button('更新卡片').onClick(async ()=>{
  31.           let formData={title:'听妈妈的话'};
  32.           //读取首选项中的id集合
  33.           let pre = await dataPreferences.getPreferences(getContext(this),'formIds');
  34.           // @ts-ignore
  35.           let formIds:string = await pre.get('formIds','[]');
  36.           let formMsg = formBindingData.createFormBindingData(formData)
  37.           //遍历集合,将新数据传到当前应用的所有卡片
  38.           JSON.parse(formIds).forEach((currentFormId)=>{
  39.             formProvider.updateForm(currentFormId,formMsg)
  40.           });
  41.         })
  42.       }
  43.       .width('100%')
  44.     }
  45.     .height('100%')
  46.   }
  47. }
复制代码
上面的例子中,我直接通过定时间,每隔一秒更新卡片上的数字,模拟充电大概歌曲播放进度效果,运行后:

总结

服务卡片的创建交互,以及刷新卡片信息的内容到此结束,相信应该能满足大部门业务需求,差异无非就是在UI上。
今天是HDC2024华为开发者大会,谨借此篇博客提前庆祝纯血鸿蒙的到来,华为加油,HarmonyOS 加油
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表