HarmonyOS APP窗口属性:从 WindowStage 到窗口状态监听,掌控你的应用窗口

举报
Jack20 发表于 2026/06/20 14:42:35 2026/06/20
【摘要】 HarmonyOS APP窗口属性:从 WindowStage 到窗口状态监听,掌控你的应用窗口📌 核心要点:WindowStage 是窗口的"舞台经理",WindowClass 是窗口的"属性管家",理解两者的协作机制是掌控窗口属性的关键一、背景与动机想象一下你正在装修一套房子。WindowStage 就像是房子的"舞台"——它决定了房子里能放什么家具、怎么布局;而 WindowClas...

HarmonyOS APP窗口属性:从 WindowStage 到窗口状态监听,掌控你的应用窗口

📌 核心要点:WindowStage 是窗口的"舞台经理",WindowClass 是窗口的"属性管家",理解两者的协作机制是掌控窗口属性的关键


一、背景与动机

想象一下你正在装修一套房子。WindowStage 就像是房子的"舞台"——它决定了房子里能放什么家具、怎么布局;而 WindowClass 则像是房子的"属性面板"——窗户朝哪开、灯有多亮、门有多宽,全归它管。

HarmonyOS 的窗口体系中,很多开发者一上来就懵了:WindowStage WindowClass 到底啥关系?窗口大小怎么改?亮度怎么调?屏幕方向怎么锁定?窗口状态变化了怎么监听?这些问题看似零散,实则都围绕着一个核心——窗口属性的管理与配置

如果你只是简单地 loadContent 加载个页面,那窗口属性用默认值也凑合。但当你需要做沉浸式状态栏、自适应布局、多窗口联动这些进阶功能时,不理解窗口属性就寸步难行了。

这篇文章,咱们就把窗口属性这件事掰开揉碎,从 WindowStage 的生命周期到 WindowClass 的每一个属性方法,一次性搞明白。


二、核心原理

2.1 WindowStage WindowClass 的关系

先上一张关系图,把这两位的"职责边界"画清楚:

简单来说:

WindowStage:窗口的"舞台",负责加载页面内容、管理窗口的生命周期回调。每个 Ability 都有自己的 WindowStage。

WindowClass:窗口的"属性管家",通过 WindowStage.getMainWindow() 获取,负责管理窗口的各种属性。

打个比方:WindowStage 是"舞台经理",负责搭台唱戏;WindowClass 是"道具师",负责调整舞台上的灯光、布景、幕布。

2.2 窗口属性全景图

2.3 窗口属性配置的时序

窗口属性的配置不是"想设就设"的,它有一个时序要求。核心原则:必须在 WindowStage 创建之后、UI 加载之前或之后进行属性设置


三、代码实战

3.1 完整的窗口属性初始化

这是一个在 Ability 中初始化窗口属性的完整示例,涵盖大小、方向、亮度、全屏等常见配置:

// WindowPropertyAbility.ets
import { UIAbility, AbilityConstant, WindowStage } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

export default class WindowPropertyAbility extends UIAbility {
// 保存窗口实例,方便后续使用
private mainWindow: window.Window | null = null;

onWindowStageCreate(windowStage: WindowStage): void {
console.info('[WindowProperty] onWindowStageCreate');

// 1. 获取主窗口
windowStage.getMainWindow().then((win: window.Window) => {
this.mainWindow = win;
this.configureWindow(win);
}).catch((err: BusinessError) => {
console.error(`[WindowProperty] 获取主窗口失败: ${err.code} - ${err.message}`);
});

// 2. 加载页面内容
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
console.error(`[WindowProperty] 加载内容失败: ${err.code}`);
return;
}
console.info('[WindowProperty] 页面加载成功');
});
}

/**
* 配置窗口属性的核心方法
* 包含:全屏、方向、亮度、屏幕常亮
*/
private configureWindow(win: window.Window): void {
try {
// 设置全屏布局(内容延伸到状态栏和导航栏区域)
win.setWindowLayoutFullScreen(true);

// 设置窗口方向为竖屏
win.setPreferredOrientation(window.Orientation.PORTRAIT);

// 设置窗口亮度(0.0 ~ 1.0),-1表示跟随系统
win.setWindowBrightness(0.8);

// 设置屏幕常亮(阅读、视频场景常用)
win.setWindowKeepScreenOn(true);

console.info('[WindowProperty] 窗口属性配置完成');
} catch (err) {
const e = err as BusinessError;
console.error(`[WindowProperty] 配置窗口属性失败: ${e.code} - ${e.message}`);
}
}

onWindowStageDestroy(): void {
// 释放窗口引用
this.mainWindow = null;
console.info('[WindowProperty] onWindowStageDestroy');
}
}

3.2 窗口状态监听与响应式适配

在实际开发中,窗口大小变化(比如折叠屏展开/折叠、分屏切换)是必须处理的场景。这个示例展示如何监听窗口状态变化并做出响应:

// WindowStatusListener.ets
import { UIAbility, WindowStage } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

export default class WindowStatusListenerAbility extends UIAbility {
private mainWindow: window.Window | null = null;

onWindowStageCreate(windowStage: WindowStage): void {
windowStage.getMainWindow().then((win: window.Window) => {
this.mainWindow = win;
this.setupWindowListeners(win);
}).catch((err: BusinessError) => {
console.error(`[WindowStatus] 获取窗口失败: ${err.message}`);
});

windowStage.loadContent('pages/Index');
}

/**
* 注册窗口状态监听
* 包括:大小变化、避让区域变化、窗口激活/失活
*/
private setupWindowListeners(win: window.Window): void {
// 监听窗口大小变化(折叠屏、分屏、旋转等场景)
win.on('windowSizeChange', (size: window.Size) => {
console.info(`[WindowStatus] 窗口大小变化: 宽=${size.width}, 高=${size.height}`);
this.handleWindowSizeChange(size);
});

// 监听避让区域变化(状态栏/导航栏显示隐藏、键盘弹出等)
win.on('avoidAreaChange', (data: window.AvoidAreaInfo) => {
console.info(`[WindowStatus] 避让区域变化: type=${data.type}`);
this.handleAvoidAreaChange(data);
});

// 监听窗口激活状态变化
win.on('windowStatusChange', (status: window.WindowStatus) => {
const statusMap: Record<number, string> = {
[window.WindowStatus.FOCUSED]: '获得焦点',
[window.WindowStatus.UNFOCUSED]: '失去焦点',
};
console.info(`[WindowStatus] 窗口状态: ${statusMap[status] || status}`);
});
}

/**
* 处理窗口大小变化
* 根据宽高比判断当前布局模式
*/
private handleWindowSizeChange(size: window.Size): void {
const ratio = size.width / size.height;
if (ratio > 1.2) {
console.info('[WindowStatus] 横屏模式,建议使用双栏布局');
} else {
console.info('[WindowStatus] 竖屏模式,建议使用单栏布局');
}
}

/**
* 处理避让区域变化
* 主要是键盘弹出时调整布局
*/
private handleAvoidAreaChange(data: window.AvoidAreaInfo): void {
if (data.type === window.AvoidAreaType.TYPE_KEYBOARD) {
const keyboardHeight = data.area.bottom;
console.info(`[WindowStatus] 键盘高度: ${keyboardHeight}`);
// 通知UI层调整布局(通过AppStorage或Emitter)
AppStorage.setOrCreate('keyboardHeight', keyboardHeight);
}
}

onWindowStageDestroy(): void {
// 移除所有监听,避免内存泄漏
if (this.mainWindow) {
try {
this.mainWindow.off('windowSizeChange');
this.mainWindow.off('avoidAreaChange');
this.mainWindow.off('windowStatusChange');
} catch (err) {
const e = err as BusinessError;
console.error(`[WindowStatus] 移除监听失败: ${e.message}`);
}
}
this.mainWindow = null;
}
}

3.3 窗口属性的动态调整(UI层控制)

有时候我们需要在 UI 页面中动态调整窗口属性,比如视频播放时切换横竖屏、阅读时调节亮度。这个示例展示如何在 UI 层操作窗口属性:

// WindowDynamicControl.ets
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';

@Entry
@Component
struct WindowDynamicControlPage {
// 当前窗口亮度
@State currentBrightness: number = 0.5;
// 当前是否全屏
@State isFullScreen: boolean = true;
// 当前方向
@State currentOrientation: string = '竖屏';
// 屏幕是否常亮
@State isScreenOn: boolean = false;

// 获取当前窗口实例
private getWindow(): window.Window | null {
const context = getContext(this) as common.UIAbilityContext;
try {
return window.findWindow(context.abilityInfo.name);
} catch (e) {
console.error('[WindowDynamic] 获取窗口失败');
return null;
}
}

build() {
Column({ space: 20 }) {
// 标题
Text('窗口属性动态控制')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 60 })

// 亮度调节
Row({ space: 12 }) {
Text('亮度')
.fontSize(16)
.width(50)
Slider({
value: this.currentBrightness * 100,
min: 10,
max: 100,
step: 5,
style: SliderStyle.OutSet
})
.width('60%')
.onChange((value: number) => {
this.currentBrightness = value / 100;
this.updateBrightness(this.currentBrightness);
})
Text(`${Math.round(this.currentBrightness * 100)}%`)
.fontSize(14)
.width(50)
}
.width('90%')
.padding(16)
.borderRadius(12)
.backgroundColor('#1a1a2e')

// 方向切换
Row({ space: 20 }) {
Button('竖屏')
.onClick(() => this.setOrientation(window.Orientation.PORTRAIT))
.backgroundColor(this.currentOrientation === '竖屏' ? '#4CAF50' : '#333')
Button('横屏')
.onClick(() => this.setOrientation(window.Orientation.LANDSCAPE))
.backgroundColor(this.currentOrientation === '横屏' ? '#4CAF50' : '#333')
Button('自动旋转')
.onClick(() => this.setOrientation(window.Orientation.AUTO_ROTATION))
.backgroundColor(this.currentOrientation === '自动' ? '#4CAF50' : '#333')
}

// 全屏切换
Toggle({ type: ToggleType.Switch, isOn: this.isFullScreen })
.onChange((isOn: boolean) => {
this.isFullScreen = isOn;
this.toggleFullScreen(isOn);
})
Text(`全屏模式: ${this.isFullScreen ? '开启' : '关闭'}`)
.fontSize(14)

// 屏幕常亮
Toggle({ type: ToggleType.Switch, isOn: this.isScreenOn })
.onChange((isOn: boolean) => {
this.isScreenOn = isOn;
this.toggleKeepScreenOn(isOn);
})
Text(`屏幕常亮: ${this.isScreenOn ? '开启' : '关闭'}`)
.fontSize(14)

// 查询窗口属性
Button('查询窗口属性')
.width('80%')
.backgroundColor('#2196F3')
.onClick(() => this.queryWindowProperties())
}
.width('100%')
.height('100%')
.backgroundColor('#0d0d1a')
.foregroundColor('#fff')
}

/**
* 更新窗口亮度
*/
private updateBrightness(brightness: number): void {
const win = this.getWindow();
if (!win) return;
try {
win.setWindowBrightness(brightness);
console.info(`[WindowDynamic] 亮度设置为: ${brightness}`);
} catch (e) {
console.error('[WindowDynamic] 设置亮度失败');
}
}

/**
* 设置窗口方向
*/
private setOrientation(orientation: window.Orientation): void {
const win = this.getWindow();
if (!win) return;
try {
win.setPreferredOrientation(orientation);
const orientationMap: Record<number, string> = {
[window.Orientation.PORTRAIT]: '竖屏',
[window.Orientation.LANDSCAPE]: '横屏',
[window.Orientation.AUTO_ROTATION]: '自动',
};
this.currentOrientation = orientationMap[orientation] || '未知';
console.info(`[WindowDynamic] 方向设置为: ${this.currentOrientation}`);
} catch (e) {
console.error('[WindowDynamic] 设置方向失败');
}
}

/**
* 切换全屏模式
*/
private toggleFullScreen(isFullScreen: boolean): void {
const win = this.getWindow();
if (!win) return;
try {
win.setWindowLayoutFullScreen(isFullScreen);
console.info(`[WindowDynamic] 全屏模式: ${isFullScreen}`);
} catch (e) {
console.error('[WindowDynamic] 设置全屏失败');
}
}

/**
* 切换屏幕常亮
*/
private toggleKeepScreenOn(isOn: boolean): void {
const win = this.getWindow();
if (!win) return;
try {
win.setWindowKeepScreenOn(isOn);
console.info(`[WindowDynamic] 屏幕常亮: ${isOn}`);
} catch (e) {
console.error('[WindowDynamic] 设置常亮失败');
}
}

/**
* 查询并打印窗口属性
*/
private queryWindowProperties(): void {
const win = this.getWindow();
if (!win) return;
try {
const props = win.getWindowProperties();
console.info(`[WindowDynamic] 窗口属性:`);
console.info(` - 窗口矩形: ${JSON.stringify(props.windowRect)}`);
console.info(` - 是否全屏: ${props.isFullScreen}`);
console.info(` - 亮度: ${props.brightness}`);
console.info(` - 是否常亮: ${props.isKeepScreenOn}`);
console.info(` - 方向: ${props.orientation}`);
} catch (e) {
console.error('[WindowDynamic] 查询属性失败');
}
}
}


四、踩坑与注意事项

4.1 WindowStage 的时序陷阱

:在 onCreate 中获取 WindowStage?不存在的!

WindowStage 是在 onWindowStageCreate 回调中才可用的。很多新手在 onCreate 里就尝试获取窗口,结果直接报错。正确的做法:

// ❌ 错误:onCreate 中没有 WindowStage
onCreate(want, launchParam) {
// 这里拿不到 WindowStage!
}

// ✅ 正确:在 onWindowStageCreate 中操作
onWindowStageCreate(windowStage: WindowStage) {
windowStage.getMainWindow().then(win => {
// 在这里配置窗口属性
});
}

4.2 setWindowLayoutFullScreen 的"假全屏"

:调了 setWindowLayoutFullScreen(true),但内容还是被状态栏遮挡。

这是因为"全屏布局"只是让内容区域延伸到状态栏和导航栏下方,并不会自动帮你避开它们。你需要手动获取避让区域并设置 padding:

// ✅ 正确做法:全屏 + 避让
win.setWindowLayoutFullScreen(true);

// 获取系统避让区域(状态栏高度)
const avoidArea = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
const statusBarHeight = avoidArea.topRect.height;

// 通过 AppStorage 传递给 UI 层
AppStorage.setOrCreate('statusBarHeight', px2vp(statusBarHeight));

4.3 亮度设置不生效

:调了 setWindowBrightness(0.8),但屏幕亮度没变化。

可能的原因:

1. 系统开启了自动亮度:某些设备上自动亮度会覆盖应用设置,需要用户手动关闭

2. 窗口未获取焦点:只有前台窗口的亮度设置才会生效

3. 参数范围错误:亮度值范围是 0.0 ~ 1.0,超出范围会被忽略

4.4 方向设置的"延迟"

:调了 setPreferredOrientation(PORTRAIT),但页面还是横屏显示了一瞬间。

这是因为方向切换是异步的,系统需要时间旋转屏幕。如果页面布局依赖方向,建议:

• 在 onWindowStageCreate 中尽早设置方向

• 监听 windowSizeChange 事件,在回调中更新布局

4.5 监听器泄漏

:页面销毁时忘记 off 掉窗口监听器,导致内存泄漏和重复回调。

// ❌ 错误:只注册不注销
win.on('windowSizeChange', callback);

// ✅ 正确:在 onWindowStageDestroy 中注销
onWindowStageDestroy() {
this.mainWindow?.off('windowSizeChange');
this.mainWindow?.off('avoidAreaChange');
}


五、HarmonyOS 6 适配

5.1 API 变化

属性/方法

HarmonyOS 5.0

HarmonyOS 6.0

setWindowBrightness

支持

支持,新增亮度渐变动画参数

setPreferredOrientation

支持

支持,新增 LOCKED 锁定方向模式

getWindowAvoidArea

支持

支持,新增 TYPE_CUTOUT 刘海屏避让

on('windowSizeChange')

返回 Size

返回 Size,新增 displayId 区分多屏

setWindowLayoutFullScreen

支持

支持,新增沉浸式模式枚举

5.2 迁移要点

1. 亮度渐变:HarmonyOS 6 新增了亮度渐变能力,旧代码的 setWindowBrightness 无需修改,但可以利用新参数实现更平滑的亮度切换

2. 方向锁定:新增 Orientation.LOCKED 模式,锁定当前方向不随设备旋转变化,适合游戏类应用

3. 多屏支持:windowSizeChange 回调新增 displayId,如果你的应用需要适配多屏设备,需要关注这个变化

4. 避让区域:新增 TYPE_CUTOUT 类型,用于获取刘海屏/挖孔屏的避让区域

5.3 兼容性写法

// 兼容 HarmonyOS 5.0 和 6.0 的窗口属性配置
private configureWindowCompat(win: window.Window): void {
// 基础属性(5.0+ 都支持)
win.setWindowLayoutFullScreen(true);
win.setPreferredOrientation(window.Orientation.PORTRAIT);

// 检查 API 版本后使用新特性
const apiVersion = deviceInfo.osFullName;
if (apiVersion >= '6.0.0') {
// HarmonyOS 6.0 新增:锁定当前方向
// win.setPreferredOrientation(window.Orientation.LOCKED);
}
}


六、总结

知识点

核心内容

关键API

WindowStage

窗口舞台,管理页面加载

loadContent, getMainWindow

WindowClass

窗口属性管家

setWindowBrightness, setPreferredOrientation

窗口大小

全屏布局与避让区域

setWindowLayoutFullScreen, getWindowAvoidArea

窗口位置

子窗口位置控制

moveWindowTo

窗口亮度

屏幕亮度与常亮

setWindowBrightness, setWindowKeepScreenOn

窗口方向

横竖屏与自动旋转

setPreferredOrientation

状态监听

大小/避让/焦点变化

on('windowSizeChange'), on('avoidAreaChange')

属性查询

获取当前窗口属性

getWindowProperties

一句话总结:WindowStage 搭台,WindowClass 调属性,监听器管变化,三者配合才能玩转窗口。记住时序——先获取窗口、再设属性、后加载页面、最后注册监听,这个顺序不能乱。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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