【HarmonyOS】应用设置全屏和安全区域详解
一、前言
IDE创建的鸿蒙应用,默认采取组件安全区布局方案。顶部会预留状态栏区域,底部会预留导航条区域。这就是所谓的安全区域。
如果不处理,界面效果很割裂。所以业内UI交互设计,都会设置应用为全屏布局。将页面绘制区域沾满整个界面。
或者将安全区域的颜色与应用UI设置为一致。
以上两种方式都是沉浸式布局的处理。所以全屏非沉浸式,概念不可混为一谈。 在移动应用开发中,"沉浸式效果"早已不是新鲜词,但要真正实现自然、和谐的沉浸式体验,却需要对系统布局、交互逻辑有深入理解。
二、什么是应用沉浸式效果?
简单来说,应用沉浸式效果是通过优化状态栏、应用界面与底部导航区域(导航条或三键导航)的视觉融合与交互适配,减少系统界面的突兀感,让用户注意力更聚焦于应用内容本身。

典型的界面元素包含三部分: 状态栏 :显示时间、电量等系统信息的顶部区域 应用界面 :承载应用核心内容的区域 底部导航区域:提供系统导航操作的底部区域
其中状态栏和底部导航区域被称为"避让区",其余区域为"安全区"。沉浸式开发的核心就是处理好这两个区域与应用内容的关系,主要涉及两类问题: UI元素避让 :避免可交互元素或关键信息被避让区遮挡 视觉融合:让避让区与应用界面的颜色、风格保持一致
三、如何设置沉浸式布局?
综上所述,我们可知,设置沉浸式布局有以下两种方式,如图所示:

1、方案一:窗口全屏布局方案
该方案通过将应用界面强制扩展到全屏(包括状态栏和导航区域),实现深度沉浸式体验。适合需要在避让区放置UI元素的场景,如视频播放器控制栏、游戏界面等。
场景1:保留避让区,需处理UI避让
当需要显示状态栏和导航区域,但希望应用内容延伸至这些区域时,需通过以下步骤实现:
(1)开启全屏布局
在应用启动时调用setWindowLayoutFullScreen
接口,让界面突破安全区限制:
typescript
// EntryAbility.ets
let windowClass = windowStage.getMainWindowSync();
windowClass.setWindowLayoutFullScreen(true).then(() => {
console.info("窗口已设置为全屏布局");
});
(2)获取并监听避让区尺寸
通过getWindowAvoidArea
获取状态栏和导航区域高度,并注册avoidAreaChange
监听动态变化(如屏幕旋转、折叠屏展开等场景):
typescript
// 获取状态栏高度
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重叠:
typescript
// 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
接口隐藏状态栏和导航区域:
typescript
// 隐藏状态栏
windowClass.setSpecificSystemBarEnabled('status', false);
// 隐藏导航区域
windowClass.setSpecificSystemBarEnabled('navigationIndicator', false);
(3)无需额外避让处理
此时界面已完全全屏,布局中无需设置避让padding,内容可直接铺满屏幕。
2、方案二:组件安全区方案
该方案为默认布局模式,UI元素自动限制在安全区内(无需手动处理避让),仅通过延伸背景绘制实现沉浸式效果。适合大多数普通应用,尤其是不需要在避让区布局UI的场景。
默认情况下,应用UI元素会自动避开避让区,但窗口背景可全屏绘制。通过以下方式优化视觉融合:
(1)状态栏与导航区域颜色相同时
直接设置窗口背景色与应用主背景一致,实现整体沉浸:
typescript
// EntryAbility.ets
windowStage.getMainWindowSync().setWindowBackgroundColor('#d5d5d5');
(2)颜色不同时:使用expandSafeArea扩展绘制
对顶部/底部组件单独设置expandSafeArea
属性,使其背景延伸至避让区:
typescript
// 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)典型场景适配技巧
背景图/视频场景: 让图片组件延伸至避让区
typescript
Image($r('app.media.bg'))
.width('100%').height('100%')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
滚动容器场景: 通过父容器扩展实现滚动背景沉浸
typescript
Scroll() {
Column() {
// 滚动内容...
}
.backgroundColor('rgb(213,213,213)')
}
.backgroundColor('rgb(213,213,213)')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
底部页签场景 : Navigation
/Tabs
组件默认支持背景延伸,自定义页签可手动设置:
typescript
// 自定义底部页签
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)
typescript
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)
typescript
export const pageMap = {
FULL_SCREEN_NORMAL: 'pages/FullScreenNormal',
FULL_SCREEN_HIDDEN: 'pages/FullScreenHidden',
SAFE_AREA_MODE: 'pages/SafeAreaMode'
};
3. 窗口全屏布局(不隐藏避让区)页面
typescript
import { EntryAbility } from '../Ability/EntryAbility';
import { pageMap } from '../common/Constants';
import { UIContext } from '@kit.ArkUI';
@Entry
@Component
struct 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. 窗口全屏布局(隐藏避让区)页面
typescript
import { pageMap } from '../common/Constants';
@Entry
@Component
struct 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. 组件安全区方案页面
typescript
import { SafeAreaEdge, SafeAreaType } from '@kit.ArkUI';
import { pageMap } from '../common/Constants';
@Entry
@Component
struct 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')
}
}