玩转HarmonyOS APP导航栏

举报
Jack20 发表于 2026/06/20 16:09:11 2026/06/20
【摘要】 玩转HarmonyOS APP导航栏📌 核心要点:通过 @ohos.window 模块控制系统导航栏的显隐、颜色与行为,适配三键导航和手势导航两种模式,确保应用底部布局完美避让 一、背景与动机你有没有遇到过这种情况?——做了一个精美的底部Tab栏,结果系统导航栏硬生生挤在下面,你的Tab栏和导航栏叠在一起,活像两排牙齿挤在一张嘴里。或者更惨的,你做了一个全屏游戏,底部的手势指示条总是在那...

玩转HarmonyOS APP导航栏

📌 核心要点:通过 @ohos.window 模块控制系统导航栏的显隐、颜色与行为,适配三键导航和手势导航两种模式,确保应用底部布局完美避让


一、背景与动机

你有没有遇到过这种情况?——做了一个精美的底部Tab栏,结果系统导航栏硬生生挤在下面,你的Tab栏和导航栏叠在一起,活像两排牙齿挤在一张嘴里。或者更惨的,你做了一个全屏游戏,底部的手势指示条总是在那里晃来晃去,玩家一个误触就退出了游戏。

导航栏就像一个"不请自来的客人"——它确实有用,帮你返回、回到桌面、切换应用,但它总是占据底部最宝贵的空间,而且你还没法完全忽视它。

HarmonyOS 提供了两种导航模式:三键导航(返回键、主页键、最近任务键)和手势导航(从边缘滑动操作)。这两种模式下的导航栏形态完全不同,适配策略也大相径庭。今天我们就来彻底搞懂它。


二、核心原理

2.1 导航栏控制架构

导航栏和状态栏同属系统栏(SystemBar),它们共享同一套控制机制,但可以独立操作。
图片.png

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 手势导航的"幽灵区域"

手势导航模式下,即使你隐藏了导航栏指示条,底部的手势操作区域仍然存在。用户从底部上滑仍然可以返回或回到桌面。这意味着:

  1. 你不能在底部边缘放置需要频繁点击的按钮
  2. 游戏场景中,底部边缘的触摸可能被系统拦截
  3. 需要给底部交互区域留出足够的"安全距离"

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动态获取避让区域,才能确保你的应用在所有场景下都完美适配。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。