HarmonyOS开发:系统级窗口——系统弹窗与悬浮窗

举报
Jack20 发表于 2026/06/28 21:12:59 2026/06/28
【摘要】 HarmonyOS开发:系统级窗口——系统弹窗与悬浮窗📌 核心要点:系统级窗口是鸿蒙窗口体系中的"特权窗口",能穿透应用层显示弹窗和悬浮窗,搞懂窗口层级和创建方式是关键。 背景与动机来电弹窗、低电量提醒、导航悬浮球——这些UI元素有个共同特点:不管你在哪个应用里,它们都能显示在最上层。普通应用能做吗?不能。普通应用的窗口被限制在自己的层级里,最多只能在自己的页面上弹个对话框。想在其他应用...

HarmonyOS开发:系统级窗口——系统弹窗与悬浮窗

📌 核心要点:系统级窗口是鸿蒙窗口体系中的"特权窗口",能穿透应用层显示弹窗和悬浮窗,搞懂窗口层级和创建方式是关键。

背景与动机

来电弹窗、低电量提醒、导航悬浮球——这些UI元素有个共同特点:不管你在哪个应用里,它们都能显示在最上层。

普通应用能做吗?不能。普通应用的窗口被限制在自己的层级里,最多只能在自己的页面上弹个对话框。想在其他应用上面弹窗?想都别想。

但系统应用可以。通过系统级窗口API,系统签名应用可以创建悬浮窗、系统弹窗、全局提示——这些窗口的z-order比普通应用窗口高,能穿透显示。

不过,系统级窗口的创建和管理有一套严格的规则。层级搞错了窗口被遮挡,类型选错了创建失败,权限不够直接报错。这篇就把系统级窗口的层级、创建、管理讲清楚。

核心原理

窗口层级体系

鸿蒙的窗口从底到顶分多个层级:

z-order 从低到高:
┌────────────────────────────────┐
│  系统安全窗口 (ALERT_WINDOW)     │ ← 最高层:系统警告
├────────────────────────────────┤
│  系统浮窗 (FLOAT_WINDOW)        │ ← 高层:来电弹窗、悬浮球
├────────────────────────────────┤
│  系统状态栏 (STATUS_BAR)        │
├────────────────────────────────┤
│  应用浮窗 (APPLICATION_FLOAT)   │ ← 应用级悬浮窗
├────────────────────────────────┤
│  应用主窗口 (MAIN_WINDOW)       │ ← 普通应用窗口
├────────────────────────────────┤
│  系统导航栏 (NAVIGATION_BAR)    │
└────────────────────────────────┘

系统级窗口的z-order比应用窗口高,所以能"穿透"显示。但不同类型的系统窗口之间也有层级差异,ALERT_WINDOWFLOAT_WINDOW更高。

窗口类型与权限

窗口类型 枚举值 权限要求 典型场景
应用主窗口 TYPE_MAIN 应用主界面
应用子窗口 TYPE_SUB 对话框、弹窗
应用浮窗 TYPE_FLOAT ohos.permission.SYSTEM_FLOAT_WINDOW 应用内悬浮窗
系统浮窗 TYPE_SYSTEM_FLOAT 系统签名 来电弹窗、悬浮球
系统告警窗 TYPE_ALERT 系统签名+特权 系统警告、低电量

系统级窗口的创建流程

flowchart TD
    A[创建系统级窗口] --> B{窗口类型}
    B -->|系统浮窗| C[TYPE_SYSTEM_FLOAT]
    B -->|系统告警窗| D[TYPE_ALERT]
    
    C --> E[检查系统签名]
    D --> E
    
    E -->|有签名| F[创建窗口]
    E -->|无签名| G[Permission Denied]
    
    F --> H[设置窗口属性]
    H --> H1[窗口尺寸]
    H --> H2[窗口位置]
    H --> H3[背景透明度]
    H --> H4[触摸事件穿透]
    
    H --> I[加载UI内容]
    I --> J[显示窗口]
    
    classDef type fill:#e3f2fd,stroke:#2196f3,color:#0d47a1
    classDef check fill:#fff3e0,stroke:#ff9800,color:#e65100
    classDef fail fill:#fce4ec,stroke:#f44336,color:#b71c1c
    classDef success fill:#e8f5e9,stroke:#4caf50,color:#1b5e20
    classDef config fill:#f3e5f5,stroke:#9c27b0,color:#4a148c
    
    class B,C,D type
    class E check
    class G fail
    class F,J success
    class H,H1,H2,H3,H4 config

代码实战

基础用法:创建系统浮窗

// common/SystemWindowManager.ets - 系统窗口管理工具
import window from '@ohos.window';
import { BusinessError } from '@ohos.base';

class SystemWindowManager {
  private tag: string = 'SystemWindowManager';
  private systemFloatWindow: window.Window | null = null;

  // 创建系统浮窗
  async createSystemFloatWindow(context: Context): Promise<window.Window | null> {
    try {
      // 创建系统浮窗
      const config: window.Configuration = {
        name: 'SystemFloatWindow',
        windowType: window.WindowType.TYPE_SYSTEM_FLOAT,
        ctx: context,
      };

      this.systemFloatWindow = await window.createWindow(config);
      
      // 设置窗口位置和大小
      await this.systemFloatWindow.moveTo(100, 200);
      await this.systemFloatWindow.resize(300, 150);
      
      // 设置窗口背景透明
      await this.systemFloatWindow.setWindowBackgroundColor('#00000000');
      
      // 设置窗口可触摸
      this.systemFloatWindow.setTouchable(true);
      
      console.info(this.tag, '系统浮窗创建成功');
      return this.systemFloatWindow;
    } catch (err) {
      const error = err as BusinessError;
      console.error(this.tag, `创建系统浮窗失败: ${error.message}`);
      return null;
    }
  }

  // 显示系统浮窗
  async showFloatWindow(): Promise<boolean> {
    if (!this.systemFloatWindow) {
      console.error(this.tag, '浮窗未创建');
      return false;
    }

    try {
      await this.systemFloatWindow.showWindow();
      console.info(this.tag, '系统浮窗显示成功');
      return true;
    } catch (err) {
      console.error(this.tag, `显示浮窗失败: ${JSON.stringify(err)}`);
      return false;
    }
  }

  // 隐藏系统浮窗
  async hideFloatWindow(): Promise<boolean> {
    if (!this.systemFloatWindow) {
      return false;
    }

    try {
      await this.systemFloatWindow.hideWindow();
      return true;
    } catch (err) {
      console.error(this.tag, `隐藏浮窗失败: ${JSON.stringify(err)}`);
      return false;
    }
  }

  // 销毁系统浮窗
  async destroyFloatWindow(): Promise<void> {
    if (!this.systemFloatWindow) {
      return;
    }

    try {
      await this.systemFloatWindow.destroyWindow();
      this.systemFloatWindow = null;
      console.info(this.tag, '系统浮窗已销毁');
    } catch (err) {
      console.error(this.tag, `销毁浮窗失败: ${JSON.stringify(err)}`);
    }
  }

  // 移动浮窗位置
  async moveFloatWindow(x: number, y: number): Promise<void> {
    if (!this.systemFloatWindow) {
      return;
    }

    try {
      await this.systemFloatWindow.moveTo(x, y);
    } catch (err) {
      console.error(this.tag, `移动浮窗失败: ${JSON.stringify(err)}`);
    }
  }
}

export default new SystemWindowManager();

进阶用法:系统弹窗实现

系统弹窗比浮窗层级更高,用于紧急通知和确认操作:

// common/SystemAlertDialog.ets - 系统告警弹窗
import window from '@ohos.window';
import { BusinessError } from '@ohos.base';

type DialogCallback = () => void;

interface AlertDialogConfig {
  title: string;
  message: string;
  positiveText?: string;
  negativeText?: string;
  onPositive?: DialogCallback;
  onNegative?: DialogCallback;
  autoDismiss?: boolean;
  timeout?: number; // 自动消失时间(毫秒)
}

class SystemAlertDialog {
  private tag: string = 'SystemAlertDialog';
  private alertWindow: window.Window | null = null;
  private autoDismissTimer: number = -1;

  // 显示系统告警弹窗
  async show(context: Context, config: AlertDialogConfig): Promise<boolean> {
    try {
      // 先销毁已有的弹窗
      await this.dismiss();

      // 创建告警窗口
      const windowConfig: window.Configuration = {
        name: 'SystemAlertDialog',
        windowType: window.WindowType.TYPE_ALERT,
        ctx: context,
      };

      this.alertWindow = await window.createWindow(windowConfig);
      
      // 全屏显示
      const display = this.alertWindow.getProperties();
      await this.alertWindow.resize(display.windowRect.width, display.windowRect.height);
      await this.alertWindow.moveTo(0, 0);
      
      // 半透明背景
      await this.alertWindow.setWindowBackgroundColor('#80000000');
      this.alertWindow.setTouchable(true);

      // 设置UI内容(实际项目中通过setUIContent加载页面)
      // this.alertWindow.setUIContent('pages/SystemDialogPage', () => {});
      
      await this.alertWindow.showWindow();
      
      // 自动消失
      if (config.autoDismiss && config.timeout) {
        this.autoDismissTimer = setTimeout(() => {
          this.dismiss();
        }, config.timeout) as unknown as number;
      }

      console.info(this.tag, `系统弹窗显示: ${config.title}`);
      return true;
    } catch (err) {
      const error = err as BusinessError;
      console.error(this.tag, `显示系统弹窗失败: ${error.message}`);
      return false;
    }
  }

  // 关闭弹窗
  async dismiss(): Promise<void> {
    if (this.autoDismissTimer !== -1) {
      clearTimeout(this.autoDismissTimer);
      this.autoDismissTimer = -1;
    }

    if (this.alertWindow) {
      try {
        await this.alertWindow.destroyWindow();
        this.alertWindow = null;
        console.info(this.tag, '系统弹窗已关闭');
      } catch (err) {
        console.error(this.tag, `关闭弹窗失败: ${JSON.stringify(err)}`);
      }
    }
  }

  // 更新弹窗内容
  async updateContent(title: string, message: string): Promise<void> {
    if (!this.alertWindow) {
      return;
    }
    // 实际项目中通过状态变量更新UI
    console.info(this.tag, `更新弹窗内容: ${title}`);
  }
}

export default new SystemAlertDialog();

完整示例:可拖拽悬浮球

一个可拖拽的系统悬浮球,点击展开功能面板:

// pages/FloatingBallPage.ets - 悬浮球页面(加载到系统浮窗中)
import window from '@ohos.window';

@Entry
@Component
struct FloatingBallPage {
  @State isExpanded: boolean = false;
  @State ballX: number = 300;
  @State ballY: number = 800;
  @State offsetX: number = 0;
  @State offsetY: number = 0;

  // 悬浮球菜单项
  private menuItems = [
    { icon: '🏠', label: '主页', action: 'home' },
    { icon: '🔒', label: '锁屏', action: 'lock' },
    { icon: '📸', label: '截图', action: 'screenshot' },
    { icon: '🔦', label: '手电筒', action: 'flashlight' },
  ];

  build() {
    Stack() {
      if (this.isExpanded) {
        // 展开菜单
        Column() {
          Text('快捷操作')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#333333')
            .margin({ bottom: 16 })

          Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) {
            ForEach(this.menuItems, (item: Record<string, string>) => {
              Column() {
                Text(item.icon)
                  .fontSize(28)
                Text(item.label)
                  .fontSize(11)
                  .fontColor('#666666')
                  .margin({ top: 4 })
              }
              .width(70)
              .height(70)
              .justifyContent(FlexAlign.Center)
              .backgroundColor('#ffffff')
              .borderRadius(12)
              .margin(8)
              .shadow({ radius: 4, color: '#1a000000', offsetY: 2 })
              .onClick(() => {
                this.handleMenuAction(item.action);
              })
            }, (item: Record<string, string>) => item.action)
          }

          // 关闭按钮
          Text('收起')
            .fontSize(13)
            .fontColor('#999999')
            .margin({ top: 16 })
            .onClick(() => {
              this.isExpanded = false;
            })
        }
        .width(200)
        .padding(16)
        .backgroundColor('#f5f5f5')
        .borderRadius(16)
        .shadow({ radius: 8, color: '#33000000', offsetY: 4 })
      } else {
        // 悬浮球
        Stack() {
          Circle({ width: 48, height: 48 })
            .fill('#1976d2')
            .shadow({ radius: 4, color: '#331976d2', offsetY: 2 })
          
          Text('⚡')
            .fontSize(24)
            .fontColor('#ffffff')
        }
        .width(56)
        .height(56)
        .gesture(
          GestureGroup(GestureMode.Exclusive,
            // 拖拽手势
            PanGesture()
              .onActionStart((event) => {
                this.offsetX = 0;
                this.offsetY = 0;
              })
              .onActionUpdate((event) => {
                this.ballX += event.offsetX - this.offsetX;
                this.ballY += event.offsetY - this.offsetY;
                this.offsetX = event.offsetX;
                this.offsetY = event.offsetY;
              })
              .onActionEnd(() => {
                // 吸附到屏幕边缘
                this.snapToEdge();
              }),
            // 点击手势
            TapGesture()
              .onAction(() => {
                this.isExpanded = true;
              })
          )
        )
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignContent(Alignment.Center)
  }

  // 吸附到屏幕边缘
  private snapToEdge() {
    const screenWidth = 360; // 实际使用时动态获取
    if (this.ballX < screenWidth / 2) {
      this.ballX = 20;
    } else {
      this.ballX = screenWidth - 76;
    }
  }

  // 处理菜单操作
  private handleMenuAction(action: string) {
    console.info('FloatingBall', `执行操作: ${action}`);
    this.isExpanded = false;
    
    switch (action) {
      case 'home':
        // 返回主页
        break;
      case 'lock':
        // 锁屏
        break;
      case 'screenshot':
        // 截图
        break;
      case 'flashlight':
        // 手电筒
        break;
    }
  }
}

踩坑与注意事项

坑一:没有系统签名创建系统浮窗失败

TYPE_SYSTEM_FLOATTYPE_ALERT窗口类型需要系统签名。普通应用调用createWindow时传入这些类型,直接抛异常。

如果你只是想在应用内显示悬浮窗,用TYPE_FLOAT类型,配合ohos.permission.SYSTEM_FLOAT_WINDOW权限就行。不需要系统签名。

坑二:系统浮窗遮挡键盘

系统浮窗的z-order很高,连输入法键盘都能挡住。用户在别的应用里打字,你的浮窗把键盘盖住了,这体验就废了。

解决办法:监听键盘弹出事件,键盘弹出时把浮窗往上移:

// 监听键盘避让区域变化
mainWindow.on('avoidAreaChange', (data) => {
  if (data.type === window.AvoidAreaType.TYPE_KEYBOARD) {
    const keyboardHeight = data.area.bottomRect.height;
    if (keyboardHeight > 0) {
      // 键盘弹出,浮窗上移
      this.moveFloatWindow(this.ballX, this.ballY - keyboardHeight);
    } else {
      // 键盘收起,浮窗回位
      this.moveFloatWindow(this.ballX, this.originalBallY);
    }
  }
});

坑三:悬浮窗触摸事件穿透问题

你创建了悬浮窗,但触摸事件穿透到了下面的应用,悬浮窗自己反而收不到触摸。

原因:窗口的touchable属性默认是false。需要手动设置为true

// 创建窗口后设置可触摸
this.systemFloatWindow.setTouchable(true);

反过来,如果你想让悬浮窗不拦截触摸事件(比如只是一个显示型的浮窗),保持false就行。

坑四:系统弹窗无法覆盖状态栏

你创建了TYPE_ALERT类型的窗口,但发现状态栏区域还是显示在最上层,弹窗被状态栏挡住了。

原因:TYPE_ALERT窗口默认不覆盖系统栏区域。需要额外设置全屏布局:

// 让弹窗覆盖状态栏区域
await this.alertWindow.setWindowLayoutFullScreen(true);

坑五:多个系统窗口的层级冲突

你同时创建了系统浮窗和系统弹窗,但发现弹窗被浮窗挡住了。

原因:同类型窗口的z-order由创建顺序决定,后创建的在上层。不同类型窗口的z-order由类型决定。

解决办法:如果需要精确控制层级,使用setWindowZLevel接口(需要HarmonyOS 6+):

// 设置窗口层级(数值越大越在上层)
await this.alertWindow.setWindowZLevel(100);
await this.floatWindow.setWindowZLevel(50);

HarmonyOS 6适配说明

HarmonyOS 6在系统级窗口方面有几个重要变化:

  1. 窗口层级精确控制:新增setWindowZLevel接口,可以精确设置窗口的z-order值,不再依赖创建顺序。

  2. 系统弹窗模板:新增SystemDialog接口,提供预置的弹窗模板(确认框、输入框、选择框),不需要自己写UI。

// HarmonyOS 6 系统弹窗模板
import systemDialog from '@ohos.systemDialog';

const dialog = new systemDialog.ConfirmDialog();
dialog.title = '系统警告';
dialog.message = '存储空间不足,请清理';
dialog.positiveButton = { text: '立即清理', action: () => {} };
dialog.negativeButton = { text: '稍后', action: () => {} };
await dialog.show();
  1. 悬浮窗安全区域适配:系统浮窗新增setFloatingWindowSafeArea接口,自动避开状态栏和导航栏区域。

  2. 窗口动画增强:系统级窗口支持自定义入场/退场动画,可以设置弹簧动画、渐变动画等效果。

适配建议:新项目优先使用SystemDialog模板创建系统弹窗,减少自定义UI的工作量。使用setWindowZLevel精确控制多窗口层级。

总结

系统级窗口是系统应用的"特权武器",能穿透应用层显示弹窗和悬浮窗。但窗口层级、类型选择、触摸事件、权限要求——每个环节都有坑。

核心要点回顾:

  • 系统浮窗用TYPE_SYSTEM_FLOAT,系统弹窗用TYPE_ALERT,都需要系统签名
  • 窗口层级从低到高:应用主窗口 → 应用浮窗 → 系统浮窗 → 系统告警窗
  • 悬浮窗要设置touchable(true),否则收不到触摸事件
  • 注意键盘遮挡问题,监听避让区域变化调整位置
  • 多窗口场景用setWindowZLevel控制层级
维度 评价
学习难度 ⭐⭐⭐⭐ 窗口层级和触摸事件处理比较复杂
使用频率 ⭐⭐⭐ 来电弹窗、悬浮球、系统通知等场景必用
重要程度 ⭐⭐⭐⭐ 系统级交互的核心能力
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。