窗口是HarmonyOS应用跟屏幕交互的基础单元——全屏、分屏、悬浮窗、亮度调节、屏幕常亮,这些功能都挂在Window API上。用得好能大幅提升体验,用得不好直接crash(比如getMainWindowSync时机不对)。
获取窗口实例
所有窗口操作的前提是拿到Window实例。推荐方式:
import{window}from'@kit.ArkUI';// 在UIAbility的onWindowStageCreate中onWindowStageCreate(windowStage:window.WindowStage):void{windowStage.loadContent('pages/Index',(err)=>{if(err.code){return;}// loadContent回调之后才能安全获取窗口letmainWindow:window.Window=windowStage.getMainWindowSync();AppStorage.setOrCreate<window.Window>('mainWindow',mainWindow);});}关键:getMainWindowSync()必须在loadContent回调之后调用。在loadContent之前调用会报1300002错误——这是新手最常犯的错。原因很简单:loadContent之前窗口还没初始化完成,取不到。
另一种方式是在页面里通过UIAbilityContext获取:
import{window}from'@kit.ArkUI';letmainWindow:window.Window=window.getLastWindow(this.context);getLastWindow是异步的,返回Promise。同步方式只有getMainWindowSync,但只能在WindowStage上用。
全屏与沉浸式
letmainWindow:window.Window=AppStorage.get<window.Window>('mainWindow');// 全屏布局(内容延伸到状态栏下方)mainWindow.setWindowLayoutFullScreen(true);// 获取避让区域letavoidArea:window.AvoidArea=mainWindow.getAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);lettopHeight:number=avoidArea.visible?avoidArea.topRect.height:0;setWindowLayoutFullScreen(true)后内容会延伸到状态栏和导航栏区域,但状态栏本身还是显示的。如果需要真正的全屏(隐藏状态栏),还要配合:
// 隐藏状态栏(需要SYSTEM_GRAPHIC_PERMISSION)mainWindow.setSpecificBarVisible(window.BarType.STATUS_BAR,false);大部分场景用setWindowLayoutFullScreen + expandSafeArea就够了,不需要隐藏状态栏。
避让区域
全屏布局后,内容可能被状态栏/导航栏/键盘遮挡。getAvoidArea获取这些区域的信息:
// 系统避让(状态栏+导航栏)letsystemAvoidArea:window.AvoidArea=mainWindow.getAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);// 键盘避让letkeyboardAvoidArea:window.AvoidArea=mainWindow.getAvoidArea(window.AvoidAreaType.TYPE_KEYBOARD);// 避让区域包含topRect和bottomRectlettopRect:window.Rect=systemAvoidArea.topRect;// 状态栏区域letbottomRect:window.Rect=systemAvoidArea.bottomRect;// 导航栏区域AvoidArea的visible字段表示该避让区是否可见。如果状态栏隐藏了,visible为false。
屏幕常亮
阅读场景、视频播放需要保持屏幕不熄灭:
mainWindow.setWindowKeepScreenOn(true);// 开启常亮mainWindow.setWindowKeepScreenOn(false);// 关闭常亮setWindowKeepScreenOn是异步方法但不需要await。不需要额外权限。
注意:页面切到后台时常亮自动失效,回到前台恢复。如果应用有后台保活需求,要配BackgroundTaskManager。
亮度调节
// 设置屏幕亮度(0.0-1.0)mainWindow.setWindowBrightness(0.5);// 获取当前亮度letbrightness:number=mainWindow.getWindowBrightness();setWindowBrightness只影响当前窗口,不影响系统亮度设置。适合阅读模式的亮度调节。
坑:不要把@State变量命名为brightness——这个名字跟组件内置方法冲突,会导致类型错误。用screenBrightness或其他名字。
窗口类型
HarmonyOS支持三种窗口类型:
| 类型 | 说明 | 场景 |
|---|---|---|
| WindowType.MAIN | 主窗口 | 应用主界面 |
| WindowType.SUB | 子窗口 | 弹窗、对话框 |
| WindowType.FLOAT | 浮动窗口 | 悬浮窗、画中画 |
创建子窗口
letsubWindow:window.Window=mainWindow.createSubWindow('subWindow');subWindow.resize(300,400);subWindow.moveWindowTo(100,200);subWindow.showWindow();子窗口在主窗口内部,位置和大小可控。适合自定义弹窗、浮层等。
创建浮动窗口
import{window}from'@kit.ArkUI';letfloatWindow:window.Window=window.createWindow({name:'floatWindow',windowType:window.WindowType.FLOAT,ctx:this.context});浮动窗口独立于主窗口,可以在其他应用上方显示。需要ohos.permission.SYSTEM_FLOAT_WINDOW权限,这是系统级权限,普通应用难以获取。
显示信息
获取屏幕物理信息:
import{display}from'@kit.ArkUI';letdefaultDisplay:display.Display=display.getDefaultDisplaySync();letscreenWidth:number=defaultDisplay.width;// 物理宽度(px)letscreenHeight:number=defaultDisplay.height;// 物理高度(px)letdensity:number=defaultDisplay.densityPixels;// 密度(如3.0)letrefreshRate:number=defaultDisplay.refreshRate;// 刷新率letorientation:number=defaultDisplay.orientation;// 方向densityPixels用于px→vp转换:vp = px / densityPixels。这个值在键盘高度计算等场景很有用。
窗口生命周期
UIAbility有五个跟窗口相关的回调:
exportdefaultclassEntryAbilityextendsUIAbility{onCreate():void{}// Ability创建onWindowStageCreate(windowStage):void{// 窗口创建 ← 在这里初始化onWindowStageDestroy(windowStage):void{// 窗口销毁onForeground():void{}// 前台onBackground():void{}// 后台}onWindowStageCreate是设置窗口属性的最佳时机。onForeground/onBackground可以用来暂停/恢复动画或定时器。
窗口事件监听
// 窗口大小变化mainWindow.on('windowSizeChange',(size:window.Size)=>{// size.width, size.height});// 避让区域变化(如键盘弹出)mainWindow.on('avoidAreaChange',(data:window.AvoidAreaData)=>{// data.type, data.area});// 可交互状态变化mainWindow.on('interactiveStatusChange',(interactive:boolean)=>{// 窗口是否可交互});avoidAreaChange特别重要——键盘弹出时type为TYPE_KEYBOARD的避让区会变化,通过这个回调可以精确计算键盘高度,实现聊天输入框跟随。
踩坑清单
| 问题 | 原因 | 解决 |
|---|---|---|
| getMainWindowSync报1300002 | 在loadContent之前调用 | 移到loadContent回调后 |
| brightness变量报类型错误 | 名字跟内置方法冲突 | 改名screenBrightness |
| FLOAT窗口创建失败 | 缺少权限 | 声明SYSTEM_FLOAT_WINDOW |
| 全屏后内容被状态栏遮挡 | 没处理避让区域 | expandSafeArea或getAvoidArea |
| 屏幕还是熄灭了 | 应用到了后台 | 配BackgroundTaskManager |
| setWindowBrightness无效 | 参数范围0-1不是0-255 | 用0.0-1.0范围 |
| 子窗口不显示 | 没调showWindow | 创建后调用showWindow() |
窗口管理的核心是"时机"——什么时候取实例、什么时候设属性、什么时候监听事件。搞清楚了loadContent后的时机要求和避让区域的计算逻辑,窗口操作就不会出问题。