Skip to content

导航与路由

导航是让用户在不同页面之间跳转的核心机制。Flutter 提供了两种导航方案:基础的 Navigator 和声明式的 go_router

基本跳转

dart
import 'package:flutter/material.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: () {
            // 跳转到新页面
            // ─── ★ Navigator.push ──────────────
            Navigator.of(context).push(
              MaterialPageRoute(builder: (context) => const DetailPage()),
            );
            // ─── ☆ Navigator.push ──────────────
          },
          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 ──────────────
            Navigator.of(context).pop();
            // ─── ☆ Navigator.pop ──────────────
          },
          child: const Text('返回'),
        ),
      ),
    );
  }
}

带参数跳转

dart
// 跳转时传参
Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) => DetailPage(id: 123, 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: Text('ID: $id'),
    );
  }
}

接收返回值

dart
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('选择城市')),
      body: ListView(
        children: [
          ListTile(
            title: const Text('北京'),
            onTap: () => Navigator.of(context).pop('北京'),
          ),
          ListTile(
            title: const Text('上海'),
            onTap: () => Navigator.of(context).pop('上海'),
          ),
          ListTile(
            title: const Text('广州'),
            onTap: () => Navigator.of(context).pop('广州'),
          ),
        ],
      ),
    );
  }
}

class CityPickerPage extends StatefulWidget {
  const CityPickerPage({super.key});

  @override
  State<CityPickerPage> createState() => _CityPickerPageState();
}

class _CityPickerPageState extends State<CityPickerPage> {
  String _selectedCity = '未选择';

  void _pickCity() async {
    // 跳转并等待返回值
    final result = await Navigator.of(context).push<String>(
      MaterialPageRoute(builder: (context) => const SelectCityPage()),
    );
    if (result != null) {
      setState(() => _selectedCity = result);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('选择城市')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('当前城市: $_selectedCity', style: const TextStyle(fontSize: 20)),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _pickCity,
              child: const Text('选择城市'),
            ),
          ],
        ),
      ),
    );
  }
}

命名路由

MaterialApp 中定义路由表:

dart
MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => HomePage(),
    '/detail': (context) => DetailPage(),
    '/settings': (context) => SettingsPage(),
  },
)

使用命名路由跳转:

dart
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => Navigator.of(context).pushNamed('/detail'),
              child: const Text('跳转详情'),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () => Navigator.of(context).pushNamed(
                '/detail',
                arguments: {'id': 123},
              ),
              child: const Text('带参数跳转'),
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as Map?;
    return Scaffold(
      appBar: AppBar(title: const Text('详情页')),
      body: Center(
        child: Text('ID: ${args?['id'] ?? '无参数'}'),
      ),
    );
  }
}

### 替换与清空

```dart
import 'package:flutter/material.dart';

// 替换当前页面(比如登录后替换为首页)
class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  void _login() {
    // 模拟登录成功后替换为首页
    Navigator.of(context).pushReplacement(
      MaterialPageRoute(builder: (context) => const HomePage()),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('登录')),
      body: Center(
        child: ElevatedButton(
          onPressed: _login,
          child: const Text('登录'),
        ),
      ),
    );
  }
}

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: () {
            // 退出登录:清空所有页面并跳转登录页
            Navigator.of(context).pushAndRemoveUntil(
              MaterialPageRoute(builder: (context) => const LoginPage()),
              (route) => false,
            );
          },
          child: const Text('退出登录'),
        ),
      ),
    );
  }
}

go_router — 声明式路由(推荐)

go_router 是 Flutter 官方推荐的声明式路由方案,支持深层链接、重定向、嵌套路由等高级功能。

安装

bash
flutter pub add go_router

基本用法

dart
import 'package:go_router/go_router.dart';

// 1. 定义路由
final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomePage(),
    ),
    GoRoute(
      path: '/detail/:id',  // 路径参数
      builder: (context, state) {
        final id = state.pathParameters['id']!;
        return DetailPage(id: id);
      },
    ),
    GoRoute(
      path: '/settings',
      builder: (context, state) => SettingsPage(),
    ),
  ],
);

// 2. 在 MaterialApp 中使用
MaterialApp.router(
  routerConfig: router,
)

// 3. 跳转
context.go('/detail/123');        // 替换当前路由
context.push('/detail/123');      // 压入新路由(可返回)
context.go('/settings');          // 跳转设置页

// 4. 返回
context.pop();

路径参数与查询参数

dart
// 路径参数:定义时用 :变量名
GoRoute(
  path: '/user/:id',
  builder: (context, state) {
    final id = state.pathParameters['id'];  // 获取路径参数
    return UserProfilePage(userId: id!);
  },
)

// 查询参数:URL 中 ?key=value
context.go('/search?keyword=flutter&page=1');

// 获取查询参数
GoRoute(
  path: '/search',
  builder: (context, state) {
    final keyword = state.uri.queryParameters['keyword'];
    final page = state.uri.queryParameters['page'];
    return SearchPage(keyword: keyword, page: int.tryParse(page ?? '1') ?? 1);
  },
)

嵌套路由(ShellRoute)

ShellRoute 可以在子路由之间共享布局(如底部导航栏):

dart
final router = GoRouter(
  initialLocation: '/',
  routes: [
    ShellRoute(
      builder: (context, state, child) {
        return ScaffoldWithNavBar(child: child);  // 共享布局
      },
      routes: [
        GoRoute(
          path: '/',
          builder: (context, state) => HomePage(),
        ),
        GoRoute(
          path: '/explore',
          builder: (context, state) => ExplorePage(),
        ),
        GoRoute(
          path: '/profile',
          builder: (context, state) => ProfilePage(),
        ),
      ],
    ),
    // 不带底部导航的页面
    GoRoute(
      path: '/detail/:id',
      builder: (context, state) => DetailPage(id: state.pathParameters['id']!),
    ),
  ],
);

共享布局组件:

dart
class ScaffoldWithNavBar extends StatelessWidget {
  final Widget child;

  const ScaffoldWithNavBar({super.key, required this.child});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: child,
      bottomNavigationBar: NavigationBar(
        selectedIndex: _selectedIndex(context),
        onDestinationSelected: (index) {
          switch (index) {
            case 0: context.go('/'); break;
            case 1: context.go('/explore'); break;
            case 2: context.go('/profile'); break;
          }
        },
        destinations: [
          NavigationDestination(icon: Icon(Icons.home), label: '首页'),
          NavigationDestination(icon: Icon(Icons.explore), label: '发现'),
          NavigationDestination(icon: Icon(Icons.person), label: '我的'),
        ],
      ),
    );
  }

  int _selectedIndex(BuildContext context) {
    final location = GoRouterState.of(context).uri.path;
    if (location.startsWith('/explore')) return 1;
    if (location.startsWith('/profile')) return 2;
    return 0;
  }
}

重定向

用于权限控制(如未登录时跳转登录页):

dart
final router = GoRouter(
  redirect: (context, state) {
    final isLoggedIn = AuthService.isLoggedIn;
    final isLoginRoute = state.uri.path == '/login';

    if (!isLoggedIn && !isLoginRoute) return '/login';
    if (isLoggedIn && isLoginRoute) return '/';
    return null;  // 无需重定向
  },
  routes: [
    GoRoute(path: '/', builder: (context, state) => HomePage()),
    GoRoute(path: '/login', builder: (context, state) => LoginPage()),
    GoRoute(path: '/settings', builder: (context, state) => SettingsPage()),
  ],
);

错误处理

dart
final router = GoRouter(
  errorBuilder: (context, state) => Scaffold(
    appBar: AppBar(title: Text('页面未找到')),
    body: Center(child: Text('404 - 页面不存在')),
  ),
  routes: [...],
);

go vs push 的区别

方法行为适用场景
context.go('/path')替换当前路由栈底部导航切换、登录后跳转
context.push('/path')压入新路由详情页、编辑页(需要返回)

特性Navigatorgo_router
风格命令式声明式
深层链接需手动处理内置支持
嵌套路由不支持ShellRoute
重定向手动实现内置支持
Web URL不支持完整支持
学习成本
推荐程度简单项目正式项目(推荐)

建议

  • 学习阶段:先用 Navigator 理解导航的基本概念
  • 正式项目:使用 go_router,支持声明式路由、深层链接和 Web URL

下一步

基于 Flutter 官方文档整理