HarmonyOS一杯冰美式的时间 -- 应用内消息通知

654 阅读4分钟

一、前言

首先,启用Notice功能需在应用设置中激活消息通知选项。对于具有应用外推送能力的功能,还需在设备的系统设置中授权通知权限。面对用户可能的权限拒绝,我们需要备选方案,在应用内部实现类似微信的Notice。

我们公司目前选择是单Ability的Stage,在Android的单Activity的开发中,我们只需要在MainActivity的DecorView处理一个自定义View就好了,很容易联想到HarmonyOS是否也能这么做。

效果图:

2024-01-29_08-58-17 (1)

如果您有任何疑问、对文章写的不满意、发现错误、想吐槽或者有更好的想法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏

二、WindowStage

通过官方文档,我们了解到Stage模型的窗口管理主要通过@ohos.window实现。关键功能类包括Window(代表当前窗口实例,是窗口管理器的基本单元),以及WindowStage(负责管理各个窗口单元的窗口管理器)。

应用入口页面,就是使用WindowStage加载

 export default class EntryAbility extends UIAbility {
 ​
   onWindowStageCreate(windowStage: window.WindowStage) {
     windowStage.loadContent('pages/SplashPage', (err, data) => {
       if (err.code) {
         return;
       }
     });
   }
 ​
 };

查看下windowStage的API,可以看到几个Sub的方法,联想下Android那些Sub方法,看样子我们可以拿来用了。

 createSubWindow(name: string): Promise<Window>;
 createSubWindow(name: string, callback: AsyncCallback<Window>): void;
 getSubWindow(): Promise<Array<Window>>;
 getSubWindow(callback: AsyncCallback<Array<Window>>): void;

HarmonyOS提供了灵活(并不灵活)的API来创建、配置、展示和销毁应用的子窗口。

按照API来

  1. 创建应用子窗口:通过createSubWindow接口创建应用子窗口。
  2. 设置子窗口属性:子窗口创建成功后,可以改变其大小、位置等,还可以根据应用需要设置窗口背景色、亮度等属性。
  3. 加载显示子窗口的具体内容:通过setUIContent和showWindow接口加载显示子窗口的具体内容。
  4. 销毁子窗口:使用destroyWindow接口销毁子窗口。

看代码

 // 定义全局变量以保存窗口和子窗口实例。
 let mWindowStage: window.WindowStage = null;
 let subWindow: window.Window = null;
 export default class EntryAbility extends UIAbility {
     //加载Sub
     showSubWindow() {
         // 1.创建应用子窗口。
         mainWindowStage.createSubWindow("NoticeSubWindow", (err, data) => {
             if (err.code) {
                 return;
             }
             subWindow = data;
             // 2.子窗口创建成功后,设置子窗口的位置、大小及相关属性等。
             subWindow.moveWindowTo(0, 0);
             let mainWindowWidth = mWindowStage.getMainWindowSync().getWindowProperties().windowRect.width
             subWindow.resize(mainWindowWidth, 500);
             // 3.为子窗口加载对应的目标页面。
             subWindow.setUIContent("pages/SubWindowNotice", (err) => {
                 if (err.code) {
                     return;
                 }
                 // 4.显示子窗口。
                 subWindow.showWindow();
             });
         })
     }
     //销毁子窗口。
     destroySubWindow() {
         
         subWindow.destroyWindow();
     }
     onWindowStageCreate(windowStage) {
         mainWindowStage = windowStage;
         this.showSubWindow();
     }
 };

在设计消息通知弹窗时,我们首先将moveWindowTo设置为0,以确保它从屏幕顶部出现。

宽度当然是填充完mWindow。高度随便写一个了。然后还需要准备一个page页面,这就是一个正常的页面了。

实际上还有一个从顶部往下的动画,标准库中的动画选项并未达到我们的预期效果,所以想自己写动画,找了半天,发现没有自定义动画的API?(鸿蒙在想什么)

思来想去,可以把Window设置为透明的,操作page中的ui作为动画即可。

 subWindow.setUIContent("pages/SubWindowNotice", (err) => {
   if (err.code) {
     return;
   }
   subWindow.setWindowBackgroundColor("#00FFFFFF");
   subWindow.showWindow();
 });

三、SubWindowNotice

首先我们需要创建一个Page,和setUIContent中的pages/SubWindowNotice一致

 @Entry
 @Preview
 @Component
 struct SubWindowNotice {
   mHeight: number = vp(40)  // 对话框的高度。
   build() {
       // 创建一行布局。
       Row({ space: vp(3) }) {
         // 添加一个图片元素。
         Image($r("app.media.icon_default"))
           .autoResize(true)
           .margin({ top: vp(5), bottom: vp(5) })
           .width(vp(25))
         // 创建一个列布局,用于放置文本。
         Column() {
           Text("标题")
           Text("这是一行普通的描述")
         }
         .alignItems(HorizontalAlign.Start)
         .layoutWeight(1)
         // 添加一个按钮。
         Button("Button").fontSize("10fp")
       }
       .height(this.mHeight)
       .padding({ left: vp(5), right: vp(5), bottom: vp(3), top: vp(3) })
       .border({
         radius: vp(10)
       })
       .margin({ left: vp(5), right: vp(5) })
       .backgroundColor(0x33000000)
   }
 }

首先,我们准备好一个布局,是一个简单的,图片+标题+内容+按钮的布局。

因为有显示、隐藏两种状态,我们需要使用一个条件来控制它:

 @State isShow: boolean = false  // 控制对话框的显示和隐藏。

使用if包裹住整个Row,这样在isShow发生改变时,Row也会触发显示隐藏。

 if (this.isShow) {
   Row({ space: vp(3) }) {
     //...
   }
   //....
 }

显然我们还需要一个动画,在这里因为触发了显示隐藏,我们可以使用transition

 // 设置对话框的过渡动画、尺寸、内边距、边框、外边距和背景颜色。
 .transition({ type: TransitionType.All, opacity: 1, translate: { x: 0, y: -this.mHeight } })

接着,我们在onPageShow添加家伙动画事件

 onPageShow() {
   // 设置一个延迟,用于触发动画效果。
   setTimeout(() => {
     this.toggleAnimation()
     // 设置另一个延迟,用于在动画完成后重新触发动画效果。
     setTimeout(() => {
       this.toggleAnimation()
     }, 3000)
   }, 300)
 }
 ​
 // 切换动画的状态。
 toggleAnimation() {
   // 使用animateTo实现动画效果。
   animateTo({ duration: 1000 }, () => {
     this.isShow = !this.isShow;
   })
 }

一切准备就绪,很简单,我们在onPageShow中,执行一个transition动画,使用isShow控制显示隐藏。在Demo中我使用了3s的延时消失,具体什么时候显示,隐藏还是需要自己去修改的。

完整的代码:

 ​
 @Entry
 @Preview
 @Component
 struct SubWindowNotice {
   @State isShow: boolean = false
   @State isRunning: boolean = false
   mHeight: number = vp(40)
   textValue: string = "textValue"
 ​
   build() {
     if (this.isShow) {
       Row({ space: vp(3) }) {
         Image($r("app.media.icon_default")).autoResize(true).margin({ top: vp(5), bottom: vp(5) }).width(vp(25))
         Column() {
           Text("标题")
           Text("这是一行普通的描述")
         }.alignItems(HorizontalAlign.Start)
         .layoutWeight(1)
         Button("Button").fontSize("10fp")
       }
       .transition({ type: TransitionType.All, opacity: 1, translate: { x: 0, y: -this.mHeight } })
       .height(this.mHeight)
       .padding({ left: vp(5), right: vp(5), bottom: vp(3), top: vp(3) })
       .border({
         radius: vp(10)
       })
       .margin({ left: vp(5), right: vp(5) })
       .backgroundColor(0x33000000)
     }
   }
 ​
   onPageShow() {
     setTimeout(() => {
       this.toggleAnimation()
       setTimeout(() => {
         this.toggleAnimation()
       }, 3000)
     }, 300)
   }
 ​
   toggleAnimation() {
     animateTo({ duration: 1000 }, () => {
       this.isShow = !this.isShow;
     })
   }
 }

四、总结

总之,在单Ability的Stage模型中,使用SubWindow可以实现在所有的Page上添加一个Notice,你也可以用来做全局动画、悬浮窗...等等。当然了这只是应用内的。

另外吐槽下HarmonyOS的@Extend的“全局”,不能在文件外用,是真恶心啊。

如果您有任何疑问、对文章写的不满意、发现错误、想吐槽或者有更好的想法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏