引言:导航路由是应用的 “骨架脉络”

在开源鸿蒙(OpenHarmony)Flutter 应用中,导航路由负责页面之间的跳转、参数传递和状态管理,是连接各个功能模块的核心。随着应用复杂度提升,简单的Navigator.push已无法满足需求 —— 多栈管理、分布式流转、路由拦截等场景需要更系统化的解决方案。

本文将以 “场景化实战” 为核心,从基础的原生路由、命名路由,到进阶的路由拦截、参数传递,再到开源鸿蒙特有的分布式 UI 流转,用 “原理 + 精简代码 + 避坑指南” 的方式,带你打造适配全场景的导航路由体系。

一、基础导航:原生路由与命名路由

1.1 原生路由(Navigator.push)

原生路由通过Navigator.pushNavigator.pop实现页面跳转,适合简单场景。

实战:两页面跳转

dart

// 首页:跳转到详情页
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // 跳转到详情页,接收返回值
            final result = await Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const DetailPage(),
              ),
            );
            // 处理详情页返回的结果
            if (result != null) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('详情页返回:$result')),
              );
            }
          },
          child: const Text('前往详情页'),
        ),
      ),
    );
  }
}

// 详情页:返回数据给首页
class DetailPage extends StatelessWidget {
  const DetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('详情页')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 返回首页,并携带数据
            Navigator.pop(context, '我是详情页返回的数据');
          },
          child: const Text('返回首页'),
        ),
      ),
    );
  }
}

关键知识点

  • Navigator.push返回Future,可通过await接收下一页返回的数据;
  • Navigator.pop(context, result)携带返回数据,上一页通过result获取。

1.2 命名路由(推荐)

命名路由通过给页面分配唯一 “路由名” 实现跳转,适合中大型应用,便于管理和维护。

步骤 1:定义路由表

dart

// routes.dart
final Map<String, WidgetBuilder> routes = {
  '/home': (context) => const HomePage(),
  '/detail': (context) => const DetailPage(),
  '/login': (context) => const LoginPage(),
};
步骤 2:在 MaterialApp 中配置

dart

// main.dart
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '命名路由Demo',
      initialRoute: '/login', // 初始路由(启动页)
      routes: routes, // 配置路由表
    );
  }
}
步骤 3:使用命名路由跳转

dart

// 登录页跳转到首页
ElevatedButton(
  onPressed: () {
    Navigator.pushReplacementNamed(context, '/home'); // 替换当前路由(不保留登录页)
  },
  child: const Text('登录'),
),

// 首页跳转到详情页
ElevatedButton(
  onPressed: () async {
    final result = await Navigator.pushNamed(context, '/detail');
    // 处理返回结果
  },
  child: const Text('前往详情页'),
),

// 详情页返回首页
ElevatedButton(
  onPressed: () {
    Navigator.pop(context, '返回数据');
  },
  child: const Text('返回'),
),

常用命名路由方法

  • Navigator.pushNamed:跳转到新页面,保留当前页面;
  • Navigator.pushReplacementNamed:跳转到新页面,替换当前页面;
  • Navigator.popAndPushNamed:弹出当前页面,跳转到新页面;
  • Navigator.pushNamedAndRemoveUntil:跳转到新页面,移除之前所有页面(如登录后跳首页):

dart

// 登录成功后跳首页,移除所有之前的路由
Navigator.pushNamedAndRemoveUntil(
  context,
  '/home',
  (route) => false, // 移除所有路由
);

二、进阶导航:参数传递与路由拦截

2.1 页面参数传递

方式 1:通过arguments传递

dart

// 首页跳详情页,传递参数
Navigator.pushNamed(
  context,
  '/detail',
  arguments: {'id': 123, 'title': '商品详情'}, // 传递任意类型参数
);

// 详情页接收参数
class DetailPage extends StatelessWidget {
  const DetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    // 获取传递的参数
    final Map<String, dynamic> args = 
        ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>;
    final int id = args['id'];
    final String title = args['title'];

    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(child: Text('商品ID:$id')),
    );
  }
}
方式 2:通过路由表动态传参(推荐)

dart

// 定义带参数的路由
final Map<String, WidgetBuilder> routes = {
  '/detail': (context) {
    final args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>;
    return DetailPage(id: args['id'], title: args['title']);
  },
};

// 详情页定义构造函数接收参数
class DetailPage extends StatelessWidget {
  final int id;
  final String title;

  const DetailPage({
    super.key,
    required this.id,
    required this.title,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(child: Text('商品ID:$id')),
    );
  }
}

2.2 路由拦截(登录状态判断)

在跳转需要登录的页面时,拦截未登录用户并跳转到登录页。

步骤 1:定义路由拦截器

dart

// main.dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '路由拦截Demo',
      initialRoute: '/home',
      // 路由生成器:实现拦截逻辑
      onGenerateRoute: (settings) {
        // 需要登录的路由列表
        const needLoginRoutes = ['/cart', '/user'];
        final routeName = settings.name;

        // 检查是否需要登录且未登录
        if (needLoginRoutes.contains(routeName) && !SpUtil.getBool('isLogin')) {
          // 跳转到登录页,并记录目标路由
          return MaterialPageRoute(
            builder: (context) => LoginPage(targetRoute: routeName),
          );
        }

        // 正常路由跳转
        return MaterialPageRoute(
          builder: (context) => routes[routeName!]!(context),
        );
      },
    );
  }
}
步骤 2:登录页跳转目标路由

dart

class LoginPage extends StatelessWidget {
  final String? targetRoute;

  const LoginPage({super.key, this.targetRoute});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('登录')),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // 模拟登录成功
            await SpUtil.setBool('isLogin', true);
            if (targetRoute != null) {
              // 跳转到之前拦截的目标路由
              Navigator.pushReplacementNamed(context, targetRoute!);
            } else {
              // 跳转到首页
              Navigator.pushReplacementNamed(context, '/home');
            }
          },
          child: const Text('登录'),
        ),
      ),
    );
  }
}

三、高阶导航:GetX 路由(无 Context + 分布式适配)

原生路由依赖context,代码冗余,GetX 路由解决了这些问题,同时支持分布式场景下的路由管理。

3.1 GetX 路由基础用法

dart

// 1. 配置GetX路由
void main() {
  runApp(GetMaterialApp(
    title: 'GetX路由Demo',
    initialRoute: '/login',
    getPages: [
      GetPage(name: '/login', page: () => const LoginPage()),
      GetPage(name: '/home', page: () => const HomePage()),
      GetPage(name: '/detail/:id', page: () => const DetailPage()), // 动态路由
    ],
  ));
}

// 2. 跳转路由(无需context)
// 登录页跳首页
ElevatedButton(
  onPressed: () {
    Get.offAllNamed('/home'); // 清除所有路由跳首页
  },
  child: const Text('登录'),
),

// 首页跳详情页(传递参数)
ElevatedButton(
  onPressed: () {
    // 方式1:通过路径传递参数(动态路由)
    Get.toNamed('/detail/123');
    // 方式2:通过arguments传递
    // Get.toNamed('/detail', arguments: {'id': 123});
  },
  child: const Text('前往详情页'),
),

// 3. 详情页接收参数
class DetailPage extends StatelessWidget {
  const DetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    // 方式1:获取动态路由参数
    final String id = Get.parameters['id']!;
    // 方式2:获取arguments参数
    // final Map<String, dynamic> args = Get.arguments;

    return Scaffold(
      appBar: AppBar(title: const Text('详情页')),
      body: Center(child: Text('商品ID:$id')),
    );
  }
}

3.2 GetX 路由拦截(中间件)

通过middleware实现路由拦截,比原生路由更灵活:

dart

// 定义登录中间件
class LoginMiddleware extends GetMiddleware {
  @override
  RouteSettings? redirect(String? route) {
    // 未登录时拦截,跳登录页
    if (!SpUtil.getBool('isLogin')) {
      return RouteSettings(name: '/login', arguments: {'targetRoute': route});
    }
    return null;
  }
}

// 配置路由时添加中间件
getPages: [
  GetPage(name: '/login', page: () => const LoginPage()),
  GetPage(name: '/home', page: () => const HomePage()),
  // 需要登录的路由添加中间件
  GetPage(
    name: '/cart',
    page: () => const CartPage(),
    middlewares: [LoginMiddleware()],
  ),
],

3.3 结合开源鸿蒙分布式流转

利用 GetX 路由的无 Context 特性,轻松实现分布式 UI 流转后的路由恢复:

dart

// 流转前保存当前路由
void beforeTransfer() {
  final currentRoute = Get.currentRoute;
  final arguments = Get.arguments;
  // 保存到分布式存储
  DistributedStoreUtil.putString(
    'last_route',
    jsonEncode({'route': currentRoute, 'arguments': arguments}),
  );
}

// 流转后恢复路由
void afterTransfer() async {
  final routeStr = await DistributedStoreUtil.getString('last_route');
  if (routeStr.isNotEmpty) {
    final routeData = jsonDecode(routeStr);
    Get.offAllNamed(routeData['route'], arguments: routeData['arguments']);
  }
}

四、开源鸿蒙特有:分布式 UI 流转

分布式 UI 流转是开源鸿蒙的核心特性,允许应用从一个设备流转到另一个设备,路由系统需要适配这种场景。

4.1 流转基础流程

  1. 发起流转:在源设备调用流转 API,指定目标设备;
  2. 状态保存:流转前保存应用当前路由和状态;
  3. 目标设备启动:目标设备启动应用;
  4. 状态恢复:目标设备恢复之前保存的路由和状态。

4.2 实战:分布式路由流转

步骤 1:源设备发起流转并保存状态

dart

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('首页'),
        actions: [
          IconButton(
            icon: const Icon(Icons.device_hub),
            onPressed: () async {
              // 1. 保存当前路由和状态
              await beforeTransfer();
              // 2. 发起分布式流转(调用鸿蒙原生API)
              await DistributedTransferUtil.transferToDevice('target_device_id');
            },
          ),
        ],
      ),
    );
  }
}
步骤 2:目标设备恢复路由和状态

dart

// main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SpUtil.init();
  // 检查是否是流转启动
  final isTransferred = await DistributedTransferUtil.isTransferred();
  if (isTransferred) {
    // 恢复路由和状态
    await afterTransfer();
  }
  runApp(GetMaterialApp(
    initialRoute: isTransferred ? null : '/login',
    getPages: getPages,
  ));
}

4.3 流转避坑指南

  • 状态保存完整性:确保流转前保存所有关键状态(路由、用户输入、列表滚动位置等);
  • 设备类型适配:流转到不同设备类型(如手机→平板)时,需适配路由页面的布局;
  • 权限申请:流转需申请分布式权限(ohos.permission.DISTRIBUTED_DATASYNC)。

五、常见问题(FAQ)

Q1:如何实现路由动画?

A1:原生路由通过pageBuilder自定义动画:

dart

Navigator.push(
  context,
  PageRouteBuilder(
    transitionDuration: const Duration(milliseconds: 300),
    pageBuilder: (context, animation, secondaryAnimation) => const DetailPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      // 淡入淡出动画
      return FadeTransition(
        opacity: animation,
        child: child,
      );
    },
  ),
);

GetX 路由通过transition参数设置动画:

dart

Get.toNamed('/detail', transition: Transition.fade); // 淡入淡出

Q2:如何监听路由变化?

A2:原生路由通过NavigatorObserver监听:

dart

class MyNavigatorObserver extends NavigatorObserver {
  @override
  void didPush(Route route, Route? previousRoute) {
    debugPrint('跳转到:${route.settings.name}');
  }

  @override
  void didPop(Route route, Route? previousRoute) {
    debugPrint('从${route.settings.name}返回');
  }
}

// 配置到MaterialApp
MaterialApp(
  navigatorObservers: [MyNavigatorObserver()],
);

GetX 路由通过routeObserver监听:

dart

GetMaterialApp(
  navigatorObservers: [GetObserver(
    didPush: (route, previousRoute) {
      debugPrint('跳转到:${route.settings.name}');
    },
  )],
);

Q3:分布式流转后,页面布局错乱怎么办?

A3:1. 流转后重新计算布局,利用MediaQuery获取目标设备屏幕尺寸;2. 在initState中调用setState触发 UI 重建;3. 针对不同设备类型预设布局方案。

结语:构建开源鸿蒙全场景导航体系

导航路由是应用的 “骨架”,从基础的页面跳转,到进阶的参数传递、路由拦截,再到开源鸿蒙特有的分布式流转,每个环节都需要结合业务需求和设备特性进行设计。

通过本文的实战案例,你已经掌握了原生路由、GetX 路由和分布式流转的核心用法,能够根据应用规模选择合适的导航方案。在开源鸿蒙全场景开发中,灵活的导航体系能让用户在不同设备间无缝切换,提升应用的竞争力。

Logo

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

更多推荐