前言

普通弹窗(Dialog)只能在当前页面里弹。但有些场景要"跨页面":比如全局悬浮客服按钮、来电浮窗、下载进度球——它们要浮在所有页面之上,切页面也不消失
这类需求靠普通组件做不到,得用悬浮窗(Window)全局弹窗(OverlayManager)。这篇文章讲清两种突破页面边界的方案,以及怎么选。

A hand-drawn doodle illustration on pure white pap

方案 A:全局弹窗 OverlayManager

适合"在任意页面弹一个浮层提示",无需独立窗口。用 OverlayManager 把内容挂到根节点之上:

代码实现

import { OverlayManager, ComponentContent } from '@kit.ArkUI'

// 准备一个可被挂载的 UI 内容
@Builder
function floatTip() {
  Column() {
    Text('这是全局浮层提示')
      .backgroundColor('#333333').fontColor('#FFFFFF').padding(12)
      .borderRadius(8)
  }
}

// 在任意位置弹出来
const uiContext = this.getUIContext()
const content = new ComponentContent(uiContext, wrapBuilder(floatTip))
OverlayManager.addOverlayContent(content)        // 加到全局最上层
// 移除:
OverlayManager.removeOverlayContent(content)

A hand-drawn doodle illustration on pure white pap

要点:

  • OverlayManager 把内容挂到所有页面之上,切页面它还在。
  • 适合轻量浮层(提示、引导、悬浮按钮外观)。
  • ComponentContent 包装你的 @Builder 内容,交给管理器挂载。

方案 B:悬浮窗 Window

需要真正"独立窗口"(如可拖拽的悬浮球、可最小化的视频窗),用 window 模块创建子窗口:

核心代码

import { window } from '@kit.ArkUI'

async function showFloatWindow() {
  const ctx = getContext()
  const floatW = await window.createWindow(ctx, 'floatWin', window.WindowType.TYPE_FLOAT)
  await floatW.setUIContent('pages/FloatPage')   // 悬浮窗里显示的页面
  await floatW.resize(80, 80)                     // 窗口大小
  await floatW.moveWindowTo(100, 200)            // 位置
  await floatW.showWindow()                       // 显示
}

A hand-drawn doodle illustration on pure white pap

  • createWindow 创建的是系统级窗口,层级在所有 Ability 页面之上。
  • setUIContent 指定悬浮窗里渲染哪个页面(在里面正常写 ArkUI)。
  • 适合悬浮球、画中画、来电窗这类"独立存在"的 UI。

两种方案怎么选

场景 方案
全局提示、引导浮层 OverlayManager
可拖拽悬浮球、独立小窗 悬浮窗 Window
切页面要保留 两者都可
需要独立窗口事件/层级 悬浮窗 Window

经验:能 OverlayManager 解决的,别上 Window。Window 权限更敏感、生命周期更重,非必要不碰。

权限与注意

  • 悬浮窗(TYPE_FLOAT)通常需要 ohos.permission.SYSTEM_FLOAT_WINDOW 权限,且要动态申请,上架审核严格。
  • 悬浮窗用完要 destroyWindow() 释放,否则一直占着。
  • OverlayManager 内容也要注意内存,不用时移除。

常见误区(小白必踩)

误区 说明
能用 OverlayManager 却上 Window 悬浮窗权限敏感、生命周期重,轻浮层用 OverlayManager 足够。
Window 不 destroyWindow 用完不销毁一直占着,资源泄漏。
权限不申请 TYPE_FLOAT 需要 SYSTEM_FLOAT_WINDOW 且动态申请,上架审核严。

下面这段代码可以直接复制到 DevEco Studio 里运行。建议你边读边敲,改一改文末「动手改一改」里的参数,亲眼看看效果。

完整可运行示例:全局弹窗

CustomDialogSubWindow 做突破页面边界的弹窗。

完整示例

// CustomDialogController 弹出自定义弹窗
@CustomDialog
struct TipDialog {
  controller: CustomDialogController = new CustomDialogController({ builder: '' })
  build() {
    Column({ space: 12 }) {
      Text('提示').fontSize(20).fontWeight(FontWeight.Bold)
      Text('这是全局弹层').fontSize(14)
      Button('知道了').onClick(() => { this.controller.close() })
    }.padding(24)
  }
}

@Entry
@Component
struct Page {
  private dlg: CustomDialogController = new CustomDialogController({
    builder: TipDialog(), autoCancel: true
  })
  build() {
    Column() {
      Button('弹个窗').onClick(() => { this.dlg.open() })
    }.padding(20)
  }
}

你会看到什么:点按钮,一个自定义内容的弹窗出现在页面之上;点「知道了」或点遮罩外区域关闭。

动手改一改

  • CustomDialogControlleralignment 控制弹窗位置(顶部/底部)。
  • showInSubWindow: true 让弹窗跨页面常驻。
  • 抽成全局单例,任意页面都能唤起同一个弹窗。

写在最后

突破页面边界就两条路:轻浮层用 OverlayManager(简单),真独立窗口用悬浮窗 Window(重,要权限)。多数"全局提示/引导"用 OverlayManager 足够,别轻易碰悬浮窗权限。

如果你要做"悬浮客服按钮",先用 OverlayManager 挂一个可点击的浮标试试——这是性价比最高的全局 UI 入口。真要做可拖拽悬浮球,再上 Window 不迟。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐