Flutter路由导航(1):基本使用
一、路由管理
1.1 认识Flutter路由
路由的概念由来已久,包括网络路由
、后端路由
,到现在广为流行的前端路由
。
- 无论路由的概念如何应用,它的核心是一个
路由映射表
- 比如:名字
detail
映射到DetailPage
页面等 - 有了这个映射表之后,我们就可以方便的根据名字来完成路由的转发(在前端表现出来的就是页面跳转)
在 Flutter 中,路由管理主要有两个类:Route 和 Navigator
1.2. Route
Route:一个页面要想被路由统一管理,必须包装为一个 Route
- 官方的说法很清晰:An abstraction for an entry managed by a Navigator.
但是 Route 是一个抽象类,所以它是不能实例化的
- 在上面有一段注释,让我们查看 MaterialPageRoute 来使用
/// See [MaterialPageRoute] for a route that replaces the
/// entire screen with a platform-adaptive transition.
abstractclass Route<T> {
}
事实上 MaterialPageRoute 并不是 Route 的直接子类:
- MaterialPageRoute 在不同的平台有不同的表现
- 对 Android 平台,打开一个页面会从屏幕底部滑动到屏幕的顶部,关闭页面时从顶部滑动到底部消失
- 对 iOS 平台,打开一个页面会从屏幕右侧滑动到屏幕的左侧,关闭页面时从左侧滑动到右侧消失
- 当然,iOS 平台我们也可以使用 CupertinoPageRoute
MaterialPageRoute -> PageRoute -> ModalRoute -> TransitionRoute -> OverlayRoute -> Route
1.3 Navigator
在 Flutter 中,Navigator 是一个管理应用视图(页面)的组件,它使用栈(Stack)的方式来控制页面的切换。每当你跳转到一个新页面时,Navigator 会将新页面的 Route 压栈(push),当你返回到之前的页面时,它会将当前页面的 Route 出栈(pop)。
为了使用 Navigator 进行页面跳转,我们需要使用 BuildContext,它表示当前 widget 在 widget 树中的位置。BuildContext 是用于与 Navigator 进行交互的必要参数。
Navigator 有几个最常见的方法:
// 路由跳转:传入一个路由对象
Future<T> push<T extendsObject>(Route<T> route)
// 路由跳转:传入一个名称(命名路由)
Future<T> pushNamed<T extendsObject>(
String routeName, {
Object arguments,
})
// 路由返回:可以传入一个参数
bool pop<T extendsObject>([ T result ])
二、路由基本使用
2.1 基本跳转
我们来实现一个最基本跳转:
- 创建首页页面,中间添加一个按钮,点击按钮跳转到详情页面
- 创建详情页面,中间添加一个按钮,点击按钮返回到首页页面
核心的跳转代码如下(首页中代码):
ElevatedButton(
child: Text("打开详情页"),
onPressed: () => _onPushTap(context),
),
// 按钮点击执行的代码
_onPushTap(BuildContext context) {
Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
return DetailPage();
}));
}
核心的返回代码如下(详情页中代码):
ElevatedButton(
child: Text("返回首页"),
onPressed: () => _onBackTap(context),
),
// 按钮点击执行的代码
_onBackTap(BuildContext context) {
Navigator.of(context).pop();
}
完整代码如下:
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
main(List<String> args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyHomeBody(),
),
);
}
}
// 首页
class MyHomeBody extends StatelessWidget {
const MyHomeBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('首页'),
),
body: Center(
child: ElevatedButton(
child: Text("打开详情页"),
onPressed: () => _onPushTap(context),
),
),
);
}
}
// 按钮点击执行的代码
_onPushTap(BuildContext context) {
Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
return DetailPage();
}));
}
// 详情页
class DetailPage extends StatelessWidget {
const DetailPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('详情页'),
),
body: Center(
child: ElevatedButton(
child: Text("返回首页"),
onPressed: () => _onBackTap(context),
),
),
);
}
}
// 按钮点击执行的代码
_onBackTap(BuildContext context) {
Navigator.of(context).pop();
}
2.2 参数传递
在跳转过程中,我们通常可能会携带一些参数,比如
- 首页跳到详情页,携带一条信息:a home message
- 详情页返回首页,携带一条信息:a detail message
首页跳转核心代码:
- 在页面跳转时,会返回一个 Future
- 该 Future 会在详情页面调用 pop 时,回调对应的 then 函数,并且会携带结果
// 按钮点击执行的代码
_onPushTap(BuildContext context) {
// 1.跳转代码
final future = Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
return DetailPage(data: "a home message");
}));
// 2.获取结果
/*future.then((res) {
setState(() {
_message = res;
});
});*/
}
详情页返回核心代码:
// 按钮点击执行的代码
_onBackTap(BuildContext context) {
Navigator.of(context).pop("a detail message");
}
完整代码如下:
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
main(List<String> args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyHomeBody(),
),
);
}
}
// 首页
class MyHomeBody extends StatelessWidget {
const MyHomeBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('首页'),
),
body: Center(
child: ElevatedButton(
child: Text("打开详情页"),
onPressed: () => _onPushTap(context),
),
),
);
}
}
// 按钮点击执行的代码
_onPushTap(BuildContext context) {
// 1.跳转代码
final future = Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
return DetailPage(data: "a home message");
}));
// 2.获取结果
/*future.then((res) {
setState(() {
_message = res;
});
});*/
}
// 详情页
class DetailPage extends StatelessWidget {
final String data;
DetailPage({required this.data});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('详情页'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center, // 主轴居中排列
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(data),
SizedBox(height: 20.0), // 这里是间隔
ElevatedButton(
child: Text("返回首页"),
onPressed: () => _onBackTap(context),
),
],
),
),
);
}
}
// 按钮点击执行的代码
_onBackTap(BuildContext context) {
Navigator.of(context).pop("a detail message");
}
2.3 返回细节
但是这里有一个问题,如果用户是点击右上角的返回按钮,如何监听呢?
方法一:自定义返回的按钮(在详情页中修改 Scaffold 的 appBar)
appBar: AppBar(
title: Text("详情页"),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.of(context).pop("a back detail message");
},
),
),
方法二:监听返回按钮的点击(给 Scaffold 包裹一个 WillPopScope)
-
WillPopScope 有一个 onWillPop 的回调函数,当我们点击返回按钮时会执行
-
这个函数要求有一个 Future 的返回值:
-
- true:那么系统会自动帮我们执行 pop 操作
- false:系统不再执行 pop 操作,需要我们自己来执行
return WillPopScope(
onWillPop: () {
Navigator.of(context).pop("a back detail message");
return Future.value(false);
},
child: Scaffold(
appBar: AppBar(
title: Text("详情页"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text("返回首页"),
onPressed: () => _onBackTap(context),
),
Text(_message, style: TextStyle(fontSize: 20, color: Colors.red),)
],
),
),
),
);
三、命名路由使用
我们可以通过创建一个新的 Route,使用 Navigator 来导航到一个新的页面,但是如果在应用中很多地方都需要导航到同一个页面(比如在开发中,首页、推荐、分类页都可能会跳到详情页),那么就会存在很多重复的代码。在这种情况下,我们可以使用命名路由(named route)
命名路由是一种用于管理页面导航的技术,它允许你为每个页面分配一个唯一的名称,并通过这些名称在应用程序中进行页面之间的导航。命名路由,由一对字符串(路由名称)和对应的屏幕(或称为页面/视图)组成。
命名路由的好处:
- 提高代码可维护性:命名路由使得路由和它们对应的屏幕解耦,这让查找和修改特定路由相关的代码变得更加容易。
- 简化路由管理:当应用的结构变得更为复杂时,使用命名路由可以帮助集中管理路由,而不是在代码中散布大量的 Navigator.push 和 MaterialPageRoute。
配置命名路由
我们可以在 MaterialApp
的 routes
属性中定义所有的命名路由。routes
是一个 Map,它的键是字符串(路由的名称),而值是对应的构造器函数,返回相应的页面 Widget。
MaterialApp(
title: 'Navigation with Named Routes',
// 初始路由,应用启动时加载的路由
initialRoute: '/',
// 定义命名路由
routes: {
'/': (context) => HomePage(),
'/newPage': (context) => NewPage(),
'/thirdPage': (context) => ThirdPage(),
},
)
Navigator.pushNamed 方法
要使用命名路由进行页面跳转,可以调用 Navigator.pushNamed
方法,并传入对应的路由名称。
Navigator.pushNamed(context, '/newPage');
Navigator.pop 方法
Navigator.pop
方法用于将栈顶的 Route
弹出,返回到前一个页面。
Navigator.pop(context);
Navigator.popAndPushNamed 方法
Navigator.popAndPushNamed 方法用于从当前页面返回到上一个页面,并立即导航到指定的命名路由。
该方法的作用是先执行 Navigator.pop 方法返回到上一个页面,然后立即执行 Navigator.pushNamed 方法导航到新的命名路由
Navigator.popAndPushNamed(context, '/thirdPage');
配置和使用命名路由示例代码
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation with Named Routes',
// 初始路由,应用启动时加载的路由
initialRoute: '/',
// 定义命名路由
routes: {
'/': (context) => HomePage(),
'/newPage': (context) => NewPage(),
'/thirdPage': (context) => ThirdPage(),
},
));
}
// 首页
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('首页'),
),
body: Center(
child: ElevatedButton(
child: Text('打开新页面'),
// 使用命名路由进行页面跳转
onPressed: () {
Navigator.pushNamed(context, '/newPage');
},
),
),
);
}
}
// 新页面
class NewPage extends StatelessWidget {
const NewPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('新页面'),
),
body: Column(
children: [
Text('欢迎来到新页面'),
TextButton(
onPressed: () {
Navigator.popAndPushNamed(context, '/thirdPage');
},
child: Text("popAndPushNamed"))
],
),
);
}
}
// 第三页面
class ThirdPage extends StatelessWidget {
const ThirdPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('第三页面'),
),
body: Column(
children: [
Text('欢迎来到第三页面'),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("pop"))
],
),
);
}
}
效果图如下所示:
四、路由生成钩子
在 Flutter 中,onGenerateRoute 是一个非常强大的钩子,允许开发者对路由进行自定义操作。它在 MaterialApp 或 CupertinoApp 中定义,并在导航到命名路由时被调用,特别是当使用 Navigator.pushNamed 时。它可以用于动态生成路由,传递参数到新页面,甚至处理未知的路由。
下面是一个使用 onGenerateRoute 的示例,其中包括了处理动态路由和传递参数到未知页面的代码:
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 应用初始路由
initialRoute: '/',
// onGenerateRoute 用于处理动态路由
onGenerateRoute: (RouteSettings settings) {
// 获取传递过来的参数,如果参数为null,则提供一个空的Map
final arguments = settings.arguments as Map<String, dynamic>? ?? {};
// 根据 settings.name 处理不同路由
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => HomePage());
case '/details':
// 假设 DetailsPage 接受一个 'data' 参数
final String data = arguments['data'] as String? ?? '默认值';
return MaterialPageRoute(
builder: (context) => DetailsPage(data: data));
default:
// 如果没有匹配的路由,返回到一个未知页面路由
return MaterialPageRoute(builder: (context) => UnknownPage());
}
},
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String _data = "缺省值";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('首页'),
),
body: Column(
children: [
Text(_data), // 显示传递到本页面的数据
ElevatedButton(
onPressed: () async {
// 导航到详情页,并传递数据。同时,使用await等待详情页返回的结果
final result = await Navigator.pushNamed(
context,
'/details',
arguments: {'data': '这是一个秘密信息!'},
);
final arguments = result as Map<String, dynamic>? ?? {};
setState(() {
if (mounted) {
_data = arguments["data"] as String? ?? "";
}
});
},
child: Text('前往详情页'),
),
],
),
);
}
}
class DetailsPage extends StatelessWidget {
final String data;
DetailsPage({required this.data});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('详情页'),
),
body: Column(
children: [
Text(data), // 显示传递到本页面的数据
TextButton(
onPressed: () {
// 回传数据数据
Navigator.pop(context, {'data': '详情返回数据'});
},
child: Text("pop"))
],
),
);
}
}
class UnknownPage extends StatelessWidget {
const UnknownPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('未知页面'),
),
body: Center(
child: Text('该路由名称不存在。'),
),
);
}
}
效果图如下所示:
参考:
Flutter基础(一)页面跳转_flutter 页面跳转-CSDN博客
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库