玩转HarmonyOS APP导航栏
玩转HarmonyOS APP导航栏
📌 核心要点:通过 @ohos.window 模块控制系统导航栏的显隐、颜色与行为,适配三键导航和手势导航两种模式,确保应用底部布局完美避让
一、背景与动机
你有没有遇到过这种情况?——做了一个精美的底部Tab栏,结果系统导航栏硬生生挤在下面,你的Tab栏和导航栏叠在一起,活像两排牙齿挤在一张嘴里。或者更惨的,你做了一个全屏游戏,底部的手势指示条总是在那里晃来晃去,玩家一个误触就退出了游戏。
导航栏就像一个"不请自来的客人"——它确实有用,帮你返回、回到桌面、切换应用,但它总是占据底部最宝贵的空间,而且你还没法完全忽视它。
HarmonyOS 提供了两种导航模式:三键导航(返回键、主页键、最近任务键)和手势导航(从边缘滑动操作)。这两种模式下的导航栏形态完全不同,适配策略也大相径庭。今天我们就来彻底搞懂它。
二、核心原理
2.1 导航栏控制架构
导航栏和状态栏同属系统栏(SystemBar),它们共享同一套控制机制,但可以独立操作。

flowchart TD
A[系统导航栏] --> B{导航模式}
B --> C[三键导航<br/>固定显示底部栏]
B --> D[手势导航<br/>底部手势指示条]
C --> E[控制方式]
D --> E
E --> F[setSpecificStatusBar<br/>NAVIGATION_BAR 显隐]
E --> G[setWindowSystemBarProperties<br/>颜色/内容颜色]
E --> H[setWindowLayoutFullScreen<br/>布局延伸]
F --> I[避让区域适配]
G --> I
H --> I
I --> J[getWindowAvoidArea<br/>TYPE_NAVIGATION_BAR]
I --> K[底部padding适配]
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 primary
class B,C,D info
class E,F,G,H warning
class I,J,K purple
2.2 两种导航模式对比
| 特性 | 三键导航 | 手势导航 |
|---|---|---|
| 视觉形态 | 底部固定栏,三个虚拟按键 | 底部细长指示条 |
| 占用高度 | 较高(约48dp) | 较低(约20dp) |
| 避让区域 | 需要完整避让 | 需要避让指示条区域 |
| 隐藏后 | 完全隐藏,无法操作 | 隐藏指示条,手势仍可用 |
| 用户体验 | 明确的按键反馈 | 沉浸感更强 |
| 适配难度 | 简单,固定高度 | 复杂,需要考虑手势区域 |
2.3 避让区域机制
导航栏的避让区域(AvoidArea)告诉我们导航栏占据了多少空间。这个值因设备、导航模式、屏幕方向而异,绝对不能硬编码。
// 获取导航栏避让区域
const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_BAR);
const navBarHeight = px2vp(avoidArea.bottomRect.height);
三、代码实战
3.1 导航栏显隐与颜色控制
最基本的操作——控制导航栏的显示/隐藏,以及修改导航栏的背景色和内容颜色。
// NavBarControl.ets
// 导航栏显隐与颜色控制
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct NavBarControlDemo {
// 导航栏是否可见
@State isNavBarVisible: boolean = true;
// 导航栏背景色
@State navBarBgColor: string = '#FF121212';
// 导航栏内容是否为浅色
@State isLightContent: boolean = true;
// 导航栏高度
@State navBarHeight: number = 0;
// 预设主题
private themes: Array<{ name: string; bgColor: string; isLight: boolean; icon: string }> = [
{ name: '深邃夜空', bgColor: '#FF121212', isLight: true, icon: '🌙' },
{ name: '极光绿', bgColor: '#FF1B5E20', isLight: true, icon: '🌿' },
{ name: '深海蓝', bgColor: '#FF0D47A1', isLight: true, icon: '🌊' },
{ name: '暖阳橙', bgColor: '#FFE65100', isLight: false, icon: '🌅' },
{ name: '薄荷白', bgColor: '#FFF5F5F5', isLight: false, icon: '🍃' },
];
aboutToAppear(): void {
this.getNavBarHeight();
}
// 获取导航栏高度
private async getNavBarHeight(): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const mainWindow = await window.getLastWindow(context);
const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_BAR);
this.navBarHeight = px2vp(avoidArea.bottomRect.height);
} catch (error) {
console.error(`获取导航栏高度失败: ${JSON.stringify(error)}`);
}
}
// 切换导航栏显隐
private async toggleNavBar(visible: boolean): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const mainWindow = await window.getLastWindow(context);
await mainWindow.setSpecificStatusBar(window.StatusBarType.NAVIGATION_BAR, visible);
this.isNavBarVisible = visible;
} catch (error) {
console.error(`切换导航栏显隐失败: ${JSON.stringify(error)}`);
}
}
// 应用导航栏主题
private async applyNavBarTheme(theme: { bgColor: string; isLight: boolean }): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const mainWindow = await window.getLastWindow(context);
await mainWindow.setWindowSystemBarProperties({
navigationBarColor: theme.bgColor,
navigationBarContentColor: theme.isLight ? '#FFFFFF' : '#000000',
});
this.navBarBgColor = theme.bgColor;
this.isLightContent = theme.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.isNavBarVisible })
.onChange((isOn: boolean) => {
this.toggleNavBar(isOn);
})
.selectedColor('#4CAF50')
}
.width('100%')
.padding({ left: 20, right: 20, top: 16, bottom: 8 })
// 导航栏高度信息
Row() {
Text(`导航栏高度: ${this.navBarHeight.toFixed(1)}vp`)
.fontSize(14)
.fontColor('#9E9E9E')
Text(`当前: ${this.isNavBarVisible ? '可见' : '隐藏'}`)
.fontSize(14)
.fontColor(this.isNavBarVisible ? '#4CAF50' : '#F44336')
.margin({ left: 16 })
}
.width('100%')
.padding({ left: 20, top: 8, bottom: 8 })
// 主题选择
Text('导航栏主题')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#E0E0E0')
.margin({ left: 20, top: 16, bottom: 8 })
Scroll() {
Row({ space: 12 }) {
ForEach(this.themes, (theme: { name: string; bgColor: string; isLight: boolean; icon: string }) => {
Column() {
Text(theme.icon)
.fontSize(28)
Row()
.width(48)
.height(24)
.borderRadius(8)
.backgroundColor(theme.bgColor)
.border({ width: 1, color: '#FFFFFF33' })
.margin({ top: 6 })
Text(theme.name)
.fontSize(11)
.fontColor('#BDBDBD')
.margin({ top: 4 })
}
.padding(12)
.borderRadius(12)
.backgroundColor('#1E1E2E')
.onClick(() => {
this.applyNavBarTheme(theme);
})
})
}
.padding({ left: 20, right: 20 })
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
// 注意事项提示
Column() {
Text('⚠️ 注意事项')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#FF9800')
.margin({ bottom: 8 })
Text('• 隐藏导航栏后,三键导航将不可用')
.fontSize(12)
.fontColor('#BDBDBD')
Text('• 手势导航模式下隐藏指示条,手势仍可用')
.fontSize(12)
.fontColor('#BDBDBD')
.margin({ top: 4 })
Text('• 导航栏颜色需要 #AARRGGBB 格式')
.fontSize(12)
.fontColor('#BDBDBD')
.margin({ top: 4 })
Text('• 避让区域高度因设备和导航模式而异')
.fontSize(12)
.fontColor('#BDBDBD')
.margin({ top: 4 })
}
.width('100%')
.padding(16)
.margin({ left: 20, right: 20, top: 16 })
.borderRadius(12)
.backgroundColor('#1E1E2E')
.border({ width: 1, color: '#FF980033' })
}
.width('100%')
.height('100%')
.backgroundColor('#121212')
}
}
3.2 沉浸式导航栏:底部避让适配
和状态栏一样,导航栏也需要沉浸式适配。关键是正确获取底部避让区域高度,给内容区域添加对应的 padding。
// ImmersiveNavBar.ets
// 沉浸式导航栏:底部避让适配
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct ImmersiveNavBarDemo {
// 状态栏高度
@State statusBarHeight: number = 0;
// 导航栏高度
@State navBarHeight: number = 0;
// 是否沉浸式
@State isImmersive: boolean = true;
// 当前选中的Tab
@State currentTab: number = 0;
// Tab数据
private tabs: Array<{ icon: string; label: string }> = [
{ icon: '🏠', label: '首页' },
{ icon: '🔍', label: '发现' },
{ icon: '➕', label: '发布' },
{ icon: '💬', label: '消息' },
{ icon: '👤', label: '我的' },
];
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',
navigationBarColor: '#00000000',
navigationBarContentColor: '#FFFFFF',
});
// 获取避让区域
const statusAvoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_STATUS_BAR);
const navAvoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_BAR);
this.statusBarHeight = px2vp(statusAvoidArea.topRect.height);
this.navBarHeight = px2vp(navAvoidArea.bottomRect.height);
} 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({
navigationBarColor: '#FF121212',
navigationBarContentColor: '#FFFFFF',
});
} catch (error) {
console.error(`退出沉浸式模式失败: ${JSON.stringify(error)}`);
}
}
build() {
Stack() {
// 背景渐变
Column()
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [['#1A1A2E', 0], ['#0F3460', 0.5], ['#1A1A2E', 1]]
})
Column() {
// 顶部安全区域
Column()
.width('100%')
.height(this.isImmersive ? this.statusBarHeight : 0)
// 顶部标题栏
Row() {
Text('沉浸式导航栏')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
Blank()
Text(this.isImmersive ? '退出沉浸' : '开启沉浸')
.fontSize(13)
.fontColor('#4CAF50')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(16)
.backgroundColor('#4CAF5022')
.onClick(() => {
this.isImmersive = !this.isImmersive;
if (this.isImmersive) {
this.setupImmersiveMode();
} else {
this.exitImmersiveMode();
}
})
}
.width('100%')
.padding({ left: 20, right: 20, top: 8, bottom: 8 })
// 内容区域
Scroll() {
Column({ space: 12 }) {
ForEach([
{ title: '沉浸式导航栏原理', desc: '让布局延伸到导航栏下方,导航栏变为透明,内容与系统UI无缝融合' },
{ title: '底部避让的关键', desc: '导航栏虽然透明,但仍然占据底部空间。必须通过避让区域获取高度,给底部内容添加padding' },
{ title: '三键 vs 手势导航', desc: '三键导航占用更多底部空间,手势导航只有一条指示条。两种模式下的避让高度不同' },
{ title: 'Tab栏的适配策略', desc: 'Tab栏应该在导航栏避让区域之上,或者与导航栏融合设计' },
], (item: { title: string; desc: string }, index: number) => {
Column() {
Text(item.title)
.fontSize(15)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
Text(item.desc)
.fontSize(12)
.fontColor('#FFFFFFAA')
.margin({ top: 4 })
}
.width('100%')
.padding(14)
.borderRadius(10)
.backgroundColor('#1E1E2ECC')
.backdropBlur(10)
})
}
.padding({ left: 16, right: 16, top: 8 })
}
.layoutWeight(1)
.scrollable(ScrollDirection.Vertical)
// 底部Tab栏(在导航栏避让区域之上)
Row() {
ForEach(this.tabs, (tab: { icon: string; label: string }, index: number) => {
Column() {
Text(tab.icon)
.fontSize(22)
Text(tab.label)
.fontSize(10)
.fontColor(this.currentTab === index ? '#4CAF50' : '#9E9E9E')
.margin({ top: 2 })
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.currentTab = index;
})
})
}
.width('100%')
.height(56)
.backgroundColor('#1A1A2EEE')
.backdropBlur(20)
// 底部安全区域(导航栏避让)
Column()
.width('100%')
.height(this.isImmersive ? this.navBarHeight : 0)
.backgroundColor('#1A1A2EEE')
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
}
3.3 全屏游戏模式:导航栏与状态栏联动控制
游戏场景需要同时隐藏状态栏和导航栏,实现真正的全屏沉浸体验。同时需要处理用户从底部上滑唤出导航栏的交互。
// GameNavBarControl.ets
// 全屏游戏模式:导航栏与状态栏联动控制
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct GameNavBarControlDemo {
// 是否全屏游戏模式
@State isGameMode: boolean = false;
// 游戏暂停菜单是否可见
@State isPauseMenuVisible: boolean = false;
// 导航栏高度
@State navBarHeight: number = 0;
// 状态栏高度
@State statusBarHeight: number = 0;
// 游戏分数
@State score: number = 0;
// 生命值
@State lives: number = 3;
aboutToAppear(): void {
this.getSystemBarHeights();
}
// 获取系统栏高度
private async getSystemBarHeights(): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const mainWindow = await window.getLastWindow(context);
const statusAvoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_STATUS_BAR);
const navAvoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_BAR);
this.statusBarHeight = px2vp(statusAvoidArea.topRect.height);
this.navBarHeight = px2vp(navAvoidArea.bottomRect.height);
} catch (error) {
console.error(`获取系统栏高度失败: ${JSON.stringify(error)}`);
}
}
// 进入游戏模式
private async enterGameMode(): 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.setSpecificStatusBar(window.StatusBarType.NAVIGATION_BAR, false);
// 全屏布局
await mainWindow.setWindowLayoutFullScreen(true);
// 屏幕常亮
await mainWindow.setWindowKeepScreenOn(true);
// 横屏锁定(可选)
await mainWindow.setPreferredOrientation(window.Orientation.LANDSCAPE);
this.isGameMode = true;
this.isPauseMenuVisible = false;
} catch (error) {
console.error(`进入游戏模式失败: ${JSON.stringify(error)}`);
}
}
// 退出游戏模式
private async exitGameMode(): 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.setSpecificStatusBar(window.StatusBarType.NAVIGATION_BAR, true);
// 退出全屏布局
await mainWindow.setWindowLayoutFullScreen(false);
// 取消屏幕常亮
await mainWindow.setWindowKeepScreenOn(false);
// 恢复竖屏
await mainWindow.setPreferredOrientation(window.Orientation.PORTRAIT);
this.isGameMode = false;
this.isPauseMenuVisible = false;
} catch (error) {
console.error(`退出游戏模式失败: ${JSON.stringify(error)}`);
}
}
build() {
Stack() {
if (this.isGameMode) {
// ===== 游戏界面 =====
Column() {
// 游戏画面(模拟)
Stack() {
// 游戏背景
Column()
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [['#0D1B2A', 0], ['#1B2838', 0.5], ['#0D1B2A', 1]]
})
// 游戏HUD
Column() {
Row() {
// 分数
Text(`⭐ ${this.score}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFD700')
Blank()
// 生命值
Row() {
ForEach([0, 1, 2], (index: number) => {
Text(index < this.lives ? '❤️' : '🖤')
.fontSize(18)
})
}
}
.width('100%')
.padding({ left: 24, right: 24, top: 12 })
Blank()
// 底部控制按钮
Row() {
Text('⏸ 暂停')
.fontSize(14)
.fontColor('#FFFFFF')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.borderRadius(20)
.backgroundColor('#FFFFFF22')
.onClick(() => {
this.isPauseMenuVisible = true;
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding({ bottom: 24 })
}
.width('100%')
.height('100%')
// 暂停菜单
if (this.isPauseMenuVisible) {
Column() {
Text('游戏暂停')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.margin({ bottom: 32 })
Column({ space: 12 }) {
Text('▶ 继续游戏')
.fontSize(16)
.fontColor('#FFFFFF')
.width('60%')
.textAlign(TextAlign.Center)
.padding({ top: 12, bottom: 12 })
.borderRadius(24)
.backgroundColor('#4CAF50')
.onClick(() => {
this.isPauseMenuVisible = false;
})
Text('🔄 重新开始')
.fontSize(16)
.fontColor('#FFFFFF')
.width('60%')
.textAlign(TextAlign.Center)
.padding({ top: 12, bottom: 12 })
.borderRadius(24)
.backgroundColor('#2196F3')
.onClick(() => {
this.score = 0;
this.lives = 3;
this.isPauseMenuVisible = false;
})
Text('🚪 退出游戏')
.fontSize(16)
.fontColor('#FFFFFF')
.width('60%')
.textAlign(TextAlign.Center)
.padding({ top: 12, bottom: 12 })
.borderRadius(24)
.backgroundColor('#F44336')
.onClick(() => {
this.exitGameMode();
})
}
}
.width('80%')
.padding(32)
.borderRadius(20)
.backgroundColor('#1E1E2EEE')
.backdropBlur(30)
.border({ width: 1, color: '#FFFFFF22' })
}
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
} else {
// ===== 主菜单界面 =====
Column() {
// 顶部安全区域
Column()
.width('100%')
.height(this.statusBarHeight)
Column() {
Text('🎮')
.fontSize(64)
Text('太空冒险')
.fontSize(36)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.margin({ top: 16 })
Text('全屏沉浸式游戏体验')
.fontSize(14)
.fontColor('#FFFFFFAA')
.margin({ top: 8 })
}
.margin({ top: 60 })
Blank()
// 开始游戏按钮
Text('🚀 开始游戏')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.padding({ left: 48, right: 48, top: 16, bottom: 16 })
.borderRadius(30)
.linearGradient({
direction: GradientDirection.Right,
colors: [['#4CAF50', 0], ['#2196F3', 1]]
})
.shadow({ radius: 20, color: '#4CAF5044', offsetY: 4 })
.onClick(() => {
this.enterGameMode();
})
Text('进入游戏后将隐藏状态栏和导航栏')
.fontSize(12)
.fontColor('#9E9E9E')
.margin({ top: 12, bottom: 40 })
// 底部安全区域
Column()
.width('100%')
.height(this.navBarHeight)
}
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [['#0D1B2A', 0], ['#1B2838', 0.5], ['#0D1B2A', 1]]
})
}
}
.width('100%')
.height('100%')
}
}
四、踩坑与注意事项
4.1 导航栏高度不能硬编码
这是新手最容易犯的错误。不同设备、不同导航模式、不同屏幕方向下,导航栏的高度都不一样。
// ❌ 错误:硬编码导航栏高度
.padding({ bottom: 48 }) // 三键导航可能对,手势导航就偏大了
// ✅ 正确:动态获取避让区域
const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_BAR);
const navBarHeight = px2vp(avoidArea.bottomRect.height);
.padding({ bottom: navBarHeight })
4.2 手势导航的"幽灵区域"
手势导航模式下,即使你隐藏了导航栏指示条,底部的手势操作区域仍然存在。用户从底部上滑仍然可以返回或回到桌面。这意味着:
- 你不能在底部边缘放置需要频繁点击的按钮
- 游戏场景中,底部边缘的触摸可能被系统拦截
- 需要给底部交互区域留出足够的"安全距离"
4.3 导航栏颜色与内容颜色不匹配
如果你把导航栏设为白色背景,但忘了把内容颜色改为深色,结果就是——白色的返回键在白色背景上完全看不见。这就像在雪地里穿白衣服,不是看不见,是完全看不见。
// ❌ 错误:白色背景 + 白色内容
await mainWindow.setWindowSystemBarProperties({
navigationBarColor: '#FFFFFFFF',
navigationBarContentColor: '#FFFFFF', // 白色按键在白色背景上不可见
});
// ✅ 正确:白色背景 + 深色内容
await mainWindow.setWindowSystemBarProperties({
navigationBarColor: '#FFFFFFFF',
navigationBarContentColor: '#000000', // 深色按键在白色背景上清晰可见
});
4.4 横竖屏切换时避让区域变化
横屏和竖屏下,导航栏的位置和高度可能不同。如果你的应用支持横竖屏切换,需要在每次方向变化时重新获取避让区域。
五、HarmonyOS 6适配
5.1 API变更
| 变更项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| 导航栏控制 | setSpecificStatusBar(NAVIGATION_BAR) |
新增 setNavigationBarStyle 独立接口 |
| 手势区域 | 无专用API | 新增 getGestureArea 获取手势安全区域 |
| 避让区域 | getWindowAvoidArea |
新增 getSystemBarLayout 统一获取 |
| 导航模式检测 | 无 | 新增 getNavigationMode 检测三键/手势 |
5.2 迁移指南
// HarmonyOS 5 写法
const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_BAR);
const navBarHeight = px2vp(avoidArea.bottomRect.height);
// HarmonyOS 6 写法(推荐)
const barLayout = mainWindow.getSystemBarLayout();
const navBarHeight = barLayout.navigationBarHeight; // 直接返回vp值
// 检测导航模式
const navMode = mainWindow.getNavigationMode();
if (navMode === window.NavigationMode.THREE_BUTTON) {
// 三键导航适配
} else {
// 手势导航适配
}
5.3 折叠屏与平板适配
HarmonyOS 6 对大屏设备的导航栏适配做了增强。折叠屏展开后,导航栏可能出现在侧边而非底部。需要使用 getSystemBarLayout 获取更精确的布局信息。
六、总结
mindmap
root((导航栏控制))
显隐控制
setSpecificStatusBar
STATUS_BAR / NAVIGATION_BAR
游戏模式联动隐藏
颜色控制
setWindowSystemBarProperties
navigationBarColor
navigationBarContentColor
背景与内容颜色匹配
导航模式
三键导航
固定底部栏
避让高度较大
手势导航
底部指示条
手势区域仍存在
底部安全距离
避让适配
getWindowAvoidArea
TYPE_NAVIGATION_BAR
px2vp单位转换
底部padding
横竖屏重新获取
踩坑要点
不硬编码高度
手势幽灵区域
颜色匹配
横竖屏变化
HarmonyOS 6
getNavigationMode
getSystemBarLayout
getGestureArea
折叠屏侧边导航栏
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(NAVIGATION_BAR) |
可独立控制,与状态栏解耦 |
| 导航栏颜色 | setWindowSystemBarProperties |
背景色和内容色必须匹配 |
| 避让区域 | getWindowAvoidArea(TYPE_NAVIGATION_BAR) |
必须用 px2vp 转换 |
| 三键导航 | 固定底部栏 | 避让高度大,隐藏后不可操作 |
| 手势导航 | 底部指示条 | 避让高度小,隐藏后手势仍可用 |
| 游戏模式 | 联动隐藏 + 屏幕常亮 + 横屏 | 需要暂停菜单提供退出入口 |
导航栏适配的精髓在于"动态获取,绝不硬编码"。不同设备、不同导航模式、不同屏幕方向,导航栏的形态千变万化。只有通过系统API动态获取避让区域,才能确保你的应用在所有场景下都完美适配。
- 点赞
- 收藏
- 关注作者
评论(0)