在移动应用程序中,路由与导航是构建多页面应用的核心。它允许用户在不同的屏幕(页面)之间进行切换。Flutter 提供了强大的导航系统,主要通过 Navigator 和 Route 来管理页面栈。
1. Navigator (导航器)
Navigator 是一个管理 Route 栈的 Widget。它是一个堆栈,每个 Route 都代表一个页面。当用户导航到新页面时,新页面被推入堆栈顶部;当用户返回时,顶部页面被弹出。
2. Route (路由)
Route 是应用程序中屏幕或页面的抽象。Flutter 提供了几种内置的 Route 类型:
MaterialPageRoute:用于创建 Material Design 风格的页面过渡动画,是 Flutter 应用中最常用的路由。CupertinoPageRoute:用于创建 iOS 风格的页面过渡动画。3. 导航操作
Navigator 提供了多种方法来操作页面栈:
Navigator.push():将新页面推入堆栈顶部。新页面会覆盖旧页面。
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondScreen()),
);
Navigator.pop():从堆栈中弹出当前页面,返回到上一个页面。
Navigator.pop(context);
Navigator.pushReplacement():将新页面推入堆栈,并替换当前页面。旧页面会被销毁。
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const LoginScreen()),
);
Navigator.pushAndRemoveUntil():将新页面推入堆栈,并移除所有旧页面,直到满足某个条件。常用于登录后清除所有历史页面。
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const HomeScreen()),
(Route<dynamic> route) => false, // 移除所有路由
);
Navigator.popAndPushNamed():弹出当前路由,然后将命名路由推入堆栈。Navigator.of(context):获取最近的 Navigator 实例。通常在 build 方法中使用 context 来获取。4. 命名路由 (Named Routes)
除了直接创建 MaterialPageRoute,Flutter 还支持命名路由。命名路由允许你通过一个字符串名称来引用页面,这使得导航代码更简洁,并且更容易管理复杂的导航流。
MaterialApp 或 CupertinoApp 的 routes 属性中定义。
MaterialApp(
routes: {
// 定义命名路由
'/': (context) => const HomeScreen(),
'/details': (context) => const DetailScreen(),
'/settings': (context) => const SettingsScreen(),
},
);
Navigator.pushNamed() 或 Navigator.popAndPushNamed()。
Navigator.pushNamed(context, '/details');
arguments 属性传递参数。
Navigator.pushNamed(
context,
'/details',
arguments: {
'id': 123,
'name': 'Product A',
},
);
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final int id = args['id'];
final String name = args['name'];
5. onGenerateRoute (动态路由生成)
对于更复杂的路由逻辑,例如需要根据参数动态生成页面,或者处理未定义的路由,可以使用 onGenerateRoute 回调。它会在 routes 中找不到匹配路由时被调用。
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/product') {
final args = settings.arguments as Map<String, dynamic>;
return MaterialPageRoute(
builder: (context) => ProductScreen(productId: args['id']),
);
}
// 处理其他路由或返回一个错误页面
return MaterialPageRoute(builder: (context) => const UnknownRouteScreen());
},
);
路由与导航是所有多页面 Flutter 应用的基础。无论是简单的页面跳转,还是复杂的带参数导航、动态路由,掌握 Flutter 的导航系统都是必不可少的。
案例:一个简单的多页面应用,包含命名路由和参数传递
我们将创建一个包含三个页面的应用:主页、详情页和设置页。主页可以导航到详情页(带参数)和设置页。详情页和设置页可以返回主页。
import 'package:flutter/material.dart';
// 主页
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('主页'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () {
// 导航到详情页,并传递参数
Navigator.pushNamed(
context,
'/details',
arguments: {'productId': 101, 'productName': 'Flutter T-Shirt'},
);
},
child: const Text('前往详情页 (带参数)'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 导航到设置页
Navigator.pushNamed(context, '/settings');
},
child: const Text('前往设置页'),
),
],
),
),
);
}
}
// 详情页
class DetailScreen extends StatelessWidget {
const DetailScreen({super.key});
@override
Widget build(BuildContext context) {
// 获取传递的参数
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>?;
final int productId = args?['productId'] ?? 0;
final String productName = args?['productName'] ?? '未知产品';
return Scaffold(
appBar: AppBar(
title: const Text('详情页'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('产品 ID: $productId', style: const TextStyle(fontSize: 20)),
Text('产品名称: $productName', style: const TextStyle(fontSize: 20)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 返回上一页
Navigator.pop(context);
},
child: const Text('返回主页'),
),
],
),
),
);
}
}
// 设置页
class SettingsScreen extends StatelessWidget {
const SettingsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('设置页'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('这是设置页面', style: TextStyle(fontSize: 24)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 返回上一页
Navigator.pop(context);
},
child: const Text('返回主页'),
),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(
title: 'Flutter 导航示例',
// 定义命名路由
routes: {
'/': (context) => const HomeScreen(),
'/details': (context) => const DetailScreen(),
'/settings': (context) => const SettingsScreen(),
},
// 如果需要处理未定义的路由,可以使用 onGenerateRoute
// onGenerateRoute: (settings) {
// if (settings.name == '/unknown') {
// return MaterialPageRoute(builder: (context) => const Text('未知路由'));
// }
// return null; // 让 routes 处理
// },
));
}
案例分析:
MaterialApp 的 routes 属性:这是定义命名路由的地方。我们将 '/' 映射到 HomeScreen,'/details' 映射到 DetailScreen,'/settings' 映射到 SettingsScreen。Navigator.pushNamed(context, '/details', arguments: {...}):在 HomeScreen 中,我们使用 pushNamed 方法通过命名路由导航到 DetailScreen。arguments 参数用于传递数据。这里传递了一个 Map,包含 productId 和 productName。ModalRoute.of(context)!.settings.arguments:在 DetailScreen 中,我们通过 ModalRoute.of(context)!.settings.arguments 来获取传递过来的参数。由于 arguments 可能是 null,并且类型是 Object?,所以需要进行类型转换和空安全处理(例如使用 as Map<String, dynamic>? 和 ?? 操作符)。Navigator.pop(context):在 DetailScreen 和 SettingsScreen 中,我们使用 pop 方法返回到上一个页面。当页面栈中只有一个页面时,pop 会关闭应用(在 Android 上)。这个案例清晰地展示了 Flutter 中路由与导航的基本用法,包括命名路由的定义、页面之间的跳转以及参数的传递。掌握这些概念是构建任何多页面 Flutter 应用的基础。