Flutter中的路由,通俗地讲就是页面跳转。在Flutter中通过 Navigator 组件管理路由导航。
Flutter中给我们提供了两种配置路由跳转的方式:基本路由和命名路由。
今天我们先来聊聊基本路由。
基本路由
首先我们创建一个 Searchpage 页面:
import 'package:flutter/material.dart';
class Searchpage extends StatelessWidget {
final String info;//用于路由传值
const Searchpage({Key key, this.info="默认值"}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(//在最底层采取Scaffold组件
appBar: AppBar(
title: Text("搜索页面"),
),
body: Text("Search Page!传递过来的参数值是:${this.info}"),
floatingActionButton: FloatingActionButton(
onPressed: (){
//返回上一级页面
Navigator.of(context).pop();
},
child: Text("back"),
),
);
}
}
然后在 Category 页面中引入SearchPage.dart,并新增一个按钮执行页面跳转。
import 'package:flutter/material.dart';
import '../SearchPage.dart';//引入其他页面
class Category extends StatefulWidget {
Category({Key key}) : super(key: key);
_CategoryState createState() => _CategoryState();
}
class _CategoryState extends State<Category> {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text("分类页面"),
RaisedButton(
child: Text("搜索页面"),
onPressed: (){
//普通路由跳转
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Searchpage(info: "666",),
)
);
},
)
],
);
}
}
关于上述代码,有以下几点需要说明。
1,新增一个页面的时候,默认是没有主题样式的,现阶段我们基本是采取Scaffold页面主题样式。也就是说,当新建一个跳转页面的时候,我们需要在最底层采取Scaffold组件,如下所示:
2,普通路由执行跳转页面的关键代码如下:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Searchpage(info: "666",),
)
);
3,普通路由也是可以传值的,我们只需要在需要跳入的页面新增一个属性,然后在跳入之前将给该参数赋值即可。对应代码如下:
//Searchpage定义
class Searchpage extends StatelessWidget {
final String info;//用于路由传值
const Searchpage({Key key, this.info="默认值"}) : super(key: key);
@override
Widget build(BuildContext context) {
......
}
//普通路由跳转
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Searchpage(info: "666",),//页面跳转并且传值
)
);
4,由A页面跳转到B页面后,B页面会自动有返回按钮以及返回操作。我们也可以自定义一个返回按钮来演示一下返回操作。Scaffold组件有一个浮动按钮的属性,我们对该属性直接配置来定义返回按钮,代码如下:
floatingActionButton: FloatingActionButton(
onPressed: (){
//返回上一级页面
Navigator.of(context).pop();
},
child: Text("back"),
)
最后,本文示例代码的演示效果如下:
命名路由
上文中介绍了Flutter中的普通路由,在小项目中使用普通路由是比较合适的,但是在一些大型商业项目中,我们最好还是统一管理路由,即使用命名路由。
我们先通过一个小例子来了解一下命名路由的大致流程:
第1步,在根组件 MaterialApp 中配置路由信息:
//main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Tabs(),
//配置路由信息
routes: {
"/search": (context) => Searchpage(),
},
);
}
}
第2步,在合适的场景下,采用 Navigator.pushNamed 进行路由跳转:
Navigator.pushNamed(context, "/search");
了解了命名路由的基本使用之后,我们再来看看命名路由如何进行传值。
第1步,在根组件中配置路由:
import 'package:flutter/material.dart';
import 'package:flutter_app_google/pages/SearchPage.dart';
import 'pages/tabs/Tabs.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
//配置命名路由信息
final routes = {
//如果需要传参,那么在配置的时候加上{arguments};如果不需要传参,则不用加{arguments}
"/search": (context, {arguments}) => Searchpage(arguments: arguments,),
};
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Tabs(),
//统一处理命名路由
onGenerateRoute: (RouteSettings settings) {
final String name = settings.name;
final Function pageContentBuilder = this.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;
}
}
});
}
}
第2步,如果所要跳入页面需要传递参数过来的话,那么就需要在需要跳入的页面中声明参数信息。
import 'package:flutter/material.dart';
class Searchpage extends StatelessWidget {
final arguments;//用于接收命名路由传递过来的参数值
const Searchpage({Key key, this.arguments}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(//在最底层采取Scaffold组件
appBar: AppBar(
title: Text("搜索页面"),
),
//获取命名路由传递过来的参数值
body: Text("Search Page!传递过来的参数值是:${arguments != null ? arguments['info'] : '默认值'}"),
floatingActionButton: FloatingActionButton(
onPressed: (){
//返回上一级页面
Navigator.of(context).pop();
},
child: Text("back"),
),
);
}
}
第3步,在何时的时刻进行命名路由跳转传值。
//命名路由跳转传值
Navigator.pushNamed(context, "/search", arguments: {"info":"777"});
现在我们已经了解了命名路由传值该怎么去操作了,但是此时的代码看起来很乱,如果后期需要管理的命名路由多了,那么如果不做代码分离,而是直接像上面那样写的话,就会造成代码堆积,可读性变差,也不利于后期维护。所以,我们有必要做代码分离,那么该如何去做呢?
第1步,在lib文件夹下新建一个routes文件夹,然后在routes文件夹下新增一个 Routes.dart 文件,如下:
第2步,将命名路由配置的相关代码都分离到Routes.dart中:
//Routes.dart
import 'package:flutter/material.dart';
import 'package:flutter_app_google/pages/SearchPage.dart';
//配置命名路由信息
final routes = {
//如果需要传参,那么在配置的时候加上{arguments};如果不需要传参,则不用加{arguments}
"/search": (context, {arguments}) => Searchpage(
arguments: arguments,
),
};
//统一处理命名路由
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;
}
}
};
第3步,在main.dart中引入Routes.dart,并且使用暴露出来的接口
import 'package:flutter/material.dart';
import 'package:flutter_app_google/routes/Routes.dart' as prefix0;
import 'pages/tabs/Tabs.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Tabs(),
//统一处理命名路由
onGenerateRoute: prefix0.onGenerateRoute);
}
}
现在我已经将命名路由的配置代码分离到 Routes.dart 文件中了,这样一分离,main.dart中的代码就简洁多了。其实,我们还可以对main.dart中的代码进一步进行优化,也就是说,我们还可以将 Tabs 这个主页面也通过命名路由进行管理,代码如下:
//Routes.dart
//配置命名路由信息
final routes = {
//如果需要传参,那么在配置的时候加上{arguments};如果不需要传参,则不用加{arguments}
"/": (context) => Tabs(),
"/search": (context, {arguments}) => Searchpage(arguments: arguments),
};
//main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// home: Tabs(),
initialRoute: "/",//初始化的时候加载的路由
//统一处理命名路由
onGenerateRoute: prefix0.onGenerateRoute);
}
}
最后,我们再来看看有状态的组件如何进行路由传值:
import 'package:flutter/material.dart';
class DetailPage extends StatefulWidget {
final Map arguments;//1,定义传值参数
DetailPage({Key key, this.arguments}) : super(key: key);//2,重新写构造函数
_DetailPageState createState() => _DetailPageState(arguments: arguments);//3,将参数值传递给_DetailPageState
}
class _DetailPageState extends State<DetailPage> {
Map arguments;//4,定义传值参数
_DetailPageState({this.arguments});//5,重新写构造函数
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("详情页面")),
body: Text("hulalaDetail!~${this.arguments["name"]}")//6,获取到传递过来的值
);
}
}
这里的DetailPage是一个StatefulWidget类型的组件,我们按照上述123456的步骤就可以完成一个可变状态组件的路由传值。
总结
关于命名路由使用的前前后后,我在该文中都做了详细总结,并且做了代码分离,后续在项目中,我们可以参考该文进行命名路由的配置。
替换路由
前文中我们了解了Flutter中的普通路由和命名路由。今天我们接着来聊聊Flutter中的替换路由和如何返回到跟路由。
首先,我们先来考虑一个场景:APP的注册页面,可能要分好几步才能注册成功,比如输入手机号——输入验证码——输入密码,然后注册成功,注册成功之后跳转到登录页面,在登录页面登陆成功之后返回到主页面。
如果按照我们之前了解的知识,页面的跳转都是通过 Navigator.pushNamed 实现的,这样的话,如果我们采用 Navigator.pop(context) 返回页面的话,就只能返回上一页面。比如,我们采用 Navigator.pushNamed 按下面的顺序一级一级跳转,然后采用 Navigator.pop(context) 返回页面,这样的话,LoginPage只能返回到RegistThirdPage,RegistThirdPage只能返回到RegistSecondPage,RegistSecondPage只能返回到RegistFirstPage,RegistFirstPage只能返回到Setting。
Setting.dart -> RegistFirstPage.dart -> RegistSecondPage.dart -> RegistThirdPage.dart -> LoginPage.dart
| |
^ |
|------------------------------<---------------<-------------<---------------<------------|
如果是按照这样的话,我们在登陆成功以后就不能直接返回到首页面了。如果我们想在登陆成功之后直接返回到首页面,那么可以采用替换路由 Navigator.pushReplacementNamed 的方式进行页面的跳转:
//在Setting.dart页面跳转到注册RegistFirstPage.dart页面
Navigator.pushNamed(context, "/registFirst");
//在RegistFirstPage.dart页面跳转到RegistSecondPage.dart页面
Navigator.pushReplacementNamed(context, "/registSecond");
//在RegistSecondPage.dart页面跳转到RegistThirdPage.dart页面
Navigator.pushReplacementNamed(context, "/registThird");
//在RegistThirdPage.dart页面跳转到LoginPage.dart页面
Navigator.pushReplacementNamed(context, "/login");
//在LoginPage.dart页面返回到Setting.dart页面
Navigator.pop(context);
替换路由 Navigator.pushReplacementNamed 的作用是,用即将跳入的页面来替换当前页面在路由栈中的位置。比如上例中,在 Setting.dart 页面中使用命名路由的方式跳转到 RegistFirstPage.dart 页面,在 RegistFirstPage.dart 页面则使用替换路由的方式跳转到 RegistSecondPage.dart 页面,那么在 RegistSecondPage.dart 页面中使用 Navigator.pop(context) 返回,返回到的是Setting.dart页面,而不是 RegistFirstPage.dart 页面。同理,在上例中的RegistThirdPage.dart、LoginPage.dart中,点击返回按钮,使用 Navigator.pop(context) 方式返回的时候,返回到的都是 Setting.dart 页面。
返回到根路由
上面我们了解了替换路由如何使用,以及如果通过替换路由返回到主页面。那么在绝大部分情况下,我们在页面跳转的时候,还是采取普通命名路由跳转的方式(而不是采取替换路由),此时,在跳转到多级页面之后,如何一键返回到主页面呢?
采用,我们可以实现该效果,代码如下:
//LoginPage.dart
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (context) => Tabs(currentIndex: 2,)), (route)=>route==null);
需要注意的是,为了能够控制跳入Tabs的第几个页面(首页还是分类还是设置),我们需要给Tabs组件增加一个控制展示第几个页面的属性 currentIndex ,
//Tabs.dart
class Tabs extends StatefulWidget {
final int currentIndex;//1,定义传值参数
Tabs({Key key, this.currentIndex=0}) : super(key: key);//2,重新写构造函数
_TabsState createState() => _TabsState(this.currentIndex);//3,将参数值传递给_TabsState
}
class _TabsState extends State<Tabs> {
int _tabIndex;//4,定义传值参数
List _pageList = [
HomePage(),
Category(),
SettingPage()
];
_TabsState(this._tabIndex);//5,重新写构造函数
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AppBarTitle"),
),
//6,获取到传递过来的值,并使用
body: this._pageList[this._tabIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
iconSize: 30,
fixedColor: Colors.pink,
currentIndex: _tabIndex,
onTap: (int index){
setState(() {
_tabIndex = index;
});
},
items: [
...
],
),
);
}
}
以上。