Skip to content

Navigator 路由

一句话理解

Flutter 的页面管理就像浏览器的标签页——你打开一个页面就是"压栈"(push),返回上一页就是"出栈"(pop)。

页面栈是什么?

想象一摞书:

┌─────────────┐
│  详情页(最上面,正在看)  │  ← 栈顶
├─────────────┤
│  列表页                    │
├─────────────┤
│  首页(最先打开的)        │  ← 栈底
└─────────────┘
  • push:往这摞书上面再放一本(打开新页面)
  • pop:拿走最上面那本(返回上一页)

最基本的跳转:push

点击按钮跳转到新页面:

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // ─── ★ Navigator.push 跳转 ──────────────
            Navigator.push(
              context,
              // ─── ★ MaterialPageRoute ──────────────
              MaterialPageRoute(builder: (context) => const DetailPage()),
              // ─── ☆ MaterialPageRoute ──────────────
            );
            // ─── ☆ 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: const Center(child: Text('这是详情页内容')),
    );
  }
}

MaterialPageRoute 是最常用的路由,它自带从右往左滑入的过渡动画。

最基本的返回:pop

在详情页的返回按钮:

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          onPressed: () => Navigator.pop(context),
        ),
        title: const Text('详情页'),
      ),
      body: const Center(child: Text('点击左上角返回')),
    );
  }
}

pop() 不需要指定返回到哪个页面——它永远是回到上一页

常用操作速查

你想做什么代码效果
打开新页面Navigator.push(...)新页面压入栈顶,可返回
返回上一页Navigator.pop(context)弹出当前页面
替换当前页面Navigator.pushReplacement(...)替换当前页面,不能返回到旧页面
清空所有页面再跳转Navigator.pushAndRemoveUntil(..., (route) => false)清空整个栈,适合"登录后跳首页"

替换当前页面:pushReplacement

场景:从启动页跳转到首页——用户不应该能返回启动页。

dart
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => const HomePage()),
);

清空栈再跳转:pushAndRemoveUntil

场景:登录成功后跳转首页——用户不应该能返回登录页。

dart
Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (context) => const HomePage()),
  (route) => false,  // false = 清除所有旧页面
);

如果想保留首页在栈底,改为 ModalRoute.withName('/')

命名路由

上面用的 MaterialPageRoute直接路由——每次跳转都要写完整的页面组件。命名路由则是给每个页面起一个名字(如 /detail),然后用名字跳转。

为什么需要命名路由?

dart
// ❌ 直接路由:跳转代码和页面组件耦合在一起
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const DetailPage()),
);

// ✅ 命名路由:只写名字,不管页面组件是什么
Navigator.pushNamed(context, '/detail');

好处:跳转代码不用 import 页面组件,所有路由集中管理在一个地方。

注册路由表

MaterialApp 中统一注册:

dart
MaterialApp(
  initialRoute: '/',    // 启动时显示的页面
  routes: {
    '/':           (context) => const HomePage(),
    '/detail':     (context) => const DetailPage(),
    '/settings':   (context) => const SettingsPage(),
  },
)

routes 是一个 Map,key 是路由名字,value 是返回页面的函数。

用名字跳转

dart
// 普通跳转
Navigator.pushNamed(context, '/detail');

// 替换跳转(不能返回)
Navigator.pushReplacementNamed(context, '/home');

// 清空栈再跳转
Navigator.pushNamedAndRemoveUntil(context, '/', (route) => false);

更好的方式:onGenerateRoute

onGenerateRoute 让你在跳转前手动处理路由逻辑,可以安全地传递参数:

dart
MaterialApp(
  initialRoute: '/',
  onGenerateRoute: (settings) {
    switch (settings.name) {
      case '/':
        return MaterialPageRoute(builder: (_) => const HomePage());
      case '/detail':
        final id = settings.arguments as int;
        return MaterialPageRoute(
          builder: (_) => DetailPage(id: id),
        );
      case '/settings':
        return MaterialPageRoute(builder: (_) => const SettingsPage());
      default:
        return MaterialPageRoute(builder: (_) => const NotFoundPage());
    }
  },
)

onGenerateRouteroutes 不能同时使用。用了 onGenerateRoute 就删掉 routes

命名路由的局限性

问题说明
不能自定义过渡动画所有页面只能用相同的 MaterialPageRoute
不支持 Web 浏览器前进按钮Web 端体验不好
深度链接难处理从外部链接打开指定页面不方便
arguments 不安全类型是 Object?,容易写错

建议

对于简单应用可以使用命名路由。有复杂导航需求的应用,推荐使用 go_router

页面传参

页面之间传递数据,主要有以下几种方式:

方式一:构造函数传参(⭐ 最常用)

这是最简单、最安全的方式——直接在创建页面时把数据传进去。

定义页面:

dart
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')),
    );
  }
}

跳转时传参:

dart
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const DetailPage(
      id: 42,
      title: '商品详情',
    ),
  ),
);

为什么推荐? 类型安全,编译器会帮你检查参数是否传对了。

方式二:pop 返回数据(⭐ 最常用)

场景:打开一个选择页,用户选完后把结果带回来。

发起页面——等待结果:

dart
final result = await Navigator.push<String>(
  context,
  MaterialPageRoute(builder: (_) => const SelectPage()),
);
if (result != null) {
  setState(() => _selected = result);
}

选择页面——带数据返回:

dart
ListTile(
  title: const Text('苹果'),
  onTap: () => Navigator.pop(context, '苹果'),  // 第二个参数就是返回值
)

关键:发起页面用 await 等,选择页面用 pop(context, 数据) 返回。

方式三:arguments 传参(命名路由专用)

如果你用了命名路由,不能直接用构造函数传参,需要通过 arguments 传递:

发送:

dart
Navigator.pushNamed(
  context,
  '/detail',
  arguments: {'id': 42, 'name': 'Tom'},
);

接收:

dart
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final id = args['id'];     // 42
final name = args['name']; // Tom

缺点:类型不安全,字段名写错了编译器不会报错。

方式四:全局状态(Provider / Riverpod)

适合多个页面共享的数据(如用户登录信息、购物车):

dart
// 写入
context.read<UserModel>().login(newUser);

// 任意页面读取
final user = context.watch<UserModel>().user;

详见 ProviderRiverpod 章节。

传参方式选择

你的场景用哪个
A 页面给 B 页面传简单数据构造函数(方式一)
B 页面选完东西返回给 A 页面pop 返回值(方式二)
用了命名路由,需要传参arguments(方式三)
多个页面共享同一份数据全局状态(方式四)

iOS 风格的过渡动画

如果你想让页面从底部滑入(iOS 风格),用 CupertinoPageRoute

dart
Navigator.push(
  context,
  CupertinoPageRoute(builder: (context) => const DetailPage()),
);

完整示例

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

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: HomePage());
  }
}

// ---- 首页:传参过去 + 接收返回 ----
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String _city = '未选择';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('选择城市')),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('当前城市: $_city', style: const TextStyle(fontSize: 24)),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                // 构造函数传参(传默认城市)+ pop 返回值
                final result = await Navigator.push<String>(
                  context,
                  MaterialPageRoute(
                    builder: (_) => const CityPage(defaultCity: '北京'),
                  ),
                );
                if (result != null) {
                  setState(() => _city = result);
                }
              },
              child: const Text('选择城市'),
            ),
          ],
        ),
      ),
    );
  }
}

// ---- 城市选择页 ----
class CityPage extends StatelessWidget {
  final String defaultCity;

  const CityPage({super.key, required this.defaultCity});

  @override
  Widget build(BuildContext context) {
    final cities = ['北京', '上海', '广州', '深圳'];

    return Scaffold(
      appBar: AppBar(title: Text('选择城市(默认: $defaultCity)')),
      body: ListView(
        children: cities.map((city) {
          return ListTile(
            title: Text(city),
            onTap: () => Navigator.pop(context, city),  // pop 返回数据
          );
        }).toList(),
      ),
    );
  }
}

小结

方法作用能否返回
push打开新页面✅ 能
pop返回上一页
pushReplacement替换当前页面❌ 不能
pushAndRemoveUntil清空栈再跳转❌ 不能
pushNamed命名路由跳转✅ 能

下一步

基于 Flutter 官方文档整理