《悬浮工具球》一、窗口开发概述使用指南

《悬浮工具球》一、窗口开发概述使用指南
HarmonyOS窗口开发概述使用指南从零到一掌握应用窗口管理全流程适用版本HarmonyOS NEXTAPI 23/ DevEco Studio 6.1关键词窗口开发、WindowStage、子窗口、全屏布局、安全区避让、沉浸式体验效果一、前言为什么需要理解窗口开发在HarmonyOS应用开发中窗口Window是用户与应用交互的第一入口。无论是应用的主界面展示、弹窗提示、悬浮工具球还是沉浸式全屏体验都离不开窗口模块的支撑。很多开发者在接触HarmonyOS窗口开发时会遇到以下困惑主窗口和窗口有什么区别什么时候用子窗口全屏布局和沉浸式开发到底怎么配置安全区域状态栏、导航条怎么避让窗口生命周期如何与应用生命周期关联本文基于HarmonyOS官方文档结合实战开发经验系统梳理窗口开发的核心概念、关键API和典型使用场景并通过一个完整示例带你快速上手。二、核心概念窗口体系全景图2.1 窗口的分类HarmonyOS中的窗口分为两大类窗口类型说明典型场景系统窗口由系统创建和管理状态栏、导航条、输入法面板、音量调节应用窗口由应用创建用于显示应用界面主页面、弹窗、悬浮球、画中画应用窗口进一步分为子类型特点生命周期应用主窗口显示应用主界面在任务管理界面可见跟随UIAbility应用子窗口弹窗、悬浮球等辅助窗口不在任务管理界面显示跟随应用主窗口关键理解子窗口的生命周期跟随主窗口主窗口销毁时子窗口也会自动销毁。2.2 窗口模式HarmonyOS支持三种应用窗口模式模式说明适用设备全屏模式主窗口铺满整个屏幕手机、平板默认分屏模式多个应用并排显示平板、折叠屏自由窗口模式窗口可自由调整大小和位置类似PC窗口PC、2in1设备2.3 核心对象关系在Stage模型下窗口开发涉及三个核心对象它们的关系层层递进UIAbility应用入口 └── WindowStage窗口管理器 ├── 主窗口 Window应用主界面 └── 子窗口 Window弹窗、悬浮球等UIAbility应用入口一个UIAbility实例对应一个WindowStageWindowStage窗口管理器负责创建和管理窗口Window具体窗口实例提供窗口属性设置、位置移动、尺寸调整等能力三、窗口生命周期理解窗口生命周期是正确开发窗口基础。在Stage模型下窗口的生命周期与UIAbility紧密关联UIAbility.onCreate() ↓ UIAbility.onWindowStageCreate(windowStage) ← 窗口创建加载主页面 ↓ [应用运行中窗口可见] ↓ UIAbility.onWindowStageDestroy() ← 窗口即将销毁释放资源 ↓ UIAbility.onDestroy() ← Ability销毁关键生命周期回调回调方法触发时机典型操作onWindowStageCreateWindowStage创建完成加载主页面、创建子窗口、设置全屏onWindowStageDestroyWindowStage即将销毁释放子窗口、清理窗口资源onForeground应用切到前台恢复窗口状态onBackground应用切到后台保存窗口状态监听窗口生命周期事件还可以通过on(windowStageEvent)监听WindowStage的生命周期变化windowStage.on(windowStageEvent,(eventType:window.WindowStageEventType){// eventType 包括// SHOWN - 窗口显示// HIDDEN - 窗口隐藏// ACTIVE - 窗口获得焦点// INACTIVE - 窗口失去焦点switch(eventType){casewindow.WindowStageEventType.SHOWN:console.info(窗口已显示);break;casewindow.WindowStageEventType.ACTIVE:console.info(窗口获得焦点);break;}});四、关键开发场景与API使用4.1 加载主页面在onWindowStageCreate中通过loadContent为主窗口加载页面onWindowStageCreate(windowStage:window.WindowStage):void{windowStage.loadContent(pages/Index,(err){if(err.code){console.error(加载页面失败:,JSON.stringify(err));return;}console.info(页面加载成功);});}注意loadContent是异步方法但AppStorage.setOrCreate(windowStage, windowStage)等全局数据的设置必须在loadContent之前完成否则组件初始化时无法读取到全局数据。获取主窗口、设置全屏等操作可在loadContent回调中执行。4.2 获取主窗口并设置全屏windowStage.loadContent(pages/Index,(err){if(err.code)return;// 获取主窗口实例constmainWindow:window.WindowwindowStage.getMainWindowSync();// 设置全屏布局mainWindow.setWindowLayoutFullScreen(true).then((){console.info(全屏布局设置成功);}).catch((err:BusinessError){console.error(全屏布局设置失败:,JSON.stringify(err));});});4.3 安全区域避让全屏布局下内容会被状态栏和导航条遮挡。需要获取避让区域并动态调整布局// 获取状态栏高度顶部安全区constsysAreamainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);consttopInsetsysArea.topRect.height;// 单位px// 获取导航条高度底部安全区constnavAreamainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);constbottomInsetnavArea.bottomRect.height;// 单位px// 保存到全局供页面使用AppStorage.setOrCreate(topInset,topInset);AppStorage.setOrCreate(bottomInset,bottomInset);动态监听安全区变化如折叠屏展开、横竖屏切换mainWindow.on(avoidAreaChange,(data){if(data.typewindow.AvoidAreaType.TYPE_SYSTEM){AppStorage.setOrCreate(topInset,data.area.topRect.height);}elseif(data.typewindow.AvoidAreaType.TYPE_NAVIGATION_INDICATOR){AppStorage.setOrCreate(bottomInset,data.area.bottomRect.height);}});4.4 创建子窗口子窗口是悬浮球、弹窗等场景的核心载体// 通过WindowStage创建子窗口windowStage.createSubWindow(mySubWindow,(err,subWin){if(err.code0){console.error(创建子窗口失败:,err.message);return;}// 设置子窗口页面subWin.setUIContent(subwindow/SubPage);// 设置位置单位pxsubWin.moveWindowTo(500,1000);// 设置大小单位px需vp2px转换subWin.resize(200,200);// 设置背景透明subWin.setWindowBackgroundColor(#00000000);// 显示子窗口subWin.showWindow();});4.5 窗口焦点切换当子窗口需要操作后把焦点还给主窗口// 获取子窗口IDconstsubWinIdwindow.findWindow(mySubWindow).getWindowProperties().id;// 获取主窗口IDconstmainWinIdwindowStage.getMainWindowSync().getWindowProperties().id;// 焦点从子窗口转移到主窗口window.shiftAppWindowFocus(subWinId,mainWinId);五、完整示例从零搭建带子窗口的全屏应用下面通过一个完整的示例演示窗口开发的核心流程。5.1 工程结构entry/src/main/ets/ ├── entryability/ │ └── EntryAbility.ets // 应用入口配置窗口 ├── pages/ │ └── Index.ets // 主页面 └── subwindow/ └── SubPage.ets // 子窗口页面5.2 EntryAbility — 窗口配置入口import{UIAbility}fromkit.AbilityKit;import{window}fromkit.ArkUI;import{BusinessError}fromkit.BasicServicesKit;exportdefaultclassEntryAbilityextendsUIAbility{onWindowStageCreate(windowStage:window.WindowStage):void{// 1. 在loadContent之前设置AppStorage确保组件初始化时可读取AppStorage.setOrCreate(windowStage,windowStage);// 2. 获取主窗口配置全屏constwinwindowStage.getMainWindowSync();win.setWindowLayoutFullScreen(true);// 3. 获取安全区域consttopAreawin.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);AppStorage.setOrCreate(topInset,topArea.topRect.height);constbottomAreawin.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);AppStorage.setOrCreate(bottomInset,bottomArea.bottomRect.height);// 4. 监听安全区变化win.on(avoidAreaChange,(data){if(data.typewindow.AvoidAreaType.TYPE_SYSTEM){AppStorage.setOrCreate(topInset,data.area.topRect.height);}elseif(data.typewindow.AvoidAreaType.TYPE_NAVIGATION_INDICATOR){AppStorage.setOrCreate(bottomInset,data.area.bottomRect.height);}});// 5. 加载主页面此时AppStorage已就绪windowStage.loadContent(pages/Index,(err){if(err.code){console.error(加载失败:,JSON.stringify(err));return;}console.info(页面加载成功);});}}5.3 Index — 主页面加载子窗口import{window}fromkit.ArkUI;EntryComponentV2struct Index{privatewindowStage:window.WindowStageAppStorage.get(windowStage)aswindow.WindowStage;aboutToAppear():void{this.createSubWindow();}createSubWindow():void{this.windowStage.createSubWindow(demoSubWin,(err,win){if(err.code0)return;win.setUIContent(subwindow/SubPage);win.setWindowBackgroundColor(#00000000);win.moveWindowTo(800,1500);win.resize(this.getUIContext().vp2px(60),this.getUIContext().vp2px(60));win.showWindow();});}build(){Column(){Text(主页面内容).fontSize(24).fontWeight(FontWeight.Bold)}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}5.4 SubPage — 子窗口页面EntryComponentV2struct SubPage{build(){Column(){Text(Hi).fontSize(20).fontColor(#FFFFFF)}.width(100%).height(100%).backgroundColor(#4A90D9).borderRadius(30).justifyContent(FlexAlign.Center)}}5.5 路由配置在main_pages.json中注册子窗口页面{src:[pages/Index,subwindow/SubPage]}六、开发注意事项与最佳实践6.1 AppStorage全局数据时序这是最容易踩坑的点。AppStorage.setOrCreate必须在loadContent之前调用否则组件初始化时Local变量读取AppStorage.get()将返回undefined// ❌ 错误写法在loadContent回调中设置AppStoragewindowStage.loadContent(pages/Index,(err){if(err.code)return;AppStorage.setOrCreate(windowStage,windowStage);// 组件已初始化读取为undefined});// ✅ 正确写法在loadContent之前设置AppStorageAppStorage.setOrCreate(windowStage,windowStage);windowStage.loadContent(pages/Index,(err){if(err.code)return;});6.2 异步操作时序loadContent和getMainWindowSync都是异步相关操作确保在loadContent回调中再执行依赖页面加载完成的窗口操作// ❌ 错误写法loadContent和getMainWindowSync并行调用windowStage.loadContent(pages/Index);constwinwindowStage.getMainWindowSync();// 可能获取到未加载完成的窗口// ✅ 正确写法在loadContent回调中操作windowStage.loadContent(pages/Index,(err){if(err.code)return;constwinwindowStage.getMainWindowSync();win.setWindowLayoutFullScreen(true);});6.3 单位转换窗口APImoveWindowTo、resize等的参数单位为物理像素px而ArkUI组件的属性单位为虚拟像素vp。必须使用vp2px进行转换constsizeVp60;// vpconstsizePxthis.getUIContext().vp2px(sizeVp);// 转换为pxwin.resize(sizePx,sizePx);6.4 子窗口页面注册子窗口加载的页面路径必须在main_pages.json中注册否则setUIContent会报错{src:[pages/Index,subwindow/SubPage]}6.5 窗口背景透明子窗口默认有白色背景如果需要透明效果如悬浮球必须设置背景色为透明win.setWindowBackgroundColor(#00000000);6.6 资源释放在onWindowStageDestroy中释放子窗口资源避免内存泄漏onWindowStageDestroy():void{// 销毁子窗口constsubWinwindow.findWindow(mySubWindow);if(subWin){subWin.destroyWindow();}}七、常见问题FAQQ1子窗口创建失败报错code 1300005这通常是因为窗口名称重复。每个子窗口的名称必须唯一不能创建两个同名子窗口。Q2全屏设置后内容被状态栏遮挡全屏布局下需要手动获取安全区域并设置padding。参考本文4.3 安全区域避让章节。Q3子窗口拖拽不跟手确保使用物理像素px单位进行窗口移动操作并在手势回调中直接更新窗口位置避免异步查询窗口属性导致延迟。Q4如何在子窗口中访问主窗口的数据通过AppStorage进行全局数据共享。在主页面的aboutToAppear中将需要共享的数据存入AppStorage在子窗口中通过AppStorage.get()获取。八、总结HarmonyOS窗口开发的核心知识点知识点要点核心对象UIAbility → WindowStage → Window层层递进主窗口配置loadContent加载页面 → getMainWindowSync获取窗口 → setWindowLayoutFullScreen全屏安全区避让getWindowAvoidArea获取避让区域 on(‘avoidAreaChange’)动态监听子窗口创建createSubWindow → setUIContent → moveWindowTo → resize → showWindow单位转换窗口API使用px组件使用vp用vp2px/px2vp转换时序保障窗口操作放在loadContent回调中避免并行调用掌握以上核心内容你就具备了HarmonyOS窗口开发的基础能力。在此基础上可以进一步探索悬浮工具球、画中画、自由窗口等高级场景。参考文档窗口开发概述管理应用窗口Stage模型ohos.window API参考