ToolkitX
知识库工具箱

导航与路由

Navigator, GoRouter, 深层链接

20min·进阶

01. Navigator 和路由基础

Flutter 用 Navigator 管理页面栈,就像一摞卡片,push 在顶上放一张,pop 把顶上那张拿走。MaterialPageRoute 定义页面切换动画。Navigator.push 跳转新页面,Navigator.pop 返回上一页。这是命令式导航,简单直接。
dart
// 跳转到第二页
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondPage()),
);

// 返回上一页
Navigator.pop(context);

// 带结果返回
// 第二页中:
Navigator.pop(context, '带回去的数据');

// 第一页接收:
final result = await Navigator.push<String>(
  context,
  MaterialPageRoute(builder: (context) => SecondPage()),
);
print('返回的结果: $result');
Navigator 是从上往下叠的,push 加一页,pop 拿掉一页,就像浏览器的前进后退。

02. 命名路由

MaterialApp 的 routes 属性定义命名路由表,类似 Web 的 URL 路由。Navigator.pushNamed 按名字跳转,路由在 app 入口统一管理。适合中小型项目路径不复杂的场景。onGenerateRoute 处理未知路由或带参数的动态路由。
dart
void main() {
  runApp(MaterialApp(
    initialRoute: '/',
    routes: {
      '/': (context) => HomePage(),
      '/profile': (context) => ProfilePage(),
      '/settings': (context) => SettingsPage(),
    },
    onGenerateRoute: (settings) {
      if (settings.name == '/user') {
        final id = settings.arguments as int;
        return MaterialPageRoute(
          builder: (context) => UserPage(id: id),
        );
      }
      return null;
    },
  ));
}

// 跳转
Navigator.pushNamed(context, '/profile');
Navigator.pushNamed(context, '/user', arguments: 123);
命名路由不支持路径参数解析(如 /user/123),需要自己处理。复杂路由用 GoRouter。

03. GoRouter 声明式路由

GoRouter 是 Flutter 官方推荐的声明式路由方案,支持路径参数(/user/:id)、路由守卫(重定向)、嵌套路由、Deep Link 等高级特性。GoRouter 基于 Router 组件,配合 MaterialApp.router 使用。路由定义是一个集中的列表,可读性极强。
dart
import 'package:go_router/go_router.dart';

final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomePage(),
    ),
    GoRoute(
      path: '/user/:id',
      builder: (context, state) {
        final id = state.pathParameters['id']!;
        return UserPage(id: int.parse(id));
      },
    ),
    // 嵌套路由
    ShellRoute(
      builder: (context, state, child) => ScaffoldWithNavBar(child: child),
      routes: [
        GoRoute(path: '/dashboard', builder: (_, __) => DashboardPage()),
        GoRoute(path: '/notifications', builder: (_, __) => NotificationPage()),
      ],
    ),
  ],
  // 导航守卫
  redirect: (context, state) {
    final isLoggedIn = AuthService.isLoggedIn;
    final isAuthRoute = state.matchedLocation == '/login';
    if (!isLoggedIn && !isAuthRoute) return '/login';
    if (isLoggedIn && isAuthRoute) return '/';
    return null;
  },
);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router,
    );
  }
}
GoRouter 支持 Deep Link,用户点击 https://yourapp.com/user/123 能直接打开对应页面。

04. 页面传参和接收结果

页面之间传参数有多种方式:构造函数直接传(最直接)、命名路由的 arguments 传递、用状态管理框架全局共享。接收返回结果用 await Navigator.push,第二页 pop 时塞数据第一页就能收到。页面想阻止返回(比如有未保存的修改)用 WillPopScope 拦截。
dart
// 方式1: 构造函数传参
class DetailPage extends StatelessWidget {
  final int itemId;
  DetailPage({required this.itemId});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('详情: $itemId')),
    );
  }
}

// 方式2: 接收返回结果
Widget build(BuildContext context) {
  return ElevatedButton(
    onPressed: () async {
      final result = await Navigator.push(
        context,
        MaterialPageRoute(builder: (_) => EditPage()),
      );
      if (result == 'saved') {
        // 刷新列表
      }
    },
    child: Text('编辑'),
  );
}

// 方式3: 防止误返回
WillPopScope(
  onWillPop: () async {
    if (_hasUnsavedChanges) {
      final shouldLeave = await showDialog<bool>(
        context: context,
        builder: (_) => AlertDialog(
          title: Text('确定离开?'),
          content: Text('有未保存的修改'),
          actions: [
            TextButton(onPressed: () => Navigator.pop(context, true), child: Text('离开')),
            TextButton(onPressed: () => Navigator.pop(context, false), child: Text('留下')),
          ],
        ),
      );
      return shouldLeave ?? false;
    }
    return true;
  },
  child: ...\n)
WillPopScope 包装页面可以监听系统的返回按钮(Android)或侧滑返回(iOS),做拦截。

05. 底部导航和 Tab 切换

底部导航栏是 App 最常见的导航方式。BottomNavigationBar 组件配合 IndexedStack 保持各 Tab 页面状态(切换 Tab 不销毁页面)。Scaffold 的 bottomNavigationBar 属性放导航栏,body 是当前显示的页面。配合 PageView 还能实现滑动手势切换。
dart
class MainScreen extends StatefulWidget {
  @override
  _MainScreenState createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int _currentIndex = 0;
  
  final _pages = [
    HomePage(),
    DiscoverPage(),
    ProfilePage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: _pages,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) => setState(() => _currentIndex = index),
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.explore), label: '发现'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
        ],
      ),
    );
  }
}
IndexedStack 缓存所有子页面,切换 Tab 时页面状态保持。如果不需要缓存直接 _pages[_currentIndex] 即可。

知识测验

1/4正确 0

Flutter Navigator.push 的作用?

下一节

网络请求

下一节