HarmonyOS Next:使用 Navigation Component 构建持久化标签页

发布: (2026年1月16日 GMT+8 17:35)
10 min read
原文: Dev.to

Source: Dev.to

标签概览

介绍

如果您正在使用 HarmonyOS NextArkUI/ArkTS 构建应用程序,您会知道流畅的用户体验有多重要。开发者常遇到的一个常见挑战是,即使用户导航到不同的屏幕,仍然保持 tab bar visible

在本文中我们将:

  1. 解释在使用路由器时标签页为何会消失。
  2. 展示一种结合 TabsNavigation 组件的稳健解决方案。

问题:导航时标签页消失

在 ArkUI 中,我们经常使用 Tabs 组件来创建诸如 HomeProfileSettings 等板块。标签栏位于屏幕底部,供用户在主视图之间切换。

仅在切换标签时一切正常。问题出现在当你在标签内部点击按钮,使用 ArkTS 的 router 跳转到新页面时。

Tabs disappearing on navigation

为什么会出现这种情况?
Tabs 组件通常放在单个页面上。当你使用 router 跳转到另一页面时,会离开承载标签栏的原页面,导致标签栏消失。用户随之失去关键的导航元素,可能会感到困惑。

我们希望 Tabs 能够 无论用户在应用的某个章节深入到何种层级,都保持可见

解决方案:将 Tabs 与 Navigation 结合

HarmonyOS Next 提供了一种更好的方法:将 Tabs 组件 一起Navigation 组件使用。此组合可以让您:

  • 将标签栏固定在底部。
  • 为每个标签页提供独立的导航栈。

因此,您可以在标签页 内部 导航到详细页面,同时标签栏仍然显示在屏幕上。

为什么选择 Navigation 而不是 Router?

在深入代码之前,让我们先阐明为何在此场景下更倾向使用 Navigation

“建议您在应用中使用 Component Navigation (Navigation),它提供了更强大的功能和自定义能力,作为路由框架。” – 官方 HarmonyOS 文档

  • Router – 在 不同页面 之间切换。
  • Navigation – 在 单个页面内部 处理 组件级路由

由于持久化标签页模式需要保持在同一页面(即包含标签页的页面)上,Navigation 是合适的工具。它仅更改标签页的内容区域,保持标签栏不变,并且在复杂 UI 流程中提供更细粒度的控制。

构建持久化标签页 UI:逐步指南

步骤 1 – 设置主标签页结构

创建承载 Tabs 组件的入口组件。这将是应用的根组件。

// Index.ets (Main Tabs Component)
@Entry
@Component
export struct Index {
  build() {
    Tabs({ barPosition: BarPosition.End }) {   // Tabs at the bottom
      // Tab A – will use Navigation for its content
      TabContent() {
        NavigationExample()                     // Custom component for Tab A
      }.tabBar('A')                               // Tab label

      // Tab B – simple placeholder
      TabContent() {
        Column()
          .width('100%')
          .height('100%')
          .backgroundColor('#007DFF')
      }.tabBar('B')

      // Tab C – simple placeholder
      TabContent() {
        Column()
          .width('100%')
          .height('100%')
          .backgroundColor('#FFBF00')
      }.tabBar('C')

      // Tab D – simple placeholder
      TabContent() {
        Column()
          .width('100%')
          .height('100%')
          .backgroundColor('#E67C92')
      }.tabBar('D')
    }.barBackgroundColor('#ffb9fdf4')   // Tab bar background color
  }
}

关键点

  • @Entry 标记此组件为应用的入口点。
  • Tabs({ barPosition: BarPosition.End }) 创建位于底部的标签栏。
  • 标签页 A 包含自定义的 NavigationExample 组件,该组件将管理自己的导航栈。
  • 标签页 B‑D 为演示用的简单彩色列。

步骤 2 – 在标签页内部实现导航

现在创建将放置在 标签页 A 中的组件。它将拥有自己的导航历史。

// NavigationExample.ets (Content for Tab A)
@Component
struct NavigationExample {
  // Dummy data for a simple list
  private arr: number[] = [1, 2, 3];

  // Provide a NavPathStack so child pages can access navigation state
  @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack();

  // -------------------------------------------------
  // Step 2.1 – Define the Navigation container
  // -------------------------------------------------
  build() {
    Navigation({
      // Optional: customize transition animations, etc.
    }) {
      // Page 1 – List view
      Page() {
        Column() {
          ForEach(this.arr, (item) => {
            Button(`Item ${item}`)
              .onClick(() => {
                // Push a new page onto the navigation stack
                this.pageInfos.push({
                  name: `DetailPage${item}`,
                  params: { id: item }
                });
              });
          })
        }
      }

      // Page 2 – Detail view (generated dynamically)
      Page({ name: (info) => info.name }) {
        Column() {
          Text(`Detail for item ${this.pageInfos.current?.params?.id}`)
          Button('Back')
            .onClick(() => this.pageInfos.pop());
        }
      }
    }
  }
}

说明

  • @Provide('pageInfos') 创建一个 NavPathStack 实例,用于存储该标签页的导航历史。
  • Navigation 组件定义了 基于栈 的导航流程:
    • 页面 1 显示项目列表。点击项目会将 详情页面 推入栈中。
    • 页面 2 显示所选项目的详情,并提供一个 返回 按钮来弹出栈。

由于导航发生在 NavigationExample 组件 内部,外部的 Tabs 组件不会被隐藏。

回顾

✅ 我们实现的目标📌 我们是如何做到的
持久的底部标签栏在应用根目录 (Index.ets) 放置 Tabs
每个标签页的独立导航在标签页内部使用 Navigation (NavigationExample.ets)
在不丢失标签页的情况下实现无缝深层导航使用 NavPathStack@Provide 管理导航状态

Source:

导航目标

// This @Builder function tells Navigation what content to show for each path.
@Builder
pageMap(name: string) {
  NavDestination() {
    if (name === '1') {
      Text("NavDestinationContent " + name)
    } else if (name === '2') { // Example detail page for '2'
      Text("Detail Content for Item " + name)
    } else if (name === '3') { // Example detail page for '3'
      Text("Another Detail Content for Item " + name)
    } else {
      // Fallback for other items, showing their number
      Text("NavDestination Content for Item " + name)
    }
  }
  .title("NavDestinationTitle " + name) // Title for the navigation bar
  .onShown(() => {
    console.info("show NavDestinationTitle " + name)
  })
  .onHidden(() => {
    console.info("hide NavDestinationTitle " + name)
  })
}

// Step 2.2: Building the Navigation Container and its Initial Content
build() {
  Column() {
    // The Navigation component itself
    Navigation(this.pageInfos) { // It uses our NavPathStack for managing history
      List({ space: 12 }) { // Initial content: A list of items
        ForEach(this.arr, (item: number) => {
          ListItem() {
            Button("Go to Child " + item)
              .width('100%')
              .onClick(() => {
                // When button is clicked, push a new path onto the Navigation stack
                this.pageInfos.pushPathByName(item.toString(), '')
              })
          }
        })
      }
    }
    .navDestination(this.pageMap) // Link Navigation to our pageMap for destination definitions
  }
  .height('100%')
  .width('100%')
}

分解 NavigationExample 的关键部分

  • @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack()
    这行代码至关重要。NavPathStackNavigation 用来在此标签页中管理屏幕历史的对象。@Provide 确保 Navigation 能正确设置。

  • pageMap([@builder](https://dev.to/builder))
    可以把它看作内容映射。它告诉 Navigation 对每个特定的 “路径”(例如 '2''3')应显示哪个 UI(NavDestination)。

  • Navigation(this.pageInfos) { … }
    这就是你的 Navigation 容器。其初始内容(大括号内的部分)是用户在此标签页首次看到的内容。

  • Button().onClick(…)
    点击按钮会调用 this.pageInfos.pushPathByName(),向 Navigation 的堆栈中添加一个新的 “屏幕”(来自 pageMap)。这样可以在不离开主标签页的情况下显示新内容。

输出

son.gif

结果:无缝的用户体验

使用此设置时,当用户位于 Tab A 时,会看到一个列表。如果他们点击 “前往 Child 1”, 则会在屏幕顶部看到 Child 1 的详细内容,但 标签栏 (A, B, C, D) 仍会显示在底部。他们可以轻松在 Tab A 的历史记录中返回,或切换到 Tab B、C 或 D,而不会失去上下文。

本方法的优势

  • 始终可见的标签页 – 您的应用主标签页始终显示在屏幕上,提供一致的导航体验。
  • 独立导航 – 每个标签页可以拥有各自独立的历史栈,使复杂的应用更易于管理和理解。
  • 更好的用户流程 – 用户可以在一个标签页内深入浏览内容,然后切换到另一个主区域而不会感到迷失。
  • 代码整洁 – 通过为每个标签页分离导航逻辑,代码库变得更有条理且易于维护。

Conclusion

通过智能地组合 HarmonyOS Next 的 TabsNavigation 组件,您可以克服常见的标签栏消失问题。这种方法使得构建 UI 元素保持固定的应用 轻松且强大,大幅提升整体用户体验。

参考文献

作者:Muhammet Ali Ilgaz

Back to Blog

相关文章

阅读更多 »