HarmonyOS APP窗口属性:从 WindowStage 到窗口状态监听,掌控你的应用窗口
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 调属性,监听器管变化,三者配合才能玩转窗口。记住时序——先获取窗口、再设属性、后加载页面、最后注册监听,这个顺序不能乱。
- 点赞
- 收藏
- 关注作者
评论(0)