写点什么

【HarmonyOS】应用设置全屏和安全区域详解

作者:GeorgeGcs
  • 2025-08-19
    上海
  • 本文字数:8359 字

    阅读完需:约 27 分钟

【HarmonyOS】应用设置全屏和安全区域详解

【HarmonyOS】应用设置全屏和安全区域详解

一、前言

IDE 创建的鸿蒙应用,默认采取组件安全区布局方案。顶部会预留状态栏区域,底部会预留导航条区域。这就是所谓的安全区域。


如果不处理,界面效果很割裂。所以业内 UI 交互设计,都会设置应用为全屏布局。将页面绘制区域沾满整个界面。


或者将安全区域的颜色与应用 UI 设置为一致。


以上两种方式都是沉浸式布局的处理。所以全屏非沉浸式,概念不可混为一谈。在移动应用开发中,"沉浸式效果"早已不是新鲜词,但要真正实现自然、和谐的沉浸式体验,却需要对系统布局、交互逻辑有深入理解。


二、什么是应用沉浸式效果?

简单来说,应用沉浸式效果是通过优化状态栏、应用界面与底部导航区域(导航条或三键导航)的视觉融合与交互适配,减少系统界面的突兀感,让用户注意力更聚焦于应用内容本身。



典型的界面元素包含三部分:状态栏:显示时间、电量等系统信息的顶部区域应用界面:承载应用核心内容的区域底部导航区域:提供系统导航操作的底部区域


其中状态栏和底部导航区域被称为"避让区",其余区域为"安全区"。沉浸式开发的核心就是处理好这两个区域与应用内容的关系,主要涉及两类问题:UI 元素避让:避免可交互元素或关键信息被避让区遮挡视觉融合:让避让区与应用界面的颜色、风格保持一致

三、如何设置沉浸式布局?

综上所述,我们可知,设置沉浸式布局有以下两种方式,如图所示:


1、方案一:窗口全屏布局方案

该方案通过将应用界面强制扩展到全屏(包括状态栏和导航区域),实现深度沉浸式体验。适合需要在避让区放置 UI 元素的场景,如视频播放器控制栏、游戏界面等。

场景 1:保留避让区,需处理 UI 避让

当需要显示状态栏和导航区域,但希望应用内容延伸至这些区域时,需通过以下步骤实现:


(1)开启全屏布局


在应用启动时调用setWindowLayoutFullScreen接口,让界面突破安全区限制:


   // EntryAbility.ets   let windowClass = windowStage.getMainWindowSync();   windowClass.setWindowLayoutFullScreen(true).then(() => {     console.info("窗口已设置为全屏布局");   });
复制代码


(2)获取并监听避让区尺寸


通过getWindowAvoidArea获取状态栏和导航区域高度,并注册avoidAreaChange监听动态变化(如屏幕旋转、折叠屏展开等场景):


   // 获取状态栏高度   let systemArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);   AppStorage.setOrCreate('statusBarHeight', systemArea.topRect.height);      // 获取导航区域高度   let navArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);   AppStorage.setOrCreate('navBarHeight', navArea.bottomRect.height);      // 动态监听变化   windowClass.on('avoidAreaChange', (data) => {     if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {       AppStorage.setOrCreate('statusBarHeight', data.area.topRect.height);     }   });
复制代码


(3)布局中实现 UI 避让


在页面布局时,通过 padding 将内容区与避让区隔开,避免 UI 重叠:


   // Index.ets   @Component   struct Index {     @StorageProp('statusBarHeight') statusBarHeight: number = 0;     @StorageProp('navBarHeight') navBarHeight: number = 0;          build() {       Column() {         // 应用内容组件...       }       .padding({         top: this.getUIContext().px2vp(this.statusBarHeight),         bottom: this.getUIContext().px2vp(this.navBarHeight)       })     }   }
复制代码

场景 2:隐藏避让区,实现纯全屏

游戏、视频类应用常需要完全隐藏状态栏和导航区域,仅在用户操作时唤起:


(1)开启全屏布局(同场景 1 步骤 1)


(2)隐藏系统栏


通过setSpecificSystemBarEnabled接口隐藏状态栏和导航区域:


   // 隐藏状态栏   windowClass.setSpecificSystemBarEnabled('status', false);   // 隐藏导航区域   windowClass.setSpecificSystemBarEnabled('navigationIndicator', false);
复制代码


(3)无需额外避让处理


此时界面已完全全屏,布局中无需设置避让 padding,内容可直接铺满屏幕。

2、方案二:组件安全区方案

该方案为默认布局模式,UI 元素自动限制在安全区内(无需手动处理避让),仅通过延伸背景绘制实现沉浸式效果。适合大多数普通应用,尤其是不需要在避让区布局 UI 的场景。


默认情况下,应用 UI 元素会自动避开避让区,但窗口背景可全屏绘制。通过以下方式优化视觉融合:


(1)状态栏与导航区域颜色相同时


直接设置窗口背景色与应用主背景一致,实现整体沉浸:


   // EntryAbility.ets   windowStage.getMainWindowSync().setWindowBackgroundColor('#d5d5d5');
复制代码


(2)颜色不同时:使用 expandSafeArea 扩展绘制


对顶部/底部组件单独设置expandSafeArea属性,使其背景延伸至避让区:


   // Index.ets   @Component   struct Index {     build() {       Column() {         // 顶部组件延伸至状态栏         Row() {           Text('顶部内容')         }         .backgroundColor('#2786d9')         .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])                  // 中间内容区...                  // 底部组件延伸至导航区         Row() {           Text('底部内容')         }         .backgroundColor('#96dffa')         .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])       }     }   }
复制代码


(3)典型场景适配技巧


背景图/视频场景:让图片组件延伸至避让区


  Image($r('app.media.bg'))    .width('100%').height('100%')    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
复制代码


滚动容器场景:通过父容器扩展实现滚动背景沉浸


  Scroll() {    Column() {      // 滚动内容...    }    .backgroundColor('rgb(213,213,213)')  }  .backgroundColor('rgb(213,213,213)')  .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
复制代码


底部页签场景Navigation/Tabs组件默认支持背景延伸,自定义页签可手动设置:


  // 自定义底部页签  Row() {    // 页签按钮...  }  .backgroundColor('#f5f5f5')  .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
复制代码

三、DEMO 源码示例:

ImmersiveDemo/├── src/main/ets/│ ├── Ability/│ │ └── EntryAbility.ets // 应用入口,处理窗口配置│ ├── pages/│ │ ├── FullScreenNormal.ets // 窗口全屏布局(不隐藏避让区)│ │ ├── FullScreenHidden.ets // 窗口全屏布局(隐藏避让区)│ │ └── SafeAreaMode.ets // 组件安全区方案│ └── common/│ └── Constants.ets // 常量定义


应用入口配置(EntryAbility.ets)



import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';import { window } from '@kit.ArkUI';import { BusinessError } from '@kit.BasicServicesKit';import { pageMap } from '../common/Constants';
export default class EntryAbility extends UIAbility { private mainWindow: window.Window | null = null;
async onWindowStageCreate(windowStage: window.WindowStage) { // 获取主窗口实例 this.mainWindow = windowStage.getMainWindowSync(); if (!this.mainWindow) { console.error('获取主窗口失败'); return; }
// 加载首页 windowStage.loadContent(pageMap.FULL_SCREEN_NORMAL, (err) => { if (err.code) { console.error(`加载页面失败: ${JSON.stringify(err)}`); return; } });
// 初始化避让区数据监听 this.initAvoidAreaListener(); }
// 初始化避让区监听 private initAvoidAreaListener() { if (!this.mainWindow) return;
// 初始获取避让区数据 this.updateAvoidAreaData();
// 监听避让区变化 this.mainWindow.on('avoidAreaChange', (data) => { console.info(`避让区变化: ${JSON.stringify(data)}`); if (data.type === window.AvoidAreaType.TYPE_SYSTEM) { AppStorage.setOrCreate('statusBarHeight', data.area.topRect.height); } else if (data.type === window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) { AppStorage.setOrCreate('navBarHeight', data.area.bottomRect.height); } }); }
// 更新避让区数据到全局存储 private updateAvoidAreaData() { if (!this.mainWindow) return;
try { // 获取状态栏区域 const systemArea = this.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); AppStorage.setOrCreate('statusBarHeight', systemArea.topRect.height);
// 获取导航栏区域 const navArea = this.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR); AppStorage.setOrCreate('navBarHeight', navArea.bottomRect.height); } catch (err) { console.error(`获取避让区数据失败: ${JSON.stringify(err)}`); } }
// 切换到窗口全屏布局(不隐藏避让区)模式 public switchToFullScreenNormal() { if (!this.mainWindow) return;
// 开启全屏布局 this.mainWindow.setWindowLayoutFullScreen(true).then(() => { // 显示状态栏和导航栏 this.mainWindow?.setSpecificSystemBarEnabled('status', true); this.mainWindow?.setSpecificSystemBarEnabled('navigationIndicator', true); // 加载对应页面 this.context?.getWindowStage().then((stage) => { stage.loadContent(pageMap.FULL_SCREEN_NORMAL); }); }); }
// 切换到窗口全屏布局(隐藏避让区)模式 public switchToFullScreenHidden() { if (!this.mainWindow) return;
// 开启全屏布局 this.mainWindow.setWindowLayoutFullScreen(true).then(() => { // 隐藏状态栏和导航栏 this.mainWindow?.setSpecificSystemBarEnabled('status', false); this.mainWindow?.setSpecificSystemBarEnabled('navigationIndicator', false); // 加载对应页面 this.context?.getWindowStage().then((stage) => { stage.loadContent(pageMap.FULL_SCREEN_HIDDEN); }); }); }
// 切换到组件安全区模式 public switchToSafeAreaMode() { if (!this.mainWindow) return;
// 关闭全屏布局(使用默认安全区布局) this.mainWindow.setWindowLayoutFullScreen(false).then(() => { // 显示状态栏和导航栏 this.mainWindow?.setSpecificSystemBarEnabled('status', true); this.mainWindow?.setSpecificSystemBarEnabled('navigationIndicator', true); // 设置窗口背景色(用于安全区方案) this.mainWindow?.setWindowBackgroundColor('#d5d5d5'); // 加载对应页面 this.context?.getWindowStage().then((stage) => { stage.loadContent(pageMap.SAFE_AREA_MODE); }); }); }}
复制代码


2. 常量定义(Constants.ets)



export const pageMap = { FULL_SCREEN_NORMAL: 'pages/FullScreenNormal', FULL_SCREEN_HIDDEN: 'pages/FullScreenHidden', SAFE_AREA_MODE: 'pages/SafeAreaMode'};
复制代码


3. 窗口全屏布局(不隐藏避让区)页面



import { EntryAbility } from '../Ability/EntryAbility';import { pageMap } from '../common/Constants';import { UIContext } from '@kit.ArkUI';
@Entry@Componentstruct FullScreenNormal { @StorageProp('statusBarHeight') statusBarHeight: number = 0; @StorageProp('navBarHeight') navBarHeight: number = 0; private uiContext: UIContext | null = null;
build() { Column() { // 顶部导航栏 Row() { Text('窗口全屏模式(不隐藏避让区)') .fontSize(18) .fontWeight(FontWeight.Bold) .color(Color.White) } .backgroundColor('#2786d9') .width('100%') .height(50) .justifyContent(FlexAlign.Center)
// 内容区 Scroll() { Column() { // 方案说明 Text('此模式下界面延伸至状态栏和导航栏,但通过padding实现内容避让') .fontSize(14) .padding(15) .backgroundColor('#e6f7ff') .margin(10) .borderRadius(8) .width('90%')
// 功能按钮区 Column() { Button('切换到全屏隐藏模式') .width('80%') .margin(5) .onClick(() => { (getContext(this) as any).ability.switchToFullScreenHidden(); })
Button('切换到组件安全区模式') .width('80%') .margin(5) .onClick(() => { (getContext(this) as any).ability.switchToSafeAreaMode(); }) } .margin(20)
// 示例内容卡片 ForEach([1, 2, 3, 4], (item) => { Row() { Text(`内容卡片 ${item}`) .fontSize(16) .color('#333') } .backgroundColor(Color.White) .width('90%') .height(100) .borderRadius(10) .margin(10) .justifyContent(FlexAlign.Center) }) } .width('100%') }
// 底部信息栏 Row() { Text('底部操作区') .fontSize(16) .color(Color.White) } .backgroundColor('#96dffa') .width('100%') .height(60) .justifyContent(FlexAlign.Center) } .width('100%') .height('100%') .backgroundColor('#d5d5d5') .padding({ top: this.uiContext ? this.uiContext.px2vp(this.statusBarHeight) : 0, bottom: this.uiContext ? this.uiContext.px2vp(this.navBarHeight) : 0 }) .onAppear(() => { this.uiContext = this.getUIContext(); }) }}
复制代码


4. 窗口全屏布局(隐藏避让区)页面



import { pageMap } from '../common/Constants';
@Entry@Componentstruct FullScreenHidden { build() { Column() { // 顶部区域 Row() { Text('全屏隐藏模式') .fontSize(18) .fontWeight(FontWeight.Bold) .color(Color.White) } .backgroundColor('#2786d9') .width('100%') .height(50) .justifyContent(FlexAlign.Center)
// 内容区 Scroll() { Column() { // 提示信息 Text('状态栏和导航栏已隐藏,上滑底部可唤起导航栏') .fontSize(14) .padding(15) .backgroundColor('#fff3cd') .margin(10) .borderRadius(8) .width('90%')
// 功能按钮区 Column() { Button('切换到全屏普通模式') .width('80%') .margin(5) .onClick(() => { (getContext(this) as any).ability.switchToFullScreenNormal(); })
Button('切换到组件安全区模式') .width('80%') .margin(5) .onClick(() => { (getContext(this) as any).ability.switchToSafeAreaMode(); }) } .margin(20)
// 模拟视频播放区域 Row() { Text('视频播放区域') .fontSize(20) .color(Color.White) } .backgroundColor('#333') .width('90%') .height(200) .borderRadius(10) .margin(10) .justifyContent(FlexAlign.Center)
// 示例内容卡片 ForEach([1, 2, 3], (item) => { Row() { Text(`内容卡片 ${item}`) .fontSize(16) .color('#333') } .backgroundColor(Color.White) .width('90%') .height(100) .borderRadius(10) .margin(10) .justifyContent(FlexAlign.Center) }) } .width('100%') }
// 底部操作区 Row() { Text('播放控制区') .fontSize(16) .color(Color.White) } .backgroundColor('#96dffa') .width('100%') .height(60) .justifyContent(FlexAlign.Center) } .width('100%') .height('100%') .backgroundColor('#d5d5d5') }}
复制代码


5. 组件安全区方案页面



import { SafeAreaEdge, SafeAreaType } from '@kit.ArkUI';import { pageMap } from '../common/Constants';
@Entry@Componentstruct SafeAreaMode { build() { Column() { // 顶部导航栏(延伸至状态栏) Row() { Text('组件安全区模式') .fontSize(18) .fontWeight(FontWeight.Bold) .color(Color.White) } .backgroundColor('#2786d9') .width('100%') .height(50) .justifyContent(FlexAlign.Center) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP]) // 延伸至状态栏
// 内容区 Scroll() { Column() { // 方案说明 Text('此模式下UI元素自动限制在安全区,通过expandSafeArea延伸背景至避让区') .fontSize(14) .padding(15) .backgroundColor('#e6f7ff') .margin(10) .borderRadius(8) .width('90%')
// 功能按钮区 Column() { Button('切换到全屏普通模式') .width('80%') .margin(5) .onClick(() => { (getContext(this) as any).ability.switchToFullScreenNormal(); })
Button('切换到全屏隐藏模式') .width('80%') .margin(5) .onClick(() => { (getContext(this) as any).ability.switchToFullScreenHidden(); }) } .margin(20)
// 示例内容卡片 ForEach([1, 2, 3, 4], (item) => { Row() { Text(`内容卡片 ${item}`) .fontSize(16) .color('#333') } .backgroundColor(Color.White) .width('90%') .height(100) .borderRadius(10) .margin(10) .justifyContent(FlexAlign.Center) }) } .width('100%') }
// 底部信息栏(延伸至导航区) Row() { Text('底部导航区') .fontSize(16) .color(Color.White) } .backgroundColor('#96dffa') .width('100%') .height(60) .justifyContent(FlexAlign.Center) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]) // 延伸至导航区 } .width('100%') .height('100%') .backgroundColor('#d5d5d5') }}
复制代码


发布于: 刚刚阅读数: 2
用户头像

GeorgeGcs

关注

路漫漫其修远兮,吾将上下而求索。 2024-12-24 加入

鸿蒙创作先锋,华为HDE专家,鸿蒙讲师,作者。 目前任职鸿蒙应用架构师。历经腾讯,宝马,研究所,金融。 待过私企,外企,央企。 深耕大应用开发领域十年。 OpenHarmony,HarmonyOS,Flutter,H5,Android,IOS。

评论

发布
暂无评论
【HarmonyOS】应用设置全屏和安全区域详解_GeorgeGcs_InfoQ写作社区