CHAPTER 04 · 10

导航与路由

掌握 Navigation 组件、页面栈管理、转场动画与深链接,构建多页面 HarmonyOS 应用的核心能力。

HarmonyOS NEXT 的导航体系

HarmonyOS NEXT 提供了两套导航方案,适合不同场景:

Navigation 组件(推荐)
ArkUI 原生导航容器,支持自适应单/双列布局(手机显示单列,平板自动变双列)、NavDestination 页面管理、转场动画、路由拦截。这是 HarmonyOS NEXT 官方推荐的主导航方案,适合绝大多数应用。
Router 模块(传统方案)
@ohos.router 提供的命令式路由 API,通过 router.pushUrl()、router.back() 控制页面栈。语法更简单,但功能不如 Navigation 丰富,不支持自适应布局。适合简单应用或旧代码迁移。

Navigation 组件

Navigation 是一个智能导航容器,它会根据设备屏幕大小自动决定显示方式:手机显示单页模式(Stack),平板/折叠屏展开后显示双栏模式(Split),开发者无需为不同设备单独适配。

基本结构

// NavPathStack 是页面栈的控制器,负责 push/pop/replace 操作
// 通常在根组件中创建,向下传递
@Entry
@Component
struct MainPage {
  // NavPathStack:页面路由栈,管理所有 NavDestination 页面
  navStack: NavPathStack = new NavPathStack()

  build() {
    // Navigation 组件包裹整个应用导航区域
    Navigation(this.navStack) {
      // 这里是"主页"内容,也是 Navigation 的首屏
      HomeContent({ navStack: this.navStack })
    }
    // 设置标题栏模式
    .title('首页')
    // 隐藏返回按钮(首页不需要)
    .hideBackButton(true)
    // 设置导航模式:Auto 自适应(手机Stack/平板Split)
    .mode(NavigationMode.Auto)
    // 注册路由表:key → 对应的 NavDestination 组件
    .navDestination(this.PageMap)
  }

  // @Builder 定义页面路由映射表
  @Builder
  PageMap(name: string, param: object) {
    if (name === 'ArticleDetail') {
      ArticleDetailPage({ param: param as ArticleParam })
    } else if (name === 'UserProfile') {
      UserProfilePage({ param: param as UserParam })
    } else if (name === 'Settings') {
      SettingsPage()
    }
  }
}

页面跳转与传参

// 定义参数类型(强烈推荐定义接口,保证类型安全)
interface ArticleParam {
  articleId: number
  title: string
  source: string
}

// 首页组件:触发页面跳转
@Component
struct HomeContent {
  navStack: NavPathStack = new NavPathStack()

  build() {
    Column() {
      Button('查看文章详情')
        .onClick(() => {
          // pushPathByName:跳转到命名路由,传递参数
          this.navStack.pushPathByName('ArticleDetail', {
            articleId: 42,
            title: 'ArkUI 深度解析',
            source: 'home'
          } as ArticleParam)
        })

      Button('个人主页')
        .onClick(() => {
          this.navStack.pushPathByName('UserProfile', { userId: 1001 })
        })

      Button('替换当前页(不留历史)')
        .onClick(() => {
          // replacePathByName:替换当前页,不产生历史记录
          this.navStack.replacePathByName('Settings', {})
        })
    }
  }
}

// 详情页:接收参数
@Component
struct ArticleDetailPage {
  param: ArticleParam = { articleId: 0, title: '', source: '' }

  // @Consume 获取 Navigation 注入的路由栈(在子页面中使用)
  pathStack: NavPathStack = new NavPathStack()

  build() {
    // NavDestination 是每个子页面的根容器
    NavDestination() {
      Column({ space: 16 }) {
        Text(this.param.title).fontSize(24).fontWeight(FontWeight.Bold)
        Text(`文章 ID:${this.param.articleId}`)

        Button('返回')
          .onClick(() => {
            this.pathStack.pop()          // 普通返回
            // this.pathStack.popToName('Home')  // 返回到指定页面
            // this.pathStack.clear()             // 清空历史
          })
      }
      .padding(24)
    }
    .title(this.param.title)
  }
}

底部 Tab 导航

大多数应用都有底部 Tab 栏。在 HarmonyOS NEXT 中,可以用 Tabs 组件或在 Navigation 内部嵌套实现:

@Entry
@Component
struct MainTabsPage {
  @State currentTab: number = 0
  private tabsController: TabsController = new TabsController()

  build() {
    Tabs({
      barPosition: BarPosition.End,   // 底部 Tab 栏
      controller: this.tabsController,
      index: this.currentTab
    }) {
      // 每个 TabContent 对应一个 Tab 页
      TabContent() {
        HomePage()
      }
      .tabBar(this.TabBarItem('首页', '🏠', 0))

      TabContent() {
        DiscoverPage()
      }
      .tabBar(this.TabBarItem('发现', '🔍', 1))

      TabContent() {
        ProfilePage()
      }
      .tabBar(this.TabBarItem('我的', '👤', 2))
    }
    .onChange((index: number) => {
      this.currentTab = index
    })
    .barMode(BarMode.Fixed)   // Tab 均分宽度
  }

  // 自定义 TabBar 图标样式
  @Builder
  TabBarItem(label: string, icon: string, index: number) {
    Column({ space: 4 }) {
      Text(icon).fontSize(22)
      Text(label)
        .fontSize(11)
        .fontColor(this.currentTab === index ? '#CF0A2C' : '#768390')
    }
    .padding({ top: 8, bottom: 8 })
  }
}

页面转场动画

HarmonyOS NEXT 提供了丰富的页面转场动画,既有系统预设,也支持完全自定义:

// NavDestination 内设置转场动画
NavDestination() {
  // 页面内容
}
.customNavContentTransition(this.customTransition)

// 自定义转场:从右侧滑入
customTransition(
  from: NavContentInfo,
  to: NavContentInfo,
  operation: NavigationOperation
): NavigationAnimatedTransition | undefined {
  if (operation === NavigationOperation.PUSH) {
    return {
      onTransitionEnd: (isSuccess: boolean) => {
        console.log('转场完成', isSuccess)
      },
      timeout: 1000,
      transition: (transitionProxy: NavigationTransitionProxy) => {
        // 使用 animateTo 控制动画
        animateTo({
          duration: 350,
          curve: Curve.EaseOut
        }, () => {
          transitionProxy.finishTransition()
        })
      }
    }
  }
  return undefined  // 使用默认转场
}

// 组件内动画(进入/退出)
Column()
  .transition(
    TransitionEffect.OPACITY.combine(
      TransitionEffect.translate({ x: 100 })
    )
    .animation({ duration: 300, curve: Curve.EaseOut })
  )

深链接与 Want

深链接(Deep Link)允许外部应用或网页直接跳转到应用内的特定页面。在 HarmonyOS NEXT 中,通过配置 Want 的 URI 和 Action 实现:

// module.json5 中配置深链接
// "skills" 字段声明 Ability 响应的 URI
{
  "skills": [{
    "entities": ["entity.system.home"],
    "actions": ["ohos.want.action.viewData"],
    "uris": [{
      "scheme": "myapp",
      "host": "article",
      "pathStartWith": "/"
    }]
  }]
}

// EntryAbility.ts — 处理深链接跳转
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
  this.handleWant(want)
}

// 应用已在前台时收到新 Want
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam) {
  this.handleWant(want)
}

handleWant(want: Want) {
  const uri = want.uri  // e.g. "myapp://article/123"
  if (uri) {
    const url = new URL(uri)
    const articleId = url.pathname.replace('/', '')

    // 通过 AppStorage 传递跳转目标,让页面组件监听并跳转
    AppStorage.setOrCreate('deepLinkTarget', {
      page: 'ArticleDetail',
      params: { articleId: Number(articleId) }
    })
  }
}

路由拦截与守卫

类似 Vue Router 的 beforeEach 守卫,Navigation 支持在页面跳转前执行拦截逻辑,常用于登录态检验:

Navigation(this.navStack) {
  HomeContent()
}
.navDestination(this.PageMap)
// 路由拦截:跳转前执行检查
.onNavBarStateChange((isVisible: boolean) => {
  console.log('导航栏可见性变化:', isVisible)
})
// 监听路由栈变化
.onNavigationModeChange((mode: NavigationMode) => {
  console.log('导航模式变化(单/双栏切换):', mode)
})

// 在跳转时手动拦截
function navigateWithAuth(
  navStack: NavPathStack,
  pageName: string,
  params: object
) {
  const isLoggedIn = AppStorage.get<boolean>('isLoggedIn')
  const needsAuth = ['UserProfile', 'Orders', 'Checkout']

  if (needsAuth.includes(pageName) && !isLoggedIn) {
    // 未登录跳转到登录页,带上原目标作为重定向参数
    navStack.pushPathByName('Login', {
      redirect: pageName,
      redirectParams: params
    })
    return
  }
  navStack.pushPathByName(pageName, params)
}
Navigation vs Router 如何选择 新项目统一使用 Navigation + NavDestination 方案。它支持多设备自适应、转场动画定制、路由拦截,且与华为未来的多窗口特性兼容。Router 模块虽然简单,但不支持自适应布局,在折叠屏和平板上体验差。老项目可以逐步将 Router 迁移到 Navigation。

页面间数据回传

从详情页返回时,有时需要把结果传回给调用方(类似 Android 的 startActivityForResult)。HarmonyOS NEXT 通过 NavPathStack.pop() 的返回值机制实现:

// 详情页:带返回值弹出
@Component
struct EditProfilePage {
  pathStack: NavPathStack = new NavPathStack()
  @State newName: string = ''

  build() {
    NavDestination() {
      Column({ space: 16 }) {
        TextInput({ placeholder: '输入新名称' })
          .onChange((val: string) => { this.newName = val })
        Button('保存并返回')
          .onClick(() => {
            // pop 时携带返回值,第二个参数是回调
            // 调用方通过 pushPathByName 的 onPop 回调接收
            this.pathStack.pop({ savedName: this.newName })
          })
      }
    }
  }
}

// 主页:推入时注册 onPop 回调
@Component
struct ProfilePage {
  navStack: NavPathStack = new NavPathStack()
  @State userName: string = '未设置'

  build() {
    Column() {
      Text(`当前名称:${this.userName}`)
      Button('编辑')
        .onClick(() => {
          // pushPathByName 第三个参数:子页面 pop 时的回调
          this.navStack.pushPathByName('EditProfile', {},
            (info: PopInfo) => {
              const result = info.result as { savedName: string }
              if (result?.savedName) {
                this.userName = result.savedName
              }
            }
          )
        })
    }
  }
}
NavPathStack 作用域陷阱 NavPathStack 实例必须在整个 Navigation 组件树中共享同一个引用,不能在每个子组件中 new NavPathStack()。常见错误是在子组件中直接 navStack: NavPathStack = new NavPathStack(),这会创建一个新的空栈,调用 push/pop 不会对父 Navigation 产生任何效果。应通过属性传递或 @Provide/@Consume 在组件树中共享同一个栈实例。

多 HAP 跨模块导航

大型应用通常拆分为多个 HAP 模块(entry HAP + feature HAP)。跨模块的页面导航需要额外配置路由表,因为 PageMap 里的 @Builder 函数无法直接引用另一个 HAP 的组件:

路由表(Router Map)
在 resources/base/profile/router_map.json 中配置页面名称与源码路径的映射。Navigation 组件通过 navigationId 绑定路由表,跨模块页面按路由名懒加载,不需要在 PageMap 里逐一 if/else 判断。这是大型项目的推荐方案(API 12+)。
HSP(HarmonyOS Shared Package)
Harmony Shared Package,可被多个 HAP 共享的模块。导航目标组件如果在 HSP 中,需要将该 HSP 声明为 feature HAP 的依赖,并通过路由表注册组件路径。
// resources/base/profile/router_map.json
{
  "routerMap": [
    {
      "name": "ArticleDetail",          // 路由名称(全局唯一)
      "pageSourceFile": "src/main/ets/pages/ArticleDetailPage.ets",
      "buildFunction": "ArticleDetailBuilder",  // @Builder 函数名
      "data": {
        "description": "文章详情页"
      }
    },
    {
      "name": "UserProfile",
      "pageSourceFile": "src/main/ets/pages/UserProfilePage.ets",
      "buildFunction": "UserProfileBuilder"
    }
  ]
}

// Navigation 绑定路由表
// Navigation 组件设置 navigationId,框架自动查找对应的 router_map.json
Navigation(this.navStack) {
  HomeContent()
}
.navDestination(this.PageMap)   // 仍可保留简单页面的 Builder

模态弹出(Sheet / Dialog)

除了页面级导航,HarmonyOS NEXT 还提供了半模态(Sheet)和自定义弹窗(CustomDialog)用于不完全切换页面的场景:

// 半模态弹出(底部抽屉)
@Entry
@Component
struct MainPage {
  @State showSheet: boolean = false

  build() {
    Column() {
      Button('查看评论')
        .onClick(() => { this.showSheet = true })
    }
    // .bindSheet:绑定半模态弹出
    .bindSheet($$this.showSheet, this.SheetContent(), {
      detents: [SheetSize.MEDIUM, SheetSize.LARGE],   // 可拖拽高度档位
      showClose: true,
      title: { title: '评论区' }
    })
  }

  // Sheet 内容(独立的 @Builder 函数)
  @Builder
  SheetContent() {
    ScrollView() {
      Column({ space: 12 }) {
        // 评论列表...
      }
    }
    .height('100%')
  }
}

// 自定义弹窗(CustomDialog)
@CustomDialog
struct ConfirmDialog {
  controller: CustomDialogController
  confirm: () => void = () => {}
  cancel: () => void = () => {}
  message: string = ''

  build() {
    Column({ space: 16 }) {
      Text(this.message).fontSize(16)
      Row({ space: 12 }) {
        Button('取消').onClick(() => {
          this.controller.close()
          this.cancel()
        })
        Button('确认').onClick(() => {
          this.controller.close()
          this.confirm()
        })
      }
    }
    .padding(24)
  }
}

// 使用自定义弹窗
@Entry
@Component
struct PageWithDialog {
  dialogController: CustomDialogController = new CustomDialogController({
    builder: ConfirmDialog({
      message: '确认删除此文章吗?',
      confirm: () => { /* 执行删除 */ },
      cancel: () => { /* 取消操作 */ }
    }),
    alignment: DialogAlignment.Center,
    maskColor: 'rgba(0,0,0,0.5)'
  })

  build() {
    Button('删除')
      .onClick(() => this.dialogController.open())
  }
}
本章小结 HarmonyOS NEXT 的导航体系以 Navigation + NavDestination 为核心,支持自适应单/双栏布局。关键要点:NavPathStack 是路由栈的唯一控制器,必须在组件树中共享同一实例;路由表(router_map.json)适合大型多模块项目;页面间数据回传通过 pushPathByName 的 onPop 回调实现;半模态(Sheet)和 CustomDialog 用于轻量级弹出场景。掌握这套体系,可以构建任意复杂度的多页面鸿蒙应用。