Flutter 路由、基本路由、基本传参、命名路由、命名传参等基础学习
参考官方文档:https://docs.flutter.dev/cookbook/navigation
一、切换页面的方式
- 直接切换,类似Android中的布局替换,因为flutter中都是组件,也就是组件直接替换。(不推荐)
- 路由跳转页面
- 基本路由 + 传参数(一般)
- Navigator.push() 或者Navigator.of(context).push()跳转和传参数 MaterialPageRoute(builder: (context) => const SecondRoute(title:"你好flutter"))
- Navigator.pop() 或者Navigator.of(context).pop() 返回页面 类似Android中的onBackPressed()方法
- 命名路由 + 传参数 (推荐)
- 普通命名路由直接传参数与接收 (常用 比较简单和自由)
- 需要在MaterialApp下的routes进行注册页面
- Navigator.pushNamed(context,"路由名称","参数") 进行跳转和传参数
- 接收参数 ModalRoute.of(context)!.settings.arguments
- 指定onGenerateRoute提取参数 (可以实现通用性,若想查看请在文章最下方查看)
- 不需要在routes中注册路由
- 可以指定接收某个路由跳转的参数做处理
- 虽然还是通过Navigator.pushNamed(context,"路由名称","参数") 进行跳转和传参数但经过onGenerateRoute中处理
本质还是通过MaterialPageRoute(builder: (context) =>PassArgumentsScreen(title,message)构造函数在传参数,只不过可以统一管理,页面清晰
- 普通命名路由直接传参数与接收 (常用 比较简单和自由)
- 替换路由
- 普通路由
- 假设A,B,C三个页面 若是A->B->C,最后C想返回到A页面,就得在B->C中B页面跳转方法中使用这个函数Navigator.of(context).pushReplacement("/C",""参数"")
- 命名路由
- 假设A,B,C三个页面 若是A->B->C,最后C想返回到A页面,就得在B->C中B页面跳转方法中使用这个函数Navigator.of(context).pushReplacementNamed("/C",""参数"")
- 普通路由
- 跳转到根目录
- 普通路由
- 假设A,B,C,D四个页面 若是A->B->C->D,最后D想返回到A页面可以在B,C页面中使用替换路由方式。
- 还有一种就是使用Navigator.of(context).pushAndRemoveUntil("首页路由",条件)
//下面的HomeScreen()是我的首页,但其实不是首页也没关系,任意一个页面都是可以的 //因为这段代码的含义是之道条件不满足就会一直删除先前加载的页面,直到满足条件,那么其实不是首页也是可以的, //只是普遍用在返回首页上。 Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder:(context)=> HomeScreen()), (route) => route ==null //这里也可以使用 ModalRoute.withName('/')但前提是你注册了根路由'/' );
- 命名路由
- 假设A,B,C,D四个页面 若是A->B->C->D,最后D想返回到A页面可以在B,C页面中使用替换路由方式。
- 还有一种就是使用Navigator.of(context).pushNamedAndRemoveUntil("首页路由",条件)
Navigator.of(context).pushNamedAndRemoveUntil( MaterialPageRoute(builder:(context)=> HomeScreen()), ModalRoute.withName('/') );
- 普通路由
- 基本路由 + 传参数(一般)
下面这段资料来自与网络 也就是路由的跳转的API更多可以参考下面
Navigator 继承自 StatefulWidget,它有很多相关静态函数,可以帮我们达到页面跳转和数据交互的功能:
- push 将设置的router信息推送到Navigator上,实现页面跳转。
- of 主要是获取 Navigator最近实例的好状态。
- pop 导航到新页面,或者返回到上个页面。
- canPop 判断是否可以导航到新页面
- maybePop 可能会导航到新页面
- popAndPushNamed 指定一个路由路径,并导航到新页面。
- popUntil 反复执行pop 直到该函数的参数predicate返回true为止。
- pushAndRemoveUntil 将给定路由推送到Navigator,删除先前的路由,直到该函数的参数predicate返回true为止。
- pushNamed 将命名路由推送到Navigator。
- pushNamedAndRemoveUntil 将命名路由推送到Navigator,删除先前的路由,直到该函数的参数predicate返回true为止。
- pushReplacement 路由替换。
- pushReplacementNamed 这个也是替换路由操作。推送一个命名路由到Navigator,新路由完成动画之后处理上一个路由。
- removeRoute 从Navigator中删除路由,同时执行Route.dispose操作。
- removeRouteBelow 从Navigator中删除路由,同时执行Route.dispose操作,要替换的路由是传入参数anchorRouter里面的路由。
- replace 将Navigator中的路由替换成一个新路由。
- replaceRouteBelow 将Navigator中的路由替换成一个新路由,要替换的路由是是传入参数anchorRouter里面的路由。
二、举例说明:直接切换
import 'package:flutter/material.dart';
import 'package:myflutterapp/pages/tabs/category_page.dart';
import 'package:myflutterapp/pages/tabs/home_page.dart';
import 'package:myflutterapp/pages/tabs/my_page.dart';
import 'package:myflutterapp/pages/tabs/setting_page.dart';
import 'package:myflutterapp/res/widgets.dart';
/**
* 切换页面
*/
class TabPage extends StatefulWidget {
const TabPage({Key? key}) : super(key: key);
@override
_TabPageState createState() => _TabPageState();
}
class _TabPageState extends State<TabPage> {
int countIndex = 0;
List list = [
HomePage(),
CategoryPage(),
SettingPage(),
MyPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Flutter Demo"),),
body: list[countIndex], //动态切换页面
bottomNavigationBar: BottomNavigationBar(
//items中的View大于3就需要这个属性fiexd,不然下面的图标和文字会变白
type: BottomNavigationBarType.fixed,
//点击事件
onTap:(int index){
setState(() {
this.countIndex = index;
print("$countIndex");
});
},
//当前选中的index 不设置这个就不会变化点击item的颜色
currentIndex: countIndex,
fixedColor: Colors.pink,
//图标的大小
iconSize: 24,
//选中文字的大小
selectedFontSize: 14,
//未选中文字的大小
unselectedFontSize: 12,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label:"首页",
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
label:"分类"
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label:"设置"
),
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
label:"我的"
),
],
),
);
}
}
上面的代码可以看出body中没做任何操作,就是单纯的替换了页面,这也是最直接的方式,这里用做的是tab页面的切换,并不适合普通两个页面的跳转。
三、举例说明:基本路由+传参数 官方文档:https://docs.flutter.dev/cookbook/navigation/navigation-basics
核心api :
- Navigator.push() 或者Navigator.of(context).push() 跳转和传参数 MaterialPageRoute(builder: (context) => const SecondRoute(title:"你好flutter"))
- Navigator.pop() 返回 类似Android中的onBackPressed()方法
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatelessWidget {
const FirstRoute({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
// 若想传参数 采用构造函数传参数
// MaterialPageRoute(builder: (context) => const SecondRoute(title:"你好flutter")),
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
String title;
SecondRoute({this.title="",Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Route'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go back!'),
//使用接收的参数
// child: Text(this.title);
),
),
);
}
}
想要传参数的话可以采用构造函数进行传参数,如上面代码中的注释操作。
四、举例说明:命名路由传参+接收
1.普通命名路由
要求:
- 需要在MaterialApp下的routes进行注册页面
- Navigator.pushNamed("路由名称") 进行跳转
不传递参数:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
initialRoute: "/",
routes: {
"/":(context) => FirstScreen(),
"/second":(context) => SecondScreen(),
},
);
}
}
class FirstScreen extends StatelessWidget {
const FirstScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("FirstScreen"),),
body: Center(
child: ElevatedButton(
// Within the `FirstScreen` widget
onPressed: () {
// Navigate to the second screen using a named route.
Navigator.pushNamed(context, '/second');
},
child: const Text('Launch screen'),
),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("SecondScreen"),),
body: Center(
child: ElevatedButton(
onPressed: (){
Navigator.pop(context);
},
child: Text("Go back!"),
),
),
);
}
}
传递参数:
要求:
- 需要在MaterialApp下的routes进行注册页面
- Navigator.pushNamed(context,"路由名称","参数") 进行跳转和传参数
- 接收参数 ModalRoute.of(context)!.settings.arguments
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
initialRoute: "/",
routes: {
"/":(context) => FirstScreen(),
"/second":(context) => SecondScreen(),
},
);
}
}
class FirstScreen extends StatelessWidget {
const FirstScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("FirstScreen"),),
body: Center(
child: ElevatedButton(
// Within the `FirstScreen` widget
onPressed: () {
//定义传递的参数
Map<String,String> args = {
"title":"this is 传递的参数"
};
//arguments接收的Object参数可以是任意类型的,这里我们用Map类型
Navigator.pushNamed(context, '/second',arguments: args);
},
child: const Text('跳转页面并传递参数'),
),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
//接收传递的参数
Map<String,String> args = ModalRoute.of(context)!.settings.arguments as Map<String,String>;
return Scaffold(
appBar: AppBar(title: Text("SecondScreen"),),
body: Center(
child: Container(
height: 200,
width: 200,
child: Column(
children: [
//接收的Map类型数据显示到界面
Text("${args["title"]}"),
ElevatedButton(
onPressed: (){
Navigator.pop(context);
},
child: Text("Go back!"),
),
],
),
),
),
);
}
}
2.指定命名路由的跳转 + 传参(指定onGenerateRoute)官方案例:https://docs.flutter.dev/cookbook/navigation/navigate-with-arguments
特点:
- 不需要在routes中注册路由
- 可以指定接收某个路由跳转的参数做处理
- 虽然还是通过Navigator.pushNamed(context,"路由名称","参数") 进行跳转和传参数但经过onGenerateRoute中处理
本质还是通过PassArgumentsScreen(title,message)构造函数在传参数,只不过可以统一管理,页面清晰
指定onGenerateRoute (单一,不推荐)
代码:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: ,
//提供一个函数来处理命名路由。
//使用此功能识别正在推送的命名路由,并创建正确的屏幕。
onGenerateRoute: (settings) {
// 指定接收某个特定的 例如当前例子中的PassArguments 路由
if (settings.name == PassArgumentsScreen.routeName) {
// 强转化参数
final args = settings.arguments as ScreenArguments;
// 然后,从参数中提取所需的数据并将数据传递到正确的屏幕。
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
// 该代码目前仅支持 PassArgumentsScreen.routeName。如果我们添加它们,则需要实现其他值。这里的断言将有助于提醒我们调用堆栈中更高的位置,因为否则该断言会在框架中的某个地方触发。
assert(false, '需要你实现路由页面 ${settings.name}');
return null;
},
title: 'Navigation with Arguments',
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 导航到命名路由的按钮。
// 对于此路由,提取 onGenerateRoute 函数中的参数并将它们传递到屏幕。
ElevatedButton(
onPressed: () {
//当用户点击按钮时,导航到命名路由并提供参数作为可选参数。
Navigator.pushNamed(
context,
PassArgumentsScreen.routeName,
arguments: ScreenArguments(
'接受参数屏幕',
'此消息是在 onGenerateRoute函数中提取的。',
),
);
},
child: const Text('导航到接受参数的命名'),
),
],
),
),
);
}
}
// 通过构造函数接受必要参数的 Widget
// 这里通过构造函数接收的参数,
// 是由MyApp 中的MaterialApp的 onGenerateRoute返回的
//return MaterialPageRoute(
// builder: (context) {
// //通过构造函数进行传参数的
// return PassArgumentsScreen(
// title: args.title,
// message: args.message,
// );
// },
// );
class PassArgumentsScreen extends StatelessWidget {
static const routeName = '/passArguments';
final String title;
final String message;
// 此 Widget 接受参数作为构造函数参数。
// 它不会从 ModalRoute 中提取参数。
// 参数由提供给 MaterialApp 小部件的 onGenerateRoute 函数提取。
const PassArgumentsScreen({
Key? key,
required this.title,
required this.message,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Text(message),
),
);
}
}
// 您可以将任何对象传递给 arguments 参数。
// 在此示例中,创建一个包含可自定义标题和消息的类。
// 这里是自定义需要的传递参数类型
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
使用通用的onGenerateRoute(通用,推荐)
代码:
import 'package:flutter/material.dart';
void main() => runApp( MyApp());
class MyApp extends StatelessWidget {
//不依赖MaterialApp中的routes创建单独的路由列表
//或者 final myRoutes 不加类型也是可以的
Map<String, WidgetBuilder>? myRoutes = {
"/":(context) => HomeScreen(),
"/second" : (context,{arguments}) => SecondScreen(arguments: arguments),
};
@override
Widget build(BuildContext context) {
return MaterialApp(
//提供一个函数来处理命名路由。
//使用此功能识别正在推送的命名路由,并创建正确的屏幕。
onGenerateRoute: (RouteSettings settings) {
//因为我们页面跳转和传参数会有很多情况,
//于是做一个通用的路由接收是有必要的
//1.获取当前调用的路由名称
String? routeName = settings.name;
//2.找到我们上面定义的Map路由表中是否包含此路由,并取出方法
Function pageContentBuilder = this.myRoutes?[routeName] as Function;
//3.判断不为null表示找到了某个路由定义的方法
if(pageContentBuilder != null) {
//4.判断setting中的 arguments 不为null
if(settings.arguments != null){
//5.返回要跳转的页面和传递参数
final Route route = MaterialPageRoute(
//6.使用我们获取到的Function 调用带参数的去传递参数
builder:(context) => pageContentBuilder(context
,arguments:settings.arguments)
);
return route;
}else{
//7.否则使用我们获取到的Function 调用不带参数的
return MaterialPageRoute(
builder:(context) => pageContentBuilder(context)
);
}
}
},
title: 'Navigation with Arguments',
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 导航到命名路由的按钮。
// 对于此路由,提取 onGenerateRoute 函数中的参数并将它们传递到屏幕。
ElevatedButton(
onPressed: () {
//当用户点击按钮时,导航到命名路由并提供参数作为可选参数。
Navigator.pushNamed(
context,
'/second',
arguments: ScreenArguments(
'接收的参数标题',
'此消息是在 onGenerateRoute函数中提取的。',
),
);
},
child: const Text('导航到接受参数的命名'),
),
],
),
),
);
}
}
class SecondScreen extends StatelessWidget {
ScreenArguments? arguments;
SecondScreen({this.arguments,Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
//接收传递的参数
return Scaffold(
appBar: AppBar(title: Text("SecondScreen"),),
body: Center(
child: Container(
height: 200,
width: 400,
child: Column(
children: [
//使用接收到的数据显示
Text("title: ${arguments?.title}"),
Text("message: ${arguments?.message}"),
ElevatedButton(
onPressed: (){
Navigator.pop(context);
},
child: Text("Go back!"),
),
],
),
),
),
);
}
}
// 您可以将任何对象传递给 arguments 参数。
// 在此示例中,创建一个包含可自定义标题和消息的类。
// 这里是自定义需要的传递参数类型
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
我们既然已经写出来通用的路由onGenerateRoute的函数方法,那么我们可以兼顾通用性
单独抽出这段代码 (强烈推荐)
import 'package:flutter/material.dart';
import '../main.dart';
//定义路由列表
final Map<String,Function> routes={
"/":(context) => HomeScreen(),
"/second" : (context,{arguments}) => SecondScreen(arguments: arguments),
//未来只需要在这儿新增路由和方法就可以了
};
//定义通用的onGenerateRoute
var onGenerateRoute = (RouteSettings settings){
//settings 中两个属性 name 表示路由名称 arguments 传递的参数若是无参则这个为null
//1.获取当前调用的路由名称
//2.找到我们上面定义的Map路由表中是否包含此路由,并取出方法
//3.取出对应的参数
//4.判断不为null表示找到了某个路由定义的方法
//5.判断setting中的 arguments 不为null
//6.返回要跳转的页面和传递参数
//7.使用我们获取到的Function 调用带参数的去传递参数
//8.否则使用我们获取到的Function 调用不带参数的
String? routeName = settings.name;
Function? pageContentBuilder = routes[routeName];
Object? args = settings.arguments;
if(pageContentBuilder != null){
if(args != null){
final Route route = MaterialPageRoute(builder: (context){
return pageContentBuilder(context,arguments:args);
});
return route;
}else{
return MaterialPageRoute(builder: (context) => pageContentBuilder(context));
}
}
};
在main.dart中使用
import 'package:flutter/material.dart';
import 'package:myflutterapp/route/routes.dart';
void main() => runApp( MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//使用通用的onGenerateRoute
onGenerateRoute: onGenerateRoute,
title: 'Navigation with Arguments',
home: const HomeScreen(),
);
}
}