引言

窗口是应用与用户交互的载体。在 HarmonyOS 应用开发中,窗口管理不仅关乎界面呈现,还涉及亮度控制、屏幕方向、折叠屏适配等实用场景。@ohos.window 模块提供了完整的窗口管理能力,让开发者可以在运行时查询和操控窗口状态。

本文从 API 架构、核心用法到实战 Demo,全面解析 @ohos.window 的窗口管理能力。所有代码基于 HarmonyOS NEXT API 24,DevEco Studio 6.1.1 实测通过。

@ohos.window 模块概览

导入方式

import window from '@ohos.window';

@kit.ArkUI 默认导入,与 @ohos.curves@ohos.process 等模块的导入方式一致。无需声明任何权限。

核心架构

模块的核心是一个三层架构:

层次 入口/对象 职责
获取窗口 window.getLastWindow(ctx) 返回当前页面对应的 Window 实例
窗口操作 Window 实例 查属性、设亮度、设方向、监听事件
数据结构 WindowPropertiesOrientation 承载窗口元数据

整个流程可以概括为:先通过 getLastWindow() 拿到窗口实例,再调用实例方法操作窗口。由于 getLastWindow() 返回 Promise,实际开发中需要处理异步逻辑。

核心 API 详解

1. getLastWindow(ctx) — 获取窗口实例

window.getLastWindow(getContext())
  .then((win: window.Window) => {
    // 窗口实例就绪,可以调用后续方法
  })
  .catch(() => {
    // 获取失败,可能是上下文类型不匹配
  });

参数ctx 是当前页面的上下文,通过 getContext() 获取。在 API 24 中,ARKUI 页面的 getContext() 可以直接传递给 getLastWindow()

返回值Promise<Window>。在 .then() 回调中拿到 Window 实例后,即可调用窗口的所有方法。

注意getLastWindow() 获取的是当前 UIAbility 的最后显示窗口。在多窗口场景下,这通常是你正在交互的那个窗口。

2. getWindowProperties() — 读取窗口属性

const prop: window.WindowProperties = win.getWindowProperties();

WindowProperties 对象包含以下关键字段:

字段 类型 说明
type WindowType 窗口类型:标准模式(0)、全屏(1)、分屏(2)、悬浮窗(3)、最小化(4)
brightness number 窗口亮度,0~1,-1 表示跟随系统调节
isFullScreen boolean 是否为全屏状态
isLayoutFullScreen boolean 是否为沉浸式全屏(状态栏和导航栏隐藏)

这些属性是同步获取的,不需要 Promise,调用后立即返回。

3. setWindowBrightness(brightness: number) — 调节亮度

win.setWindowBrightness(0.7)
  .then(() => { /* 设置成功 */ })
  .catch(() => { /* 设置失败,可能需要系统权限 */ });

参数brightness 范围为 0.01 ~ 1.0。设为 -1 表示恢复系统自动调节。

返回值Promise<void>。这意味着亮度设置是一个异步操作,需要在 Promise 中处理成功/失败情况。

限制:在某些设备上,应用可能没有权限修改系统亮度,此时调用会进入 .catch() 分支。因此建议使用时配合 try-catch 或 .catch() 做降级处理。

4. setPreferredOrientation(orientation: Orientation) — 设置方向

import window from '@ohos.window';

// 设置为竖屏
win.setPreferredOrientation(window.Orientation.PORTRAIT);

// 设置为横屏
win.setPreferredOrientation(window.Orientation.LANDSCAPE);

// 恢复自动旋转
win.setPreferredOrientation(window.Orientation.AUTO_ROTATION);

参数window.Orientation 枚举,常用值如下:

枚举值 含义
Orientation.PORTRAIT 强制竖屏(向上)
Orientation.PORTRAIT_INVERTED 强制竖屏(倒转)
Orientation.LANDSCAPE 强制横屏(左转)
Orientation.LANDSCAPE_INVERTED 强制横屏(右转)
Orientation.AUTO_ROTATION 跟随传感器自动旋转

返回值:同样返回 Promise<void>。设置方向可能受系统限制(如分屏模式下无法切换方向)。

5. on / off — 窗口事件监听

// 注册监听
win.on('windowSizeChange', (size: window.Size) => {
  console.log('窗口尺寸: ' + size.width + '×' + size.height);
});

// 取消监听(页面销毁时必须)
win.off('windowSizeChange');

支持的事件类型(WindowEventType):

事件名 触发时机
windowSizeChange 窗口尺寸发生变化(折叠屏展开/折叠、分屏调整、旋转等)
windowStatusChange 窗口状态变化(最大/最小化、焦点获得/失去)
avoidAreaChange 避让区域变化(状态栏、导航栏、刘海屏等)

最佳实践:在 aboutToAppear 中注册监听,在 aboutToDisappear 中取消监听,避免内存泄漏。

实战 Demo:窗口管理实验室

下面实现一个完整的窗口管理工具页面,包含 4 个功能区域:

  1. 窗口属性展示 — 实时显示方向、模式、亮度、全屏状态
  2. 亮度调节 — Slider 滑块 + 应用按钮
  3. 方向控制 — 三按钮切换竖屏/横屏/自动旋转
  4. 事件日志 — 监听窗口尺寸变化并记录历史

完整代码

import { router } from '@kit.ArkUI';
import window from '@ohos.window';
import { FontSize, Spacing } from '../common/Constants';

interface EventLog {
  time: string;
  msg: string;
}

@Entry
@Component
struct WindowLabPage {
  @State orientation: string = '加载中...';
  @State winBrightness: number = 0;
  @State winType: string = '加载中...';
  @State isFullScreen: string = '加载中...';
  @State brightnessSlider: number = 0.5;
  @State orientationIndex: number = 2;
  @State eventLogs: EventLog[] = [];
  @State loading: boolean = true;

  private win: window.Window | null = null;

  aboutToAppear(): void {
    window.getLastWindow(getContext())
      .then((w: window.Window) => {
        this.win = w;
        this.loadProperties();
        this.setupListener();
        this.loading = false;
      })
      .catch(() => {
        this.orientation = '获取失败';
        this.winType = '获取失败';
        this.loading = false;
        this.addLog('窗口获取失败,请检查 Context');
      });
  }

  aboutToDisappear(): void {
    if (this.win !== null) {
      this.win.off('windowSizeChange');
    }
  }

  private loadProperties(): void {
    if (this.win === null) {
      return;
    }
    try {
      const prop: window.WindowProperties = this.win.getWindowProperties();
      this.winType = this.typeName(prop.type);
      this.isFullScreen = prop.isFullScreen ? '是' : '否';
      this.winBrightness = prop.brightness;
      this.brightnessSlider = prop.brightness < 0 ? 0.5 : prop.brightness;
      this.orientation = '自动旋转';
      this.orientationIndex = 2;
    } catch (e) {
      this.orientation = '获取失败';
    }
  }

  private typeName(t: number): string {
    switch (t) {
      case 1: return '全屏模式';
      case 2: return '分屏模式';
      case 3: return '悬浮窗模式';
      case 4: return '最小化模式';
      default: return '标准模式 (' + t + ')';
    }
  }

  private setupListener(): void {
    if (this.win === null) {
      return;
    }
    try {
      this.win.on('windowSizeChange', (size: window.Size) => {
        this.addLog('窗口尺寸变更: ' + size.width + 'x' + size.height);
        this.loadProperties();
      });
      this.addLog('窗口事件监听已启动');
    } catch (e) {
      this.addLog('事件监听注册失败');
    }
  }

  private addLog(msg: string): void {
    const now: Date = new Date();
    const ts: string = now.getHours().toString().padStart(2, '0') + ':' +
      now.getMinutes().toString().padStart(2, '0') + ':' +
      now.getSeconds().toString().padStart(2, '0');
    const entry: EventLog = { time: ts, msg: msg };
    this.eventLogs = [entry].concat(this.eventLogs).slice(0, 30);
  }

  private setBrightness(): void {
    if (this.win === null) {
      this.addLog('窗口未就绪');
      return;
    }
    this.win.setWindowBrightness(this.brightnessSlider)
      .then(() => {
        this.addLog('亮度已设为 ' + this.brightnessSlider.toFixed(2));
        this.winBrightness = this.brightnessSlider;
      })
      .catch(() => {
        this.addLog('设置亮度失败(可能需要系统权限)');
      });
  }

  private setOrientation(index: number): void {
    if (this.win === null) {
      this.addLog('窗口未就绪');
      return;
    }
    this.orientationIndex = index;
    let orient: window.Orientation;
    let label: string;
    if (index === 0) {
      orient = window.Orientation.PORTRAIT;
      label = '竖屏';
    } else if (index === 1) {
      orient = window.Orientation.LANDSCAPE;
      label = '横屏';
    } else {
      orient = window.Orientation.AUTO_ROTATION;
      label = '自动旋转';
    }
    this.win.setPreferredOrientation(orient)
      .then(() => {
        this.orientation = label;
        this.addLog('方向已设为: ' + label);
      })
      .catch(() => {
        this.addLog('设置方向失败(可能需要系统权限)');
      });
  }

  private refreshAll(): void {
    this.addLog('刷新窗口属性');
    this.loadProperties();
  }

  build() {
    Column() {
      Row() {
        Text('<')
          .fontSize(28).fontColor('#FFFFFF')
          .onClick(() => { router.back(); })
        Text('窗口管理实验室')
          .fontSize(FontSize.TITLE).fontColor('#FFFFFF')
          .fontWeight(FontWeight.Bold).margin({ left: Spacing.MD })
        Blank()
        Text('@ohos.window')
          .fontSize(9).fontColor('#FFFFFFCC')
      }
      .width('100%')
      .padding({ left: Spacing.LG, right: Spacing.LG, top: 14, bottom: 14 })
      .backgroundColor('#0891B2')

      Scroll() {
        Column() {
          Text('窗口属性')
            .fontSize(FontSize.CAPTION).fontColor('#0891B2')
            .width('100%').margin({ bottom: 6 })

          Column() {
            this.propRow('当前方向', this.orientation, '#ECFEFF')
            this.propRow('窗口模式', this.winType, '#FFFFFF')
            this.propRow('屏幕亮度', this.winBrightness < 0 ? '跟随系统' :
              this.winBrightness.toFixed(2), '#ECFEFF')
            this.propRow('全屏状态', this.isFullScreen, '#FFFFFF')
            if (this.loading) {
              LoadingProgress()
                .width(24).height(24)
                .color('#0891B2')
                .margin({ top: 4 })
            }
          }
          .width('100%').padding(Spacing.MD)
          .backgroundColor('#FFFFFF').borderRadius(10)
          .margin({ bottom: Spacing.MD })

          Text('屏幕亮度调节')
            .fontSize(FontSize.CAPTION).fontColor('#0891B2')
            .width('100%').margin({ bottom: 6 })

          Column() {
            Row() {
              Slider({
                value: this.brightnessSlider,
                min: 0.01, max: 1.0, step: 0.01,
                style: SliderStyle.InSet
              })
                .layoutWeight(1)
                .onChange((v: number) => { this.brightnessSlider = v; })
              Text((this.brightnessSlider * 100).toFixed(0) + '%')
                .fontSize(16).fontColor('#0891B2')
                .fontWeight(FontWeight.Bold).fontFamily('monospace')
                .width(48).textAlign(TextAlign.End)
            }
            .width('100%')
            .margin({ bottom: 10 })

            Button('应用亮度')
              .fontSize(13).fontColor('#FFFFFF')
              .height(36).backgroundColor('#0891B2').borderRadius(8)
              .width('100%')
              .onClick(() => { this.setBrightness(); })
          }
          .width('100%').padding(Spacing.MD)
          .backgroundColor('#FFFFFF').borderRadius(10)
          .margin({ bottom: Spacing.MD })

          Text('屏幕方向控制')
            .fontSize(FontSize.CAPTION).fontColor('#0891B2')
            .width('100%').margin({ bottom: 6 })

          Column() {
            Row() {
              this.orientBtn('竖屏', 0)
              this.orientBtn('横屏', 1)
              this.orientBtn('自动旋转', 2)
            }
            .width('100%')
          }
          .width('100%').padding(Spacing.MD)
          .backgroundColor('#FFFFFF').borderRadius(10)
          .margin({ bottom: Spacing.MD })

          Text('窗口事件日志')
            .fontSize(FontSize.CAPTION).fontColor('#0891B2')
            .width('100%').margin({ bottom: 6 })

          Column() {
            if (this.eventLogs.length === 0) {
              Text('暂无事件')
                .fontSize(12).fontColor('#94A3B8')
                .width('100%').textAlign(TextAlign.Center)
                .padding({ top: 12, bottom: 12 })
            } else {
              ForEach(this.eventLogs, (log: EventLog) => {
                Row() {
                  Text(log.time)
                    .fontSize(10).fontColor('#94A3B8').fontFamily('monospace')
                    .width(50)
                  Text(log.msg)
                    .fontSize(11).fontColor('#0F172A')
                    .layoutWeight(1).maxLines(2)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                }
                .width('100%')
                .padding({ top: 5, bottom: 5 })
                .border({ width: { bottom: 0.5 }, color: '#E2E8F0' })
              })
            }
          }
          .width('100%').padding(Spacing.MD)
          .backgroundColor('#FFFFFF').borderRadius(10)
          .margin({ bottom: Spacing.MD })

          Button('刷新窗口属性')
            .fontSize(13).fontColor('#FFFFFF')
            .height(40).backgroundColor('#0891B2').borderRadius(10)
            .width('100%')
            .onClick(() => { this.refreshAll(); })

          Text('@ohos.window 提供窗口管理能力。核心 API:window.getLastWindow(ctx) 获取窗口实例 → 返回 Promise<Window>。Window 对象:getWindowProperties() 获取窗口属性(类型/亮度/全屏等)、setWindowBrightness() 调节屏幕亮度(0~1)、setPreferredOrientation() 设置屏幕方向(竖屏/横屏/自动)、on/off 监听 windowSizeChange 窗口尺寸变化事件。需注意:部分操作(设置方向、亮度)受系统保护和设备限制,可能无效果或抛异常。')
            .fontSize(FontSize.CAPTION)
            .fontColor('#64748B')
            .width('100%')
            .margin({ top: Spacing.MD, bottom: Spacing.XXL })
        }
        .width('100%')
        .padding({ left: Spacing.LG, right: Spacing.LG, top: Spacing.MD, bottom: Spacing.MD })
      }
      .layoutWeight(1).scrollBar(BarState.Off)
      .backgroundColor('#F2F3F5')
    }
    .width('100%').height('100%')
    .backgroundColor('#F2F3F5')
  }

  @Builder
  propRow(label: string, value: string, bgColor: string) {
    Row() {
      Text(label)
        .fontSize(12).fontColor('#64748B').layoutWeight(1)
      Text(value)
        .fontSize(13).fontColor('#0F172A')
        .fontWeight(FontWeight.Bold).fontFamily('monospace')
    }
    .width('100%')
    .padding({ left: 10, right: 10, top: 8, bottom: 8 })
    .backgroundColor(bgColor).borderRadius(6)
    .margin({ bottom: 4 })
  }

  @Builder
  orientBtn(label: string, index: number) {
    Text(label)
      .fontSize(12)
      .fontColor(this.orientationIndex === index ? '#FFFFFF' : '#0891B2')
      .fontWeight(this.orientationIndex === index ? FontWeight.Bold : FontWeight.Normal)
      .padding({ left: 14, right: 14, top: 8, bottom: 8 })
      .borderRadius(6)
      .backgroundColor(this.orientationIndex === index ? '#0891B2' : '#ECFEFF')
      .margin({ right: 8 })
      .onClick(() => { this.setOrientation(index); })
  }
}

页面结构解析

标题栏:使用 #0891B2(cyan-600)作为主色调,与之前的实验室页面形成视觉区分。左侧返回按钮 + 标题"窗口管理实验室" + 右上角模块名。

窗口属性卡片(交互点 1 — 属性读取):展示 4 个关键属性——当前方向、窗口模式、屏幕亮度、全屏状态。加载时显示 LoadingProgress 动画,窗口就绪后通过 getWindowProperties() 同步读取所有属性。注意 brightness 字段与内置组件方法冲突,我们在代码中命名为 winBrightness

亮度调节卡片(交互点 2 — 亮度控制):使用 SliderStyle.InSet 样式滑块,范围 0.01~1.0,步进 0.01。右侧百分比数字实时跟随滑块。点击"应用亮度"调用 setWindowBrightness(),成功后更新属性卡片中的亮度显示。

方向控制卡片(交互点 3 — 方向切换):三个按钮(竖屏/横屏/自动旋转),选中态高亮(白字 + 主题色背景),未选中态(主题色字 + 浅色背景)。基于 orientationIndex 驱动视觉状态。每个按钮调用 setPreferredOrientation(),结果在事件日志中反馈。

事件日志卡片(交互点 4 — 事件监听):在 setupListener() 中注册 windowSizeChange 监听。每次窗口尺寸变化时,将尺寸和时间戳写入日志列表。日志最多保留 30 条,最新在前。页面销毁时在 aboutToDisappear()off('windowSizeChange') 清理。

关键注意点

1. brightness 属性名冲突

ArkTS 中,@State brightness: number 会与 ArkUI 内置的 .brightness() 视觉属性方法冲突,导致编译错误:

Property 'brightness' in type 'WindowLabPage' is not assignable...
Type 'number' is not assignable to type '{ (value: number): CommonAttribute; }'

解决方案:将状态变量重命名为 winBrightness 或其他不与内置属性重名的名称。
在这里插入图片描述
在这里插入图片描述

2. getContext() 的上下文限制

在 API 24 中,getContext() 在新版本 SDK 中可能被标记为 deprecated,推荐使用组件自带的 getUIContext() 方法。不过 getLastWindow() 目前仍接受 getContext() 的返回值。如果未来版本不再支持,需要改用 this.getUIContext().getHostContext() 等替代方案。

3. setPreferredOrientation 不是强制的

调用 setPreferredOrientation() 只是"建议"系统使用某个方向,不保证一定生效。例如在分屏模式下,系统可能忽略方向设定。如果设置失败,Promise 会进入 .catch(),此时应在日志中反馈而不是静默失败。

4. 页面销毁时必须取消监听

窗口事件的监听器是全局注册的,不会随页面退出自动解绑。如果不在 aboutToDisappear() 中调用 off(),当用户多次进入/退出页面时,会产生大量僵尸监听器,最终导致内存泄漏和不必要的回调触发。

5. WindowProperties 字段可用性

并非 .d.ts 中声明的所有 WindowProperties 字段都在运行时有效。部分字段(如 orientation)在当前版本 SDK 中声明存在但实际运行时属性不可读,访问会触发异常。Demo 中通过 loadProperties() 包裹 try-catch 来防御。

适用场景

  • 阅读器/视频播放器:通过 setPreferredOrientation 锁定横屏/竖屏
  • 系统设置类应用:通过 setWindowBrightness 提供亮度调节
  • 折叠屏适配:通过 windowSizeChange 监听窗口尺寸变化,动态调整布局
  • 分屏模式调试:通过 getWindowProperties() 检测当前窗口模式
  • 沉浸式体验:通过 isFullScreen 检测全屏状态,配合 setWindowLayoutFullScreen 实现沉浸式

总结

@ohos.window 是一个基础但实用的模块。它的 API 数量不多——核心就是 getLastWindow() 获取窗口,然后通过 Window 实例操作属性和注册监听——但覆盖了窗口管理的核心场景。

相比其他模块(如 @ohos.process 的零权限同步 API),窗口模块的特点是异步 Promise 风格,这要求开发者在 .then() / .catch() 中正确处理成功和失败路径。同时它涉及上下文获取和生命周期管理,是一个考验开发者工程素质的模块。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐