《HarmonyOS技术精讲-窗口管理》第五篇:布局管理——分屏与全屏

《HarmonyOS技术精讲-窗口管理》第五篇:布局管理——分屏与全屏
一、开篇LayoutMode 到底该在什么场景下用HarmonyOS NEXT 开发里窗口布局管理不算复杂但真正落到代码层面很多细节容易被忽略。特别是setWindowLayoutMode这个 API官方示例能跑通但放到实际项目中会发现分屏事件监听、布局切换时的状态同步、全屏退出的恢复逻辑每个环节都容易出问题。这篇文章不讲概念只讲三个实际场景怎么落地分屏自适应、全屏沉浸体验、以及这两个模式切换时的状态管理。代码可以直接粘贴到你的项目里运行。二、场景说明一个需要适配多种布局的笔记应用假设你正在开发一个笔记应用核心需求是分屏模式用户开启分屏后笔记列表和编辑区自动调整为左右布局全屏模式用户点击“全屏编辑”后隐藏状态栏、导航栏编辑区占满整个屏幕布局切换分屏退出或全屏退出后布局能恢复到初始状态这个需求覆盖了setWindowLayoutMode的两种模式以及监听windowSizeChange事件做自适应布局。三、环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机、平板分屏效果在平板上更明显四、核心实现1. 准备工作获取窗口实例分屏和全屏操作都基于windowStage获取的主窗口对象。在UIAbility的onWindowStageCreate回调里拿到它。// MainAbility.tsimport{UIAbility,Want}fromkit.AbilityKit;import{window}fromkit.ArkUI;exportdefaultclassMainAbilityextendsUIAbility{privatemainWindow:window.Window|nullnull;onWindowStageCreate(windowStage:window.WindowStage):void{windowStage.loadContent(pages/Index);// 获取主窗口实例this.mainWindowwindowStage.getMainWindowSync();}}注意getMainWindowSync()是同步方法如果窗口还没准备好会抛出异常。建议在onWindowStageCreate里调用这是窗口生命周期的正确阶段。2. 分屏模式监听窗口变化自适应布局分屏的核心是setWindowLayoutMode(window.WindowLayoutMode.WINDOW_LAYOUT_MODE_SPLIT)并把窗口尺寸变化传递给 UI 层做布局调整。下面这段代码实现在Index.ets中监听窗口尺寸变化并动态决定显示单列还是双列布局。// pages/Index.etsimport{window}fromkit.ArkUI;EntryComponentstruct Index{StateisWideMode:booleanfalse;privatewindowWidth:number0;aboutToAppear():void{// 监听窗口尺寸变化window.getLastWindow(getContext(),(err,win){if(!err){win.on(windowSizeChange,(size){this.windowWidthsize.width;this.isWideModesize.width600;// 宽度大于600px认为是宽屏模式分屏或平板竖屏});}});}build(){Row(){if(this.isWideMode){// 分屏/宽屏模式左侧笔记列表右侧编辑区Column(){Text(笔记列表).fontSize(20).height(100%).backgroundColor(#f0f0f0)}.layoutWeight(1)Column(){Text(编辑区域).fontSize(20).height(100%).backgroundColor(#ffffff)}.layoutWeight(2)}else{// 非分屏/窄屏模式只显示编辑区Text(全屏编辑模式).fontSize(24).height(100%).textAlign(TextAlign.Center)}}.width(100%).height(100%)}}关键点这里没有直接调用setWindowLayoutMode进入分屏而是先监听窗口尺寸变化。实际开发中用户通过系统分屏手势进入分屏后窗口尺寸会变化windowSizeChange事件会被触发。你的 UI 需要响应这个事件而不是依赖固定的分屏状态。3. 全屏模式隐藏状态栏并进入全屏布局全屏模式需要两步设置窗口布局模式为全屏setWindowLayoutMode(WINDOW_LAYOUT_MODE_FULLSCREEN)隐藏状态栏和导航栏如果需要沉浸式通过setWindowLayoutFullScreen(true)实现下面这段代码给页面添加一个“全屏编辑”按钮点击后切换全屏状态。// pages/FullscreenButton.ts引入到Index.ets中使用import{window}fromkit.ArkUI;Componentexportstruct FullscreenButton{StateisFullscreen:booleanfalse;privateasynctoggleFullscreen(){constwinawaitwindow.getLastWindow(getContext());if(!this.isFullscreen){// 进入全屏win.setWindowLayoutMode(window.WindowLayoutMode.WINDOW_LAYOUT_MODE_FULLSCREEN);// 设置全屏显示并隐藏状态栏win.setWindowLayoutFullScreen(true,(err){if(!err){// 隐藏导航栏可选win.setWindowSystemBarEnable([]);}});this.isFullscreentrue;}else{// 退出全屏win.setWindowLayoutMode(window.WindowLayoutMode.WINDOW_LAYOUT_MODE_FLOATING);// 恢复导航栏和状态栏win.setWindowLayoutFullScreen(false);win.setWindowSystemBarEnable([navigationBar,statusBar]);this.isFullscreenfalse;}}build(){Button(this.isFullscreen?退出全屏:全屏编辑).onClick(()this.toggleFullscreen()).margin({top:20})}}注意事项setWindowLayoutFullScreen(true)只是让内容铺满全屏状态栏是否隐藏取决于第二个参数。这里我们传了true表示要隐藏状态栏。退出全屏时需要显式调用setWindowLayoutFullScreen(false)恢复状态栏和导航栏否则它们可能不会自动显示。setWindowSystemBarEnable([])是清空显示的系统栏列表等同于全部隐藏。退出时传回完整的列表。4. 整合到主页面在Index.ets中添加按钮让整个功能可以运行。// pages/Index.ets完整版import{window}fromkit.ArkUI;import{FullscreenButton}from./FullscreenButton;EntryComponentstruct Index{StateisWideMode:booleanfalse;privatewindowWidth:number0;aboutToAppear():void{window.getLastWindow(getContext(),(err,win){if(!err){win.on(windowSizeChange,(size){this.windowWidthsize.width;this.isWideModesize.width600;});}});}build(){Stack(){if(this.isWideMode){Row(){Column(){Text(笔记列表).fontSize(20).height(100%).backgroundColor(#f0f0f0)}.layoutWeight(1)Column(){Text(编辑区域).fontSize(20).height(100%).backgroundColor(#ffffff)FullscreenButton()}.layoutWeight(2)}}else{Column(){Text(全屏编辑模式).fontSize(24).height(100%).textAlign(TextAlign.Center)FullscreenButton()}.width(100%).height(100%)}}.width(100%).height(100%)}}五、常见问题真正的踩坑记录问题 1分屏后点击全屏按钮布局错乱现象在分屏模式下窗口宽度 500px点击全屏按钮编辑区域没有占满整个屏幕而是出现了白边或者状态栏异常显示。原因分屏模式和全屏模式的切换是独立的。分屏模式下调用了setWindowLayoutMode(FULLSCREEN)后窗口会尝试扩展到最大可用区域但之前设置的windowSizeChange回调里宽度判断是基于进入分屏时的窗口尺寸没有主动刷新isWideMode状态。解决方案在全屏切换的回调里主动触发一次窗口尺寸检查。推荐的做法是在toggleFullscreen方法末尾通过win.getWindowProperties()获取当前实际窗口尺寸然后更新isWideMode。privateasynctoggleFullscreen(){constwinawaitwindow.getLastWindow(getContext());// ... 切换逻辑 ...// 同步窗口真实尺寸constpropswin.getWindowProperties();this.isWideModeprops.windowRect.width600;}问题 2全屏退出后状态栏没有恢复显示现象从全屏模式退出后状态栏和导航栏依然隐藏用户无法退出应用或看时间。原因setWindowLayoutFullScreen(false)只是告诉系统不再全屏显示内容但之前通过setWindowSystemBarEnable([])隐藏的系统栏不会自动恢复。这是 HarmonyOS 的一个设计特性需要显式恢复。解决方案退出全屏时必须显式恢复系统栏。win.setWindowSystemBarEnable([statusBar,navigationBar]);如果只调用了setWindowLayoutFullScreen(true)而没有设置setWindowSystemBarEnable则退出时只需setWindowLayoutFullScreen(false)即可系统栏会自动恢复。建议保持一致性全屏时设置系统栏退出时也统一恢复。六、最佳实践不在 build() 中频繁创建对象window.getLastWindow()是异步操作每次 build 都调用会导致重复创建窗口监听浪费性能。推荐在aboutToAppear里只调用一次把窗口实例保存到局部变量或类成员中。监听分屏事件优先使用on(windowSizeChange)而非定时轮询有些开发者会通过setInterval轮询窗口尺寸这会导致不必要的 UI 刷新。windowSizeChange是系统层面的事件精确且高效。全屏退出后重置 UI 状态如果全屏时 UI 发生了变化比如隐藏了导航栏退出后需要手动恢复。建议在toggleFullscreen方法中用一个State变量记录当前模式退出时统一重置。七、Demo 入口可直接运行// entry/src/main/ets/pages/Index.ets完整可运行版本import{window}fromkit.ArkUI;EntryComponentstruct Index{StateisWideMode:booleanfalse;StateisFullscreen:booleanfalse;privatewindowWidth:number0;aboutToAppear():void{window.getLastWindow(getContext(),(err,win){if(!err){win.on(windowSizeChange,(size){this.windowWidthsize.width;this.isWideModesize.width600;});}});}privateasynctoggleFullscreen(){constwinawaitwindow.getLastWindow(getContext());if(!this.isFullscreen){win.setWindowLayoutMode(window.WindowLayoutMode.WINDOW_LAYOUT_MODE_FULLSCREEN);win.setWindowLayoutFullScreen(true);win.setWindowSystemBarEnable([]);this.isFullscreentrue;}else{win.setWindowLayoutMode(window.WindowLayoutMode.WINDOW_LAYOUT_MODE_FLOATING);win.setWindowLayoutFullScreen(false);win.setWindowSystemBarEnable([statusBar,navigationBar]);this.isFullscreenfalse;}}build(){Stack(){if(this.isWideMode){Row(){Column(){Text(笔记列表).fontSize(20).height(100%).backgroundColor(#f0f0f0)}.layoutWeight(1)Column(){Text(编辑区域).fontSize(20).height(100%).backgroundColor(#ffffff)Button(this.isFullscreen?退出全屏:全屏编辑).onClick(()this.toggleFullscreen()).margin({top:20})}.layoutWeight(2)}}else{Column(){Text(全屏编辑模式).fontSize(24).height(100%).textAlign(TextAlign.Center)Button(this.isFullscreen?退出全屏:全屏编辑).onClick(()this.toggleFullscreen()).margin({top:20})}.width(100%).height(100%)}}.width(100%).height(100%)}}八、FAQQ1为什么在模拟器上分屏事件不触发A模拟器不支持真实的分屏手势。需要开启“自由窗口”模式或在 DevEco Studio 中通过“多窗口配置”模拟分屏。建议在真机上验证分屏逻辑。Q2分屏切换到全屏后UI 状态丢失比如输入框内容被清空A这不是窗口管理的 bug而是组件重建导致的。分屏状态下编辑区可能被销毁切换到全屏时重新创建State 状态会被重置。解决方案把用户输入等持久化数据存储在 ViewModel 或 AppStorage 中组件重建后读取。Q3为什么多次快速点击全屏按钮会导致应用无响应AsetWindowLayoutMode是异步操作连续快速调用可能会导致多个状态共存窗口进入卡死状态。建议在切换过程中设置一个标志位禁止重复点击在toggleFullscreen方法开头设置this.isProcessing true切换完成后置为false按钮的enabled属性绑定这个标志。示例代码项目地址项目地址