HarmonyOS APP开发中的状态栏
HarmonyOS APP开发中的状态栏:@ohos.window状态栏控制、状态栏显隐、状态栏颜色/内容、沉浸式状态栏、状态栏与全屏模式
📌 核心要点:通过 @ohos.window 模块精准控制状态栏的显隐、颜色与内容,实现沉浸式体验与全屏模式的无缝切换
一、背景与动机
你有没有遇到过这样的场景?——精心设计了一个全屏的启动页,结果顶部状态栏硬生生把你的大图切掉一块;或者做视频播放器的时候,状态栏的白色文字和你的浅色背景混在一起,用户根本看不清时间。
这就好比你精心布置了一个舞台,结果灯光师非要在舞台上方挂一排探照灯,你没法关掉它,也没法换个颜色。状态栏就是这样一个"既不能不要,又不太好用"的存在。
好在 HarmonyOS 给了我们足够的控制力。通过 @ohos.window 模块,我们可以像导演控制灯光一样,精确地操控状态栏的方方面面——显示还是隐藏、什么颜色、什么内容,甚至让它完全沉浸到我们的界面里。
二、核心原理
2.1 状态栏控制架构
状态栏的控制核心在于 Window 对象。每个 UIAbility 在创建时都会获得一个主窗口,我们通过这个窗口实例来操作状态栏属性。

flowchart TD
A[UIAbility启动] --> B[获取主窗口 WindowStage]
B --> C[windowStage.getMainWindow]
C --> D[主窗口实例 mainWindow]
D --> E{状态栏控制}
E --> F[setWindowLayoutFullScreen<br/>全屏布局]
E --> G[setSpecificStatusBar<br/>状态栏显隐]
E --> H[setWindowSystemBarProperties<br/>颜色/内容]
E --> I[状态栏内容颜色<br/>isLightIcon]
classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
classDef error fill:#F44336,stroke:#D32F2F,color:#fff
classDef info fill:#2196F3,stroke:#1976D2,color:#fff
classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff
class A,B primary
class C,D info
class E warning
class F,G,H,I purple
2.2 关键API一览
| API | 功能 | 说明 |
|---|---|---|
setWindowLayoutFullScreen(isFullScreen) |
设置窗口全屏布局 | true时布局延伸到状态栏区域 |
setSpecificStatusBar(statusBar, status) |
控制状态栏显隐 | 可单独控制状态栏或导航栏 |
setWindowSystemBarProperties(properties) |
设置状态栏属性 | 颜色、内容颜色等 |
getWindowAvoidArea(type) |
获取避让区域 | 获取状态栏高度等安全区域信息 |
2.3 沉浸式状态栏原理
沉浸式状态栏的本质是:让页面布局延伸到状态栏下方,同时保持状态栏可见。这就像给舞台加了一层透明玻璃——你能看到后面的布景,但灯光依然亮着。
关键步骤:
- 调用
setWindowLayoutFullScreen(true)让布局延伸 - 获取状态栏避让区域高度
- 在页面顶部添加对应高度的 padding,防止内容被遮挡
三、代码实战
3.1 基础状态栏控制:显隐与颜色
最基础的场景——控制状态栏的显示/隐藏,以及修改状态栏背景色和内容颜色。
// StatusBarController.ets
// 状态栏基础控制器:显隐与颜色控制
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct StatusBarBasicDemo {
// 状态栏是否可见
@State isStatusBarVisible: boolean = true;
// 当前状态栏背景色
@State currentBgColor: string = '#00000000';
// 状态栏内容是否为浅色(白色图标/文字)
@State isLightContent: boolean = false;
// 预设颜色方案
private colorSchemes: Array<{ name: string; bgColor: string; isLight: boolean }> = [
{ name: '透明沉浸', bgColor: '#00000000', isLight: true },
{ name: '深邃黑', bgColor: '#FF1A1A2E', isLight: true },
{ name: '海洋蓝', bgColor: '#FF0077B6', isLight: true },
{ name: '暖阳橙', bgColor: '#FFFF8800', isLight: false },
{ name: '薄荷绿', bgColor: '#FF2EC4B6', isLight: false },
];
aboutToAppear(): void {
this.initStatusBar();
}
// 初始化状态栏
private async initStatusBar(): Promise<void> {
try {
const mainWindow = await this.getMainWindow();
if (mainWindow) {
// 默认设置为沉浸式
await mainWindow.setWindowLayoutFullScreen(true);
}
} catch (error) {
console.error(`初始化状态栏失败: ${JSON.stringify(error)}`);
}
}
// 获取主窗口实例
private async getMainWindow(): Promise<window.Window | null> {
try {
const context = getContext(this) as common.UIAbilityContext;
return await window.getLastWindow(context);
} catch (error) {
console.error(`获取主窗口失败: ${JSON.stringify(error)}`);
return null;
}
}
// 切换状态栏显隐
private async toggleStatusBar(visible: boolean): Promise<void> {
try {
const mainWindow = await this.getMainWindow();
if (mainWindow) {
await mainWindow.setSpecificStatusBar(window.StatusBarType.STATUS_BAR, visible);
this.isStatusBarVisible = visible;
}
} catch (error) {
console.error(`切换状态栏显隐失败: ${JSON.stringify(error)}`);
}
}
// 应用颜色方案
private async applyColorScheme(scheme: { bgColor: string; isLight: boolean }): Promise<void> {
try {
const mainWindow = await this.getMainWindow();
if (mainWindow) {
await mainWindow.setWindowSystemBarProperties({
statusBarColor: scheme.bgColor,
statusBarContentColor: scheme.isLight ? '#FFFFFF' : '#000000',
});
this.currentBgColor = scheme.bgColor;
this.isLightContent = scheme.isLight;
}
} catch (error) {
console.error(`应用颜色方案失败: ${JSON.stringify(error)}`);
}
}
build() {
Column() {
// 顶部标题区域(带状态栏避让)
Column() {
Text('状态栏控制台')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
Text('掌控每一像素的视觉体验')
.fontSize(14)
.fontColor('#FFFFFFAA')
.margin({ top: 4 })
}
.width('100%')
.padding({ top: 56, left: 20, right: 20, bottom: 20 })
.linearGradient({
direction: GradientDirection.BottomRight,
colors: [['#1A1A2E', 0], ['#16213E', 1]]
})
// 显隐控制
Row() {
Text('状态栏显隐')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#E0E0E0')
Blank()
Toggle({ type: ToggleType.Switch, isOn: this.isStatusBarVisible })
.onChange((isOn: boolean) => {
this.toggleStatusBar(isOn);
})
.selectedColor('#4CAF50')
}
.width('100%')
.padding({ left: 20, right: 20, top: 16, bottom: 8 })
// 颜色方案选择
Text('颜色方案')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#E0E0E0')
.margin({ left: 20, top: 16, bottom: 8 })
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(this.colorSchemes, (scheme: { name: string; bgColor: string; isLight: boolean }) => {
Column() {
Row()
.width(40)
.height(40)
.borderRadius(20)
.backgroundColor(scheme.bgColor === '#00000000' ? '#333333' : scheme.bgColor)
.border({ width: 1, color: '#FFFFFF33' })
Text(scheme.name)
.fontSize(12)
.fontColor('#BDBDBD')
.margin({ top: 4 })
}
.margin({ left: 12, bottom: 12 })
.onClick(() => {
this.applyColorScheme(scheme);
})
})
}
.width('100%')
.padding({ left: 8, right: 8 })
// 当前状态信息
Column() {
Text('当前状态')
.fontSize(14)
.fontColor('#9E9E9E')
.margin({ bottom: 8 })
Row() {
Text(`可见: ${this.isStatusBarVisible ? '✓' : '✗'}`)
.fontSize(13)
.fontColor('#BDBDBD')
Text(`背景: ${this.currentBgColor}`)
.fontSize(13)
.fontColor('#BDBDBD')
.margin({ left: 16 })
Text(`内容: ${this.isLightContent ? '浅色' : '深色'}`)
.fontSize(13)
.fontColor('#BDBDBD')
.margin({ left: 16 })
}
}
.width('100%')
.padding(16)
.margin({ left: 20, right: 20, top: 16 })
.borderRadius(12)
.backgroundColor('#1E1E2E')
}
.width('100%')
.height('100%')
.backgroundColor('#121212')
}
}
3.2 沉浸式状态栏:避让区域计算与适配
沉浸式状态栏的关键在于正确处理避让区域——让内容延伸到状态栏下方,同时保证重要内容不被遮挡。
// ImmersiveStatusBar.ets
// 沉浸式状态栏:避让区域计算与适配
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct ImmersiveStatusBarDemo {
// 状态栏高度(避让区域)
@State statusBarHeight: number = 0;
// 是否使用沉浸式
@State isImmersive: boolean = true;
// 页面滚动偏移
@State scrollOffset: number = 0;
aboutToAppear(): void {
this.setupImmersiveMode();
}
// 设置沉浸式模式并获取避让区域
private async setupImmersiveMode(): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const mainWindow = await window.getLastWindow(context);
// 开启全屏布局
await mainWindow.setWindowLayoutFullScreen(true);
// 设置状态栏为透明
await mainWindow.setWindowSystemBarProperties({
statusBarColor: '#00000000',
statusBarContentColor: '#FFFFFF',
});
// 获取状态栏避让区域
const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_STATUS_BAR);
this.statusBarHeight = px2vp(avoidArea.topRect.height);
console.info(`状态栏高度: ${this.statusBarHeight}vp`);
} catch (error) {
console.error(`设置沉浸式模式失败: ${JSON.stringify(error)}`);
}
}
// 退出沉浸式模式
private async exitImmersiveMode(): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const mainWindow = await window.getLastWindow(context);
await mainWindow.setWindowLayoutFullScreen(false);
await mainWindow.setWindowSystemBarProperties({
statusBarColor: '#1A1A2E',
statusBarContentColor: '#FFFFFF',
});
} catch (error) {
console.error(`退出沉浸式模式失败: ${JSON.stringify(error)}`);
}
}
build() {
Stack() {
// 背景大图(延伸到状态栏下方)
Image($r('app.media.startIcon'))
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
.blur(this.scrollOffset > 50 ? 10 : 0)
// 内容层
Scroll() {
Column() {
// 顶部安全区域占位
Column()
.width('100%')
.height(this.isImmersive ? this.statusBarHeight : 0)
// 顶部标题栏
Row() {
Text('🏠')
.fontSize(20)
Text('沉浸式体验')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.margin({ left: 8 })
Blank()
Text('⚙️')
.fontSize(20)
.onClick(() => {
this.isImmersive = !this.isImmersive;
if (this.isImmersive) {
this.setupImmersiveMode();
} else {
this.exitImmersiveMode();
}
})
}
.width('100%')
.padding({ left: 20, right: 20, top: 8, bottom: 8 })
// 内容卡片列表
ForEach([
{ title: '什么是沉浸式状态栏', desc: '让页面布局延伸到状态栏区域,状态栏变为透明,内容与系统UI融为一体' },
{ title: '避让区域的作用', desc: '避让区域告诉我们状态栏占据了多少空间,以便在布局中添加对应的padding' },
{ title: 'px2vp的必要性', desc: '系统返回的避让区域高度是px单位,需要通过px2vp转换为vp才能在布局中使用' },
{ title: '全屏布局 vs 全屏模式', desc: '全屏布局(setWindowLayoutFullScreen)只影响布局延伸,全屏模式还会隐藏状态栏' },
], (item: { title: string; desc: string }, index: number) => {
Column() {
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
Text(item.desc)
.fontSize(13)
.fontColor('#FFFFFFAA')
.margin({ top: 6 })
.maxLines(2)
}
.width('100%')
.padding(16)
.margin({ top: index === 0 ? 16 : 8, left: 16, right: 16 })
.borderRadius(12)
.backgroundColor('#1E1E2ECC')
.backdropBlur(20)
})
// 底部间距
Column()
.height(60)
}
}
.width('100%')
.height('100%')
.scrollable(ScrollDirection.Vertical)
.onScroll(() => {
// 滚动时可以做一些视差效果
})
}
.width('100%')
.height('100%')
}
}
3.3 视频播放器全屏模式:状态栏自动显隐
视频播放器是状态栏控制最典型的应用场景——播放时隐藏状态栏实现全屏,暂停或退出时恢复显示。
// VideoPlayerStatusBar.ets
// 视频播放器全屏模式:状态栏自动显隐
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct VideoPlayerStatusBarDemo {
// 是否全屏播放
@State isFullScreen: boolean = false;
// 控制栏是否可见
@State isControlBarVisible: boolean = true;
// 播放进度
@State playProgress: number = 35;
// 是否正在播放
@State isPlaying: boolean = false;
// 状态栏高度
@State statusBarHeight: number = 0;
// 控制栏自动隐藏定时器
private hideTimer: number = -1;
aboutToAppear(): void {
this.getStatusBarHeight();
}
// 获取状态栏高度
private async getStatusBarHeight(): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const mainWindow = await window.getLastWindow(context);
const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_STATUS_BAR);
this.statusBarHeight = px2vp(avoidArea.topRect.height);
} catch (error) {
console.error(`获取状态栏高度失败: ${JSON.stringify(error)}`);
}
}
// 进入全屏模式
private async enterFullScreen(): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const mainWindow = await window.getLastWindow(context);
// 隐藏状态栏
await mainWindow.setSpecificStatusBar(window.StatusBarType.STATUS_BAR, false);
// 全屏布局
await mainWindow.setWindowLayoutFullScreen(true);
// 设置屏幕常亮(可选)
await mainWindow.setWindowKeepScreenOn(true);
this.isFullScreen = true;
this.scheduleHideControlBar();
} catch (error) {
console.error(`进入全屏模式失败: ${JSON.stringify(error)}`);
}
}
// 退出全屏模式
private async exitFullScreen(): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const mainWindow = await window.getLastWindow(context);
// 显示状态栏
await mainWindow.setSpecificStatusBar(window.StatusBarType.STATUS_BAR, true);
// 退出全屏布局
await mainWindow.setWindowLayoutFullScreen(false);
// 取消屏幕常亮
await mainWindow.setWindowKeepScreenOn(false);
this.isFullScreen = false;
this.isControlBarVisible = true;
clearTimeout(this.hideTimer);
} catch (error) {
console.error(`退出全屏模式失败: ${JSON.stringify(error)}`);
}
}
// 定时隐藏控制栏
private scheduleHideControlBar(): void {
clearTimeout(this.hideTimer);
this.hideTimer = setTimeout(() => {
if (this.isPlaying) {
this.isControlBarVisible = false;
}
}, 5000);
}
build() {
Stack() {
// 视频画面区域(模拟)
Column() {
Image($r('app.media.startIcon'))
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
}
.width('100%')
.height('100%')
.backgroundColor('#000000')
// 控制栏层
if (this.isControlBarVisible || !this.isPlaying) {
Column() {
// 顶部控制栏
Row() {
Text('◀')
.fontSize(22)
.fontColor('#FFFFFF')
.onClick(() => {
if (this.isFullScreen) {
this.exitFullScreen();
}
})
Text('视频标题 - 沉浸式播放体验')
.fontSize(16)
.fontColor('#FFFFFF')
.margin({ left: 12 })
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Blank()
Text(this.isFullScreen ? '⤓' : '⤒')
.fontSize(22)
.fontColor('#FFFFFF')
.onClick(() => {
if (this.isFullScreen) {
this.exitFullScreen();
} else {
this.enterFullScreen();
}
})
}
.width('100%')
.padding({ left: 16, right: 16, top: this.isFullScreen ? this.statusBarHeight + 8 : 8, bottom: 8 })
.linearGradient({
direction: GradientDirection.Bottom,
colors: [['#000000CC', 0], ['#00000000', 1]]
})
Blank()
// 底部控制栏
Column() {
// 进度条
Row() {
Text('02:15')
.fontSize(12)
.fontColor('#FFFFFFCC')
Progress({ value: this.playProgress, total: 100, type: ProgressType.Linear })
.width('60%')
.color('#4CAF50')
.backgroundColor('#FFFFFF33')
Text('06:30')
.fontSize(12)
.fontColor('#FFFFFFCC')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({ left: 16, right: 16 })
// 播放控制按钮
Row() {
Text('⏮')
.fontSize(24)
.fontColor('#FFFFFF')
Text(this.isPlaying ? '⏸' : '▶')
.fontSize(32)
.fontColor('#FFFFFF')
.margin({ left: 24, right: 24 })
.onClick(() => {
this.isPlaying = !this.isPlaying;
if (this.isPlaying && this.isFullScreen) {
this.scheduleHideControlBar();
}
})
Text('⏭')
.fontSize(24)
.fontColor('#FFFFFF')
Blank()
Text(this.isFullScreen ? '退出全屏' : '全屏')
.fontSize(14)
.fontColor('#FFFFFF')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(16)
.backgroundColor('#FFFFFF22')
.onClick(() => {
if (this.isFullScreen) {
this.exitFullScreen();
} else {
this.enterFullScreen();
}
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 8, bottom: this.isFullScreen ? 24 : 8 })
}
.linearGradient({
direction: GradientDirection.Top,
colors: [['#000000CC', 0], ['#00000000', 1]]
})
}
.width('100%')
.height('100%')
}
.animation({ duration: 300, curve: Curve.EaseInOut })
// 点击区域:切换控制栏显隐
Column()
.width('60%')
.height('60%')
.onClick(() => {
this.isControlBarVisible = !this.isControlBarVisible;
if (this.isControlBarVisible && this.isPlaying && this.isFullScreen) {
this.scheduleHideControlBar();
}
})
}
.width('100%')
.height('100%')
}
}
四、踩坑与注意事项
4.1 避让区域高度的单位陷阱
这是最常见的一个坑——getWindowAvoidArea 返回的高度是 px 单位,而 ArkUI 布局使用的是 vp 单位。如果你直接用 px 值做 padding,在不同密度的设备上会出现明显的偏差。
// ❌ 错误:直接使用px值
const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_STATUS_BAR);
this.statusBarHeight = avoidArea.topRect.height; // px单位,直接用会偏大或偏小
// ✅ 正确:转换为vp
const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_STATUS_BAR);
this.statusBarHeight = px2vp(avoidArea.topRect.height); // 转换为vp
4.2 setWindowLayoutFullScreen 的时机问题
setWindowLayoutFullScreen 必须在窗口创建完成后调用。如果在 onCreate 阶段过早调用,可能会因为窗口尚未初始化而失败。推荐在 onWindowStageCreate 回调中或页面 aboutToAppear 中调用。
4.3 状态栏颜色不生效的问题
有时候你明明调用了 setWindowSystemBarProperties,但状态栏颜色就是不变。这通常是因为:
- 没有先设置全屏布局——在某些设备上,非全屏布局模式下状态栏颜色可能被系统覆盖
- 颜色格式不对——必须使用
#AARRGGBB格式,缺了 alpha 通道会不生效 - 调用时机太早——窗口还没准备好就调用了
4.4 多窗口场景的注意事项
如果你的应用有多个窗口(比如画中画、悬浮窗),每个窗口需要独立控制自己的状态栏。获取窗口实例时要确保拿到的是正确的窗口。
五、HarmonyOS 6适配
5.1 API变更
| 变更项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| 状态栏控制 | setSpecificStatusBar |
新增 setStatusBarStyle 统一接口 |
| 避让区域 | getWindowAvoidArea |
新增 getWindowSafeArea 支持折叠屏 |
| 全屏模式 | setWindowLayoutFullScreen |
新增 setImmersiveMode 一键沉浸式 |
5.2 迁移指南
// HarmonyOS 5 写法
await mainWindow.setWindowLayoutFullScreen(true);
await mainWindow.setWindowSystemBarProperties({
statusBarColor: '#00000000',
statusBarContentColor: '#FFFFFF',
});
// HarmonyOS 6 写法(推荐)
await mainWindow.setImmersiveMode({
enable: true,
statusBarStyle: window.StatusBarStyle.TRANSPARENT,
contentColor: '#FFFFFF',
});
5.3 折叠屏适配
HarmonyOS 6 加强了折叠屏设备的状态栏适配。折叠屏展开时状态栏可能位于不同位置,需要使用新的 getWindowSafeArea API 获取更精确的安全区域信息。
六、总结
mindmap
root((状态栏控制))
显隐控制
setSpecificStatusBar
全屏模式自动隐藏
视频播放器场景
颜色控制
setWindowSystemBarProperties
背景色 statusBarColor
内容色 statusBarContentColor
#AARRGGBB格式
沉浸式
setWindowLayoutFullScreen
避让区域 getWindowAvoidArea
px2vp单位转换
顶部padding适配
全屏模式
隐藏状态栏+全屏布局
屏幕常亮 setWindowKeepScreenOn
控制栏自动隐藏
踩坑要点
px vs vp单位
调用时机
颜色格式
多窗口独立控制
HarmonyOS 6
setImmersiveMode统一接口
折叠屏安全区域
getWindowSafeArea
classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
classDef error fill:#F44336,stroke:#D32F2F,color:#fff
classDef info fill:#2196F3,stroke:#1976D2,color:#fff
classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff
| 知识点 | 关键API | 核心要点 |
|---|---|---|
| 状态栏显隐 | setSpecificStatusBar |
可独立控制状态栏和导航栏 |
| 状态栏颜色 | setWindowSystemBarProperties |
必须使用 #AARRGGBB 格式 |
| 沉浸式布局 | setWindowLayoutFullScreen |
布局延伸到状态栏下方 |
| 避让区域 | getWindowAvoidArea + px2vp |
px 转 vp 是必须步骤 |
| 全屏模式 | 显隐 + 全屏布局 + 常亮 | 视频播放器典型场景 |
状态栏控制看似简单,实则暗藏玄机。从最基本的显隐切换到沉浸式适配,再到全屏模式的自动管理,每一步都需要对系统API有深入的理解。记住那个最关键的坑——px和vp的转换,这能帮你省去大半的调试时间。
- 点赞
- 收藏
- 关注作者
评论(0)