HarmonyOS 鸿蒙Next 全局Loading弹窗:用子窗口的形式来实现(Api9)
HarmonyOS 鸿蒙Next 全局Loading弹窗:用子窗口的形式来实现(Api9) 因为自定义弹窗在按下返回键或ESC键后会消失,有时候并不能满足实际需求,所以考虑采用创建子窗口的方式来实现,子窗口天生对返回事件免疫。小生能力有限,欢迎各位方家指正。 【注】当前程序对应api9。
1. 创建加载要显示的Page页面(LoadingPage.ets)
@Entry
@Component
struct LoadingPage {
@StorageProp('loading_message') message: string = '请稍候'
build() {
Stack({ alignContent: Alignment.Center }) {
Column() {
LoadingProgress()
.width(30)
.height(30)
.color(Color.White)
Text(this.message ?? '请稍候')
.fontColor(Color.White)
.fontSize(16)
.margin({ top: 15 })
.width('100%')
.textAlign(TextAlign.Center)
}
.justifyContent(FlexAlign.Center)
.width(100)
.height(100)
.backgroundColor('#88000000')
.borderRadius(8)
}
.width('100%')
.height('100%')
.backgroundColor(Color.Transparent)
}
}
2. 创建LoadingUtils类用于创建子窗口(LoadingUtils.ts)
/**
* 通过创建子窗口来显示Loading弹窗
*/
export class LoadingUtils {
private static EVENT_CREATE_SUB_WINDOW = 'createSubWindow'
private static EVENT_CLOSE_SUB_WINDOW = 'closeSubWindow'
static LOADING_MESSAGE = 'loading_message'
/**
* 发送创建loading子窗口的事件,显示loading
* @param context context
*/
static showLoading(context: common.UIAbilityContext, message: string = '请稍候') {
AppStorage.SetOrCreate(LoadingUtils.LOADING_MESSAGE, message)
context?.eventHub.emit(LoadingUtils.EVENT_CREATE_SUB_WINDOW)
}
/**
* 发送关闭loading子窗口的事件,隐藏loading
* @param context context
*/
static hideLoading(context: common.UIAbilityContext) {
context?.eventHub.emit(LoadingUtils.EVENT_CLOSE_SUB_WINDOW)
}
/**
* 订阅创建和关闭loading子窗口的事件
* @param ability ability
* @param stage stage
*/
static subscribeLoadingEvent(ability: UIAbility, stage: window.WindowStage) {
if (ability) {
ability.context.eventHub.on(LoadingUtils.EVENT_CREATE_SUB_WINDOW, () => LoadingUtils.showSubWindow(stage))
ability.context.eventHub.on(LoadingUtils.EVENT_CLOSE_SUB_WINDOW, () => LoadingUtils.closeSubWindow(stage))
}
}
/**
* 取消订阅创建和关闭loading子窗口的事件
* @param ability ability
*/
static unsubscribeLoadingEvent(ability: UIAbility) {
if (ability) {
ability.context.eventHub.off(LoadingUtils.EVENT_CREATE_SUB_WINDOW)
ability.context.eventHub.off(LoadingUtils.EVENT_CLOSE_SUB_WINDOW)
}
}
/**
* 显示loading子窗口
* @param stage stage
*/
static async showSubWindow(stage: window.WindowStage) {
stage?.createSubWindow('sub_window').then(async win => {
// 设置子窗口显示的页面
await win.setUIContent("pages/LoadingPage")
let d = display.getDefaultDisplaySync()
let windowClass = stage.getMainWindowSync()
let area = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
// 调整子窗口大小,需剔除状态栏和导航栏高度,否则显示内容不居中
await win.resize(d.width, d.height - area.topRect.height - area.bottomRect.height)
// 设置半透明效果
win.setWindowBackgroundColor('#88000000')
win.showWindow()
})
}
/**
* 关闭loading子窗口
* @param stage stage
*/
static closeSubWindow(stage: window.WindowStage) {
stage?.getSubWindow().then(win => {
if (win.length > 0) {
win[0].destroyWindow()
}
})
}
}
3. 根据UIAbility生命周期回调,实现子窗口事件订阅与取消(MyAbilityStage.ets)
创建MyAbilityStage
export default class MyAbilityStage extends AbilityStage {
onCreate() {
this.context.getApplicationContext().on('abilityLifecycle', this.abilityLifecycleCallback)
}
/**
* 声明ability生命周期回调,需配置所有回调后才可以在applicationContext注册
*/
abilityLifecycleCallback: AbilityLifecycleCallback = {
onAbilityCreate(ability: UIAbility) {},
onWindowStageCreate(ability: UIAbility, windowStage: window.WindowStage) {
// 订阅子窗口事件
LoadingUtils.subscribeLoadingEvent(ability, windowStage)
},
onWindowStageActive(ability: UIAbility, windowStage: window.WindowStage) {},
onWindowStageInactive(ability: UIAbility, windowStage: window.WindowStage) {},
onWindowStageDestroy(ability: UIAbility, windowStage: window.WindowStage) {
LoadingUtils.unsubscribeLoadingEvent(ability)
},
onAbilityDestroy(ability: UIAbility) {},
onAbilityForeground(ability: UIAbility) {},
onAbilityBackground(ability: UIAbility) {},
onAbilityContinue(ability: UIAbility) {}
}
}
配置MyAbilityStage
"module": {
...
"srcEntry": './ets/MyAbilityStage.ets',
...
}
4. 使用
显示loading:
LoadingUtils.showLoading(getContext(this) as common.UIAbilityContext, '加载中')
隐藏loading:
LoadingUtils.hideLoading(getContext(this) as common.UIAbilityContext)
更多关于HarmonyOS 鸿蒙Next 全局Loading弹窗:用子窗口的形式来实现(Api9)的实战教程也可以访问 https://www.itying.com/category-93-b0.html
- 修改LoadingUtils.subscribeLoadingEvent()方法:
static subscribeLoadingEvent(ability: UIAbility, stage: window.WindowStage) {
if (ability) {
ability.context.eventHub.on(LoadingUtils.EVENT_CREATE_SUB_WINDOW, () => LoadingUtils.showSubWindow(stage))
ability.context.eventHub.on(LoadingUtils.EVENT_CLOSE_SUB_WINDOW, (onHide?: () => void) => LoadingUtils.closeSubWindow(stage, onHide))
}
}
- 修改LoadingUtils.hideLoading()方法:
static hideLoading(context: common.UIAbilityContext, onHide?: () => void) {
context?.eventHub.emit(LoadingUtils.EVENT_CLOSE_SUB_WINDOW, onHide)
}
- 修改LoadingUtils.closeSubWindow()方法:
static closeSubWindow(stage: window.WindowStage, onHide?: () => void) {
stage?.getSubWindow().then(win => {
if (win.length > 0) {
win[0].destroyWindow(err => {
if (onHide && err && !err.code) {
onHide()
}
})
}
})
}
- 隐藏Loading的调用:
LoadingUtils.hideLoading(getContext(this) as common.UIAbilityContext, () => {
promptAction.showToast({ message: '子窗口已关闭' })
})
更多关于HarmonyOS 鸿蒙Next 全局Loading弹窗:用子窗口的形式来实现(Api9)的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
感谢回复和提供解决思路。我这边期望的是LoadingPage可以接收一些方法,目前看到的message是通过AppStorage传递的。类似于
LoadingUtils.showLoading({
context,
onClose:() => {},
onClick:() => {},
otherFun:() => {}
})
对,我也需要这个。
可以啊,回调函数可以往LocalStorage里放,见下方评论区的代码~
- 创建一个接口用于回调:
export interface WindowCallback {
onHide(): void
}
- 修改LoadingUtils.subscribeLoadingEvent()方法:
static subscribeLoadingEvent(ability: UIAbility, stage: window.WindowStage) {
if (ability) {
ability.context.eventHub.on(LoadingUtils.EVENT_CREATE_SUB_WINDOW, () => LoadingUtils.showSubWindow(stage))
ability.context.eventHub.on(LoadingUtils.EVENT_CLOSE_SUB_WINDOW, (callback: WindowCallback) => LoadingUtils.closeSubWindow(stage, callback))
}
}
- 修改LoadingUtils.hideLoading()方法:
static hideLoading(context: common.UIAbilityContext, callback?: WindowCallback) {
context?.eventHub.emit(LoadingUtils.EVENT_CLOSE_SUB_WINDOW, callback)
}
- 修改LoadingUtils.closeSubWindow()方法:
static closeSubWindow(stage: window.WindowStage, callback?: WindowCallback) {
stage?.getSubWindow().then(win => {
if (win.length > 0) {
win[0].destroyWindow(err => {
if (callback && !err.code) {
callback.onHide()
}
})
}
})
}
- 隐藏Loading的调用:
LoadingUtils.hideLoading(getContext(this) as common.UIAbilityContext, {
onHide() {
promptAction.showToast({ message: '子窗口已关闭' })
}
})
尝试了下,在关闭加载框hideLoading后调用系统toast,无法正常弹出toast。
楼主,我这边创建子window,无法覆盖住屏幕的导航栏,导致如果有背景色的话,就会显示的很奇怪。请问,楼主解决这个问题了吗?
即使设置子窗口为全屏,子窗口仍然不能覆盖状态栏和虚拟导航栏,但是在NEXT版本上则不存在该问题,所以我觉得这可能个api9的bug,在我看来,api9也算个过渡,不用过于纠结,而且在NEXT版本上实现起来更容易,也没有这么多问题。
您好,显示出来的是半透明的遮罩层,没有显示 loading page 的内容。没有跨module调用,请问可能是什么原因呢
我是在 HSP 中创建子窗口和 loadingpage ,也是在 HSP 中调用。setUIContent 设置的路径使用 ‘pages/xxx’ 和 @bundle:packageName/moduleName/ets/pages/XXX 都不行,
这是 api 9 版本的限制吗?如果需要适配 api 9 , 是不是只能将 hsp 改成 hap 了?
显示出来的是一个遮罩层,没有预览图的loading page 内容,检查了也没有垮module调用,请问可能是什么问题呢?
如果你想隐藏状态栏和底部需要设置一下,让窗口沉浸式显示,但是不知是不是系统的Bug,子窗口会自动避开状态栏。
```bash
let win = windowStage.getMainWindowSync()
win.setWindowSystemBarEnable([])
win.setWindowLayoutFullScreen(true)
我主窗口隐藏了状态栏了,但是在弹出子窗口loading的时候那个状态栏又出来了,
let win = windowStage.getMainWindowSync() // 这个应该是设置主窗口隐藏的,
我把所有的子窗口都获取出来了,然后循环设置隐藏状态栏,但是结果还是不管用,不知道为什么,我在研究研究吧 谢谢啦,
在api11模拟器上是没问题的,我认为是api9的bug。
楼主你好,请问一下,弹出的loading如何设置全屏啊?隐藏状态栏和底部的那三个按钮。
-
修改
showLoading
方法:static showLoading(context: common.UIAbilityContext, message: string = '请稍候', callback: () => void) { context?.eventHub.emit(LoadingUtils.EVENT_CREATE_SUB_WINDOW, message, callback) }
-
修改
subscribeLoadingEvent
方法:static subscribeLoadingEvent(ability: UIAbility, stage: window.WindowStage) { if (ability) { ability.context.eventHub.on(LoadingUtils.EVENT_CREATE_SUB_WINDOW, (message: string, callback: () => void) => LoadingUtils.showSubWindow(stage, message, callback)) } }
-
创建子窗口时使用
loadContent
加载页面(showSubWindow
方法):let localStorage = new LocalStorage() localStorage.setOrCreate('loading_message', message) if (callback) { localStorage.setOrCreate(CALLBACK, callback) } await win.loadContent(`pages/LoadingPage`, localStorage)
-
修改
LoadingPage.ets
:let storage = LocalStorage.GetShared() [@Entry](/user/Entry)(storage) [@Component](/user/Component) export struct LoadingPage { @LocalStorageProp('loading_message') message: string = '请稍候' /** * 调试时发现一个bug,callback如果为undefined,页面在销毁时会导致程序崩溃; * 如果是null时,点击按钮后却发现this.callback不为null,但无法实现this.callback()的调用; * 所以,这里提供默认实现,同时在向LocalStorage中存储callback时加一个判断,防止将null或undefined赋值为callback */ @LocalStorageProp('callback') callback: () => void = () => { } }
-
LoadingPage.ets
中Button点击回调:Button('点击').onClick(() => { if (this.callback) { this.callback() } })
大佬,你那边eventHub.emit发送message和callback后还能显示吗?我这边显示不出来了,把callback删除后就可以了,
我这边倒是没事,不知你那边是啥情况~
好的,那我再自己看看,
在HarmonyOS鸿蒙Next中,全局Loading弹窗可以通过子窗口的形式实现,具体使用Api9的相关接口。首先,需要使用WindowManager
来创建和管理子窗口。通过WindowManager.createWindow
方法可以创建一个新的子窗口,并设置其类型为WindowManager.LayoutConfig.TYPE_SYSTEM_ALERT
,以确保该窗口在所有应用之上显示。
在子窗口中,可以加载自定义的Loading界面,使用ComponentContainer
来布局和管理UI组件。通过WindowManager.addWindow
方法将子窗口添加到当前窗口中,并设置其显示位置和大小。为了确保Loading弹窗的全局性,可以在应用的生命周期中管理子窗口的显示和隐藏,例如在onForeground
和onBackground
回调中控制其状态。
在实现过程中,需要注意子窗口的生命周期管理,避免内存泄漏和资源浪费。可以通过WindowManager.removeWindow
方法在不需要时移除子窗口,并释放相关资源。此外,子窗口的层级和透明度等属性也可以通过WindowManager.LayoutConfig
进行配置,以适应不同的应用场景。
通过子窗口形式实现全局Loading弹窗,可以提供更灵活和高效的UI交互体验,同时确保其在应用中的全局性和一致性。具体实现细节可以参考鸿蒙Next的官方文档和相关API说明。