鸿蒙原生开发手记:徒步迹 - 页面路由与Navigation导航

掌握鸿蒙 Navigation 路由系统,实现页面间流畅跳转


一、前言

页面路由是 App 的基础骨架,决定了用户如何在不同页面间导航。HarmonyOS 提供了两套路由方案:

  1. Router API:轻量级路由,适合简单跳转
  2. Navigation 组件:功能强大的声明式导航,支持路由栈管理、深层链接等

本文将从基础到进阶,全面覆盖“徒步迹“App 中的页面路由实现。


二、Router API 基础

Router 是系统提供的轻量路由 API,适合页面数不多、路由结构简单的场景。

2.1 引入模块

import { router } from '@kit.ArkUI';

2.2 基本跳转

// 1. pushUrl - 推入新页面(可返回)
router.pushUrl({
  url: 'pages/RouteDetailPage',
  params: {          // 传递参数
    routeId: 123,
    routeName: '香山徒步',
  },
});

// 2. replaceUrl - 替换当前页面(不可返回)
router.replaceUrl({
  url: 'pages/LoginPage',
});

// 3. 返回上一页
router.back();

// 4. 返回指定页面
router.back({
  url: 'pages/HomePage',
});

2.3 接收参数

// 在目标页面中接收参数
@Entry
@Component
struct RouteDetailPage {
  @State routeId: number = 0;
  @State routeName: string = '';

  aboutToAppear(): void {
    // 通过 router.getParams() 获取参数
    const params = router.getParams() as Record<string, Object>;
    if (params) {
      this.routeId = params['routeId'] as number;
      this.routeName = params['routeName'] as string;
    }
  }
}

三、Navigation 组件

Navigation 是鸿蒙 NEXT 推荐的声明式导航方案,功能更强大:

3.1 基本结构

Navigation() {
  // 导航内容区域
  Column() {
    Text('首页内容')
  }
}
.title('徒步迹')
.mode(NavigationMode.Auto);  // 自动适配单栏/双栏模式

3.2 NavPathStack 路由栈

NavPathStack 是 Navigation 的核心路由管理工具:

@Entry
@Component
struct MainPage {
  // 创建路由栈
  private navStack: NavPathStack = new NavPathStack();

  build() {
    Navigation(this.navStack) {
      Column() {
        Button('查看路线详情')
          .onClick(() => {
            // 推入新页面
            this.navStack.pushPath({
              name: 'RouteDetailPage',  // 路由名称
              param: {                   // 参数
                routeId: 1,
                routeName: '香山徒步',
              },
            });
          });

        Button('记录轨迹')
          .onClick(() => {
            this.navStack.pushPathByName('TrackingPage', null);
          });
      }
    }
    .title('徒步迹')
    .navDestination(this.PageMap);  // 注册页面映射
  }

  // 页面映射
  @Builder
  PageMap(name: string, param: Object) {
    if (name === 'RouteDetailPage') {
      RouteDetailPage({ routeId: param['routeId'] as number });
    } else if (name === 'TrackingPage') {
      TrackingPage();
    }
  }
}

3.3 路由栈操作

// 1. 推入页面
navStack.pushPath({ name: 'ProfilePage', param: { userId: 1 } });
navStack.pushPathByName('SettingsPage', null);

// 2. 替换当前页面
navStack.replacePath({ name: 'LoginPage' });
navStack.replacePathByName('RegisterPage', { phone: '13800138000' });

// 3. 返回上一页
navStack.pop();                 // 返回上一页
navStack.popToName('HomePage'); // 返回到指定页面
navStack.popToIndex(0);         // 返回到根页面

// 4. 清除所有路由
navStack.clear();

// 5. 获取路由栈信息
let pathInfo = navStack.getPathByName('HomePage');
let index = navStack.getIndexByName('HomePage');
let size = navStack.size();  // 路由栈大小

四、页面配置

所有需要路由跳转的页面必须在 module.json5 中配置:

// module.json5 的 pages 字段
{
  "module": {
    "pages": "$profile:main_pages",
    // ...
  }
}
// resources/base/profile/main_pages.json
{
  "src": [
    "pages/SplashPage",
    "pages/LoginPage",
    "pages/RegisterPage",
    "pages/HomePage",
    "pages/RouteListPage",
    "pages/RouteDetailPage",
    "pages/TrackingPage",
    "pages/TrackHistoryPage",
    "pages/HealthDataPage",
    "pages/TeamListPage",
    "pages/TeamDetailPage",
    "pages/PhotoGalleryPage",
    "pages/RoadbookListPage",
    "pages/RoadbookDetailPage",
    "pages/ProfilePage",
    "pages/SettingsPage",
    "pages/AboutPage"
  ]
}

五、实战:徒步迹导航结构

5.1 整体路由设计

SplashPage (启动页)
   └── LoginPage (登录)
        └── HomePage (首页)
             ├── RouteListPage → RouteDetailPage
             ├── TrackingPage
             ├── TeamListPage → TeamDetailPage
             ├── PhotoGalleryPage
             ├── RoadbookListPage → RoadbookDetailPage
             ├── HealthDataPage
             ├── TrackHistoryPage
             ├── ProfilePage
             │    ├── SettingsPage
             │    └── AboutPage
             └── (通过底部Tab导航)

5.2 首页 Tab 切换实现

// HomePage.ets - 带Tab导航的首页
@Entry
@Component
struct HomePage {
  @State currentTab: number = 0;
  private tabs: string[] = ['首页', '路线', '记录', '团队', '我的'];

  build() {
    Column() {
      // 内容区域
      if (this.currentTab === 0) {
        this.HomeContent();
      }

      // 底部 TabBar
      Row() {
        ForEach(this.tabs, (tab: string, index: number) => {
          Column() {
            Text(tab)
              .fontSize(12)
              .fontColor(
                this.currentTab === index
                  ? $r('app.color.primary_color')
                  : $r('app.color.text_secondary')
              );
          }
          .layoutWeight(1)
          .padding(8)
          .onClick(() => {
            this.currentTab = index;
            // 切换到其他Tab时跳转到对应页面
            if (index === 1) router.pushUrl({ url: 'pages/RouteListPage' });
            else if (index === 2) router.pushUrl({ url: 'pages/TrackingPage' });
            else if (index === 3) router.pushUrl({ url: 'pages/TeamListPage' });
            else if (index === 4) router.pushUrl({ url: 'pages/ProfilePage' });
          });
        }, (tab: string) => tab);
      }
      .width('100%')
      .height(56)
      .backgroundColor(Color.White);
    }
    .width('100%')
    .height('100%');
  }

  @Builder
  HomeContent() {
    Scroll() {
      // 首页内容...
    }
  }
}

5.3 页面间参数传递最佳实践

// 从路线列表页跳转到详情页
// RouteListPage.ets
router.pushUrl({
  url: 'pages/RouteDetailPage',
  params: {
    routeId: 1,
    source: 'RouteListPage',
  },
});

// RouteDetailPage.ets - 接收参数
aboutToAppear(): void {
  const params = router.getParams() as Record<string, Object>;
  if (params) {
    this.routeId = params['routeId'] as number;
    this.source = params['source'] as string;
  }
}

六、路由动画

ArkUI 支持页面切换动画的自定义:

// 自定义转场动画
router.pushUrl({
  url: 'pages/RouteDetailPage',
  params: { routeId: 123 },
}, router.RouterMode.Single, (err) => {
  if (!err) {
    // 跳转成功
  }
});

// 页面内使用 animateTo 控制动画
animateTo({ duration: 300, curve: Curve.Friction }, () => {
  // 状态变化触发 UI 更新
  this.isVisible = true;
});

七、常见问题

7.1 页面跳转无反应

检查 main_pages.json 中是否注册了该页面路径。

7.2 参数传递丢失

确保参数类型为 Record<string, Object>,复杂对象建议先序列化。

// 传递复杂对象前先序列化
const serialized = JSON.stringify(complexObject);
router.pushUrl({
  url: 'pages/RouteDetailPage',
  params: { routeData: serialized },
});

// 接收端反序列化
const data = JSON.parse(params['routeData'] as string);

7.3 跨模块路由

// 跨 HAP 模块路由
router.pushUrl({
  url: '@bundle:com.hiking.feature/ets/pages/FeaturePage',
});

八、总结

本文全面介绍了鸿蒙系统的页面路由方案,从简单的 Router API 到强大的 Navigation 组件,再到“徒步迹“的完整导航结构设计。良好的路由设计是 App 架构的基础。

下一篇文章将学习资源管理与多主题适配,让“徒步迹“支持多种视觉风格。


下一篇预告:鸿蒙原生开发手记:徒步迹 - 资源管理与多主题适配

如有疑问欢迎留言交流。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐