一、前言
首先,启用Notice功能需在应用设置中激活消息通知选项。对于具有应用外推送能力的功能,还需在设备的系统设置中授权通知权限。面对用户可能的权限拒绝,我们需要备选方案,在应用内部实现类似微信的Notice。
我们公司目前选择是单Ability的Stage,在Android的单Activity的开发中,我们只需要在MainActivity的DecorView处理一个自定义View就好了,很容易联想到HarmonyOS是否也能这么做。
效果图:
如果您有任何疑问、对文章写的不满意、发现错误、想吐槽或者有更好的想法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
二、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来
- 创建应用子窗口:通过createSubWindow接口创建应用子窗口。
- 设置子窗口属性:子窗口创建成功后,可以改变其大小、位置等,还可以根据应用需要设置窗口背景色、亮度等属性。
- 加载显示子窗口的具体内容:通过setUIContent和showWindow接口加载显示子窗口的具体内容。
- 销毁子窗口:使用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的“全局”,不能在文件外用,是真恶心啊。
如果您有任何疑问、对文章写的不满意、发现错误、想吐槽或者有更好的想法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏