版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!
情感语录:别说什么火克木,水克土;你只有努力,只有成功了才能克天、克地、克人生。
欢迎来到本章节,上一章节我们讲了StatelessWidget
和StatefulWidget
组件还记得吗?知识点回顾 戳这里 Flutter基础第四章
本章节主要讲解路由
,Flutter 中的路由通俗的讲就是页面跳转。在Android原生中是通过意图Intent
来进行跳转的;在 Flutter 中通过 Navigator 组件管理路由导航;并提供了管理堆栈的方法。如:Navigator.of(context).push(跳转到目标页面) 和 Navigator.of(context).pop(退出目标页面,既返回到上一个页面)
本章简要:
1、普通路由、普通路由传值、命名路由、命名路由传值
2、 pushReplacementNamed路由替换 、pushNamedAndRemoveUntil返回到根路由
Flutter 中给我们提供了两种配置路由跳转的方式:普通路由 和 命名路由
一、普通路由和传值
1、普通路由实例:首先我们准备两个页面,一个是主页,主页上有按钮,点击按钮然后跳转到第二个界面。新建一个SecondPage.dart
页面用于验证主页是否能跳转过来。
import 'package:flutter/material.dart';
class SecondPage extends StatelessWidget {
String value;
//构造函数接收一个可选命名参数
SecondPage({this.value = ""});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第二个界面"),
),
body: Center(
child: Text("这是第二个界面${this.value}"),
),
);
}
}
2、在主页中通过普通路由:Navigator.of(context).push(MaterialPageRoute(builder: (context) => 目标页面 ));
进行跳转
import 'package:flutter/material.dart';
import 'page/SecondPage.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text("呆萌"),
),
body: HomePage()));
}
}
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
RaisedButton(
child: Text("跳转到第二个界面"),
onPressed: () {
//路由跳转
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondPage()));
},
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary),
SizedBox(
height: 10,
),
RaisedButton(
child: Text("传值跳转到第二个界面"),
onPressed: () {
// 通过构造函数传值,上面的写法也可以换成这种方式 去掉of
Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage(value: "----->传值跳转",)),
);
},
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary),
],
),
);
}
}
MaterialPageRoute 创建路由,它是一种模态路由,可以通过平台自适应的过渡效果来切换屏幕。默认情况下,当一个模态路由被另一个替换时,上一个路由将保留在内存中,如果想释放所有资源,可以将 maintainState
设置为 false
,下面我们来看上面代码效果:
可以看到两种跳转方式都没问题,且在跳转时通过构造函数成功将值(----->传值跳转
)传入到了第二个界面;细心的你有没有发现一个神奇的地方,在第二个界面我们并没有加返回按钮,怎么自己就有出来了一个呢?其实Flutter中会检测到你是通过路由跳页面的情况,Scaffold
控件会自动在 AppBar 上添加一个返回按钮,点击该按钮会调用 Navigator.pop
即退出该页面;如果在页面的其他地方也有退出按钮,在添加事件后,手动调用也是一样可以实现的。
二、命名路由和传值
1、命名路由跳转
命名路由应该是开发中用的最多的一种方式,使用起来虽然比普通路由相对复杂一点,但它可以做到统一管理,便于维护。路由名称通常使用路径结构:/xxx/xxx/目标页
,主页默认为 '/' (启动第一个加载的页面)
。既:Navigator.pushNamed(context,/xxx/xxx/目标页)
方式调用进行跳转 。创建 MaterialApp
时可以指定 routes
参数,该参数是一个映射路由名称和构造器的 Map。MaterialApp
使用此映射为导航器的 onGenerateRoute 回调参数提供路由。下面我们来将上面例子简单修改成命名路由。
import 'package:flutter/material.dart';
import 'page/SecondPage.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text("呆萌"),
),
body: HomePage()),
//在MaterialApp 中配置路由
routes: {
'/SecondPage': (context) => SecondPage(),
});
}
}
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
RaisedButton(
child: Text("跳转到第二个界面"),
onPressed: () {
//命名路由跳转
Navigator.pushNamed(context, '/SecondPage');
},
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary),
],
),
);
}
}
第二页面也稍做修改:
import 'package:flutter/material.dart';
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
//这里也添加一个返回按钮。
floatingActionButton: FloatingActionButton(
child: Text('返回'),
onPressed: (){
// 返回上一个页面
Navigator.of(context).pop();
},
),
appBar: AppBar(
title: Text("第二个界面"),
),
body: Center(
child: Text("这是第二个界面"),
),
);
}
}
通过命名路由也能实现跳转。但这里有两个注意点需要记住:
1、必须在MaterialApp
中进行路由管理配置。
2、路由管理配置的目标页面名称必须和跳转时写的一致,否则不能跳转。既routes
下的'/SecondPage'
必须和Navigator.pushNamed
中填写的一致。这里建议可以使用全局常量进行配置优化。
2、命名路由传值
细心的你可能发现一个问题,命名路由是通过Navigator.pushNamed
方式跳转,于是这里再也没有构造函数进行利用传值,那怎么办?没办法,先看下官方文档,官方文档有这么一段代码:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// Provide a function to handle named routes. Use this function to
// identify the named route being pushed, and create the correct
// Screen.
onGenerateRoute: (settings) {
// If you push the PassArguments route
if (settings.name == PassArgumentsScreen.routeName) {
// Cast the arguments to the correct type: ScreenArguments.
final ScreenArguments args = settings.arguments;
// Then, extract the required data from the arguments and
// pass the data to the correct screen.
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
},
title: 'Navigation with Arguments',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// A button that navigates to a named route that. The named route
// extracts the arguments by itself.
RaisedButton(
child: Text("Navigate to screen that extracts arguments"),
onPressed: () {
// When the user taps the button, navigate to the specific route
// and provide the arguments as part of the RouteSettings.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ExtractArgumentsScreen(),
// Pass the arguments as part of the RouteSettings. The
// ExtractArgumentScreen reads the arguments from these
// settings.
settings: RouteSettings(
arguments: ScreenArguments(
'Extract Arguments Screen',
'This message is extracted in the build method.',
),
),
),
);
},
),
// A button that navigates to a named route. For this route, extract
// the arguments in the onGenerateRoute function and pass them
// to the screen.
RaisedButton(
child: Text("Navigate to a named that accepts arguments"),
onPressed: () {
// When the user taps the button, navigate to a named route
// and provide the arguments as an optional parameter.
Navigator.pushNamed(
context,
PassArgumentsScreen.routeName,
arguments: ScreenArguments(
'Accept Arguments Screen',
'This message is extracted in the onGenerateRoute function.',
),
);
},
),
],
),
),
);
}
}
// A Widget that extracts the necessary arguments from the ModalRoute.
class ExtractArgumentsScreen extends StatelessWidget {
static const routeName = '/extractArguments';
@override
Widget build(BuildContext context) {
// Extract the arguments from the current ModalRoute settings and cast
// them as ScreenArguments.
final ScreenArguments args = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text(args.title),
),
body: Center(
child: Text(args.message),
),
);
}
}
// A Widget that accepts the necessary arguments via the constructor.
class PassArgumentsScreen extends StatelessWidget {
static const routeName = '/passArguments';
final String title;
final String message;
// This Widget accepts the arguments as constructor parameters. It does not
// extract the arguments from the ModalRoute.
//
// The arguments are extracted by the onGenerateRoute function provided to the
// MaterialApp widget.
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),
),
);
}
}
// You can pass any object to the arguments parameter. In this example,
// create a class that contains both a customizable title and message.
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
官方文档这段代码看起来有点长哈,你仔细看其实发现不难理解,首先是在HomeScreen 中通过arguments
指定了要传的值,案例中是封装在ScreenArguments 对象中。然后在MaterialApp
中通过onGenerateRoute()
进行参数提取,然后路由名进行判断,如果是指定的路由名通过settings
将传入的值进行提取,接着MaterialPageRoute
通过构建把值转入。
你可能又有问题要问了,上面代码确实不难,但有一个问题,如果有很多个页面我想通过命名路由传值,是不是都要去做分别判断然后再处理不同类型的值提取呢?是否可以对官方文档这段代码进行优化呢?我想要的是一次处理就好! 咦....这个问题过分了啊!!我想想, 嗯.................呜....... 当然还是可以啦。下面我们来看看吧咳咳......重点:
为了方便统一管理,我们将路由单独抽离出来,先新建一个route文件夹(名字随意见名知意即可),然后新建Routes.dart文件,然后写这么一段代码:
import 'package:flutter/material.dart';
import 'package:flutter_learn/page/SecondPage.dart';
//配置路由
final routes={
//如有多个,请在这里添加即可。
'/SecondPage':(context,{arguments})=>SecondPage(arguments:arguments),
};
//优化后
// ignore: strong_mode_top_level_function_literal_block
var onGenerateRoute=(RouteSettings settings) {
// 统一处理
final String name = settings.name;
final Function pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
if (settings.arguments != null) {
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
}else{
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context));
return route;
}
}
};
咦.........看不懂?没关系,这段代码拿过去用即可,感觉很牛逼的样,不试试怎么知道呢? 下面我们来简单修改下主页和第二个页面:
import 'package:flutter/material.dart';
import 'package:flutter_learn/route/Routes.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text("呆萌"),
),
body: HomePage()
),
//initialRoute: '/', //初始化的时候加载的路由
onGenerateRoute: onGenerateRoute);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
RaisedButton(
child: Text("跳转到第二个界面"),
onPressed: () {
//命名路由跳转 并传值
Navigator.pushNamed(context, '/SecondPage',arguments: {
"key":"哎呦,你来啦O(∩_∩)O"
});
},
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary),
],
),
);
}
}
上面代码中我们在MaterialApp
中引用了抽离出去的onGenerateRoute
,然后在点击按钮后通过Navigator.pushNamed
传入了一个arguments
参数,arguments其实就是个Map
类型,通过key,value传值即可。
第二个页面也需要处理下,我们在构造函数中接受下这个arguments
参数。
import 'package:flutter/material.dart';
class SecondPage extends StatelessWidget {
final arguments;
SecondPage({this.arguments});
@override
Widget build(BuildContext context) {
return Scaffold(
//这里也添加一个返回按钮。
floatingActionButton: FloatingActionButton(
child: Text('返回'),
onPressed: (){
// 返回上一个页面
Navigator.of(context).pop();
},
),
appBar: AppBar(
title: Text("第二个界面"),
),
body: Center(
child: Text("这是第二个界面 传值内容:"+arguments["key"]),
),
);
}
}
好了,运行尝试下效果吧:
哈哈,看到了吧,成功的将值哎呦,你来啦O(∩_∩)O
传入到了第二个界面。牛逼,手动 666.......... O(∩_∩)O
三、pushReplacementNamed路由替换 、pushNamedAndRemoveUntil 返回到根路由
上面我们讲了路由跳转,也提及和运用了使用路由返回到上一个界面Navigator.of(context).pop(); 或者 Navigator.pop(context);
如果有参数需要携带回去还可以使用Navigator.pop(context,"携带参数");
3.1 pushReplacementNamed
生活常常有这样一种场景,比如你打开某一个应用 首先进入了欢迎页
再然后进入了主页
当你点击主页返回按钮希望的是退出应用,而不是再次回到了欢迎页,此时 pushReplacementNamed 进行路由替换跳转就可以排上用场了,使用格式如下:
Navigator.of(context).pushReplacementNamed('/主页');
3.2 pushNamedAndRemoveUntil
假设现在有A、B、C三个界面,当用户从A界面依次进入到了C界面,再C界面想直接回到A界面那该怎么办呢?此时pushNamedAndRemoveUntil路由跳转就可以排上用场了,使用格式如下:
//返回根
Navigator.of(context).pushAndRemoveUntil(
new MaterialPageRoute(builder: (context) => A()),
(route) => route == null
);
在上面中讲到了可以通过pop
将值可以携带回上一个页面,那上一个页面怎么接收携带回来的这个值呢? 其实无论是在普通路由还是命名路由中我们都可以使用then
函数进行接收。
//命名路由跳转 并传值
Navigator.pushNamed(context, '/SecondPage',arguments: {
"key":"哎呦,你来啦O(∩_∩)O"
}).then((value){ //接收回传回来的值
print(value);
});
好了本章节的东西虽然不多也不是很难,但是在Flutter中极其重要,本章节的案例可能相对较少,尤其是第三大点所讲的东西并未给出演示效果和Demo。因为这个东西确实不难,稍微练习下即可,该章节的内容在后面章节中也会经常使用到,所以懒的练习的同学希望持续关注我哟,O(∩_∩)O 。谢谢大家观看,下章再会 !!!!
实例源码地址: https://github.com/zhengzaihong/flutter_learn