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

24 回复
  1. 修改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))
  }
}
  1. 修改LoadingUtils.hideLoading()方法:
static hideLoading(context: common.UIAbilityContext, onHide?: () => void) {
  context?.eventHub.emit(LoadingUtils.EVENT_CLOSE_SUB_WINDOW, onHide)
}
  1. 修改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()
        }
      })
    }
  })
}
  1. 隐藏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里放,见下方评论区的代码~

  1. 创建一个接口用于回调:
export interface WindowCallback {
  onHide(): void
}
  1. 修改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))
  }
}
  1. 修改LoadingUtils.hideLoading()方法:
static hideLoading(context: common.UIAbilityContext, callback?: WindowCallback) {
  context?.eventHub.emit(LoadingUtils.EVENT_CLOSE_SUB_WINDOW, callback)
}
  1. 修改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()
        }
      })
    }
  })
}
  1. 隐藏Loading的调用:
LoadingUtils.hideLoading(getContext(this) as common.UIAbilityContext, {
  onHide() {
    promptAction.showToast({ message: '子窗口已关闭' })
  }
})

尝试了下,在关闭加载框hideLoading后调用系统toast,无法正常弹出toast。

楼主有完整的demo吗!

楼主,我这边创建子window,无法覆盖住屏幕的导航栏,导致如果有背景色的话,就会显示的很奇怪。请问,楼主解决这个问题了吗?

即使设置子窗口为全屏,子窗口仍然不能覆盖状态栏和虚拟导航栏,但是在NEXT版本上则不存在该问题,所以我觉得这可能个api9的bug,在我看来,api9也算个过渡,不用过于纠结,而且在NEXT版本上实现起来更容易,也没有这么多问题。

您好,显示出来的是半透明的遮罩层,没有显示 loading page 的内容。没有跨module调用,请问可能是什么原因呢

我是在 HSP 中创建子窗口和 loadingpage ,也是在 HSP 中调用。setUIContent 设置的路径使用 ‘pages/xxx’ 和 @bundle:packageName/moduleName/ets/pages/XXX 都不行,

在这个api9版本里,LoadingPage必须在entry或feature中进行定义才能显示出来,在HSP里是不行的。

这是 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如何设置全屏啊?隐藏状态栏和底部的那三个按钮。

  1. 修改showLoading方法:

    static showLoading(context: common.UIAbilityContext, message: string = '请稍候', callback: () => void) {
      context?.eventHub.emit(LoadingUtils.EVENT_CREATE_SUB_WINDOW, message, callback)
    }
    
  2. 修改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))
      }
    }
    
  3. 创建子窗口时使用loadContent加载页面(showSubWindow方法):

    let localStorage = new LocalStorage()
    localStorage.setOrCreate('loading_message', message)
    
    if (callback) {
      localStorage.setOrCreate(CALLBACK, callback)
    }
    await win.loadContent(`pages/LoadingPage`, localStorage)
    
  4. 修改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 = () => {
      }
    }
    
  5. 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弹窗的全局性,可以在应用的生命周期中管理子窗口的显示和隐藏,例如在onForegroundonBackground回调中控制其状态。

在实现过程中,需要注意子窗口的生命周期管理,避免内存泄漏和资源浪费。可以通过WindowManager.removeWindow方法在不需要时移除子窗口,并释放相关资源。此外,子窗口的层级和透明度等属性也可以通过WindowManager.LayoutConfig进行配置,以适应不同的应用场景。

通过子窗口形式实现全局Loading弹窗,可以提供更灵活和高效的UI交互体验,同时确保其在应用中的全局性和一致性。具体实现细节可以参考鸿蒙Next的官方文档和相关API说明。

回到顶部