路由管理

在 Flutter 中,页面之间的跳转是通过 Route 和 Navigator 来管理的:

  • Route 是页面的抽象,主要负责创建对应的界面,接收参数,响应 Navigator 打开和关闭;
  • 而 Navigator 则会维护一个路由栈管理 Route,Route 打开即入栈,Route 关闭即出栈,还可以直接替换栈内的某一个 Route。

根据是否需要提前注册页面标识符,Flutter 中的路由管理可以分为两种方式:

  • 基本路由。无需提前注册,在页面切换时需要自己构造页面实例。
  • 命名路由。需要提前注册页面标识符,在页面切换时通过标识符直接打开新的路由。

基本路由

要导航到一个新的页面,我们需要创建一个 MaterialPageRoute 的实例,调用 Navigator.push 方法将新页面压到堆栈的顶部

MaterialPageRoute 是一种路由模板,定义了路由创建及切换过渡动画的相关配置,可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画。

如果我们想返回上一个页面,则需要调用 Navigator.pop 方法从堆栈中删除这个页面.
**
下面的代码演示了基本路由的使用方法:在第一个页面的按钮事件中打开第二个页面,并在第二个页面的按钮事件中回退到第一个页面:

  1. class FirstScreen extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return RaisedButton(
  5. //打开页面
  6. onPressed: ()=> Navigator.push(context, MaterialPageRoute(builder: (context) => SecondScreen()));
  7. );
  8. }
  9. }
  10. class SecondPage extends StatelessWidget {
  11. @override
  12. Widget build(BuildContext context) {
  13. return RaisedButton(
  14. // 回退页面
  15. onPressed: ()=> Navigator.pop(context)
  16. );
  17. }
  18. }

命名路由

Flutter 提供了另外一种方式来简化路由管理,即命名路由。我们给页面起一个名字,然后就可以直接通过页面名字打开它了。

要想通过名字来指定页面切换,我们必须先给应用程序 MaterialApp 提供一个页面名称映射规则,即路由表 routes,这样 Flutter 才知道名字与页面 Widget 的对应关系。

路由表实际上是一个 Map,其中 key 值对应页面名字,而 value 值则是一个 WidgetBuilder 回调函数,我们需要在这个函数中创建对应的页面。而一旦在路由表中定义好了页面名字,我们就可以使用 Navigator.pushNamed 来打开页面了。

下面的代码演示了命名路由的使用方法:在 MaterialApp 完成了页面的名字 second_page 及页面的初始化方法注册绑定,后续我们就可以在代码中以 second_page 这个名字打开页面了:

  1. MaterialApp(
  2. ...
  3. //注册路由
  4. routes:{
  5. "second_page":(context)=>SecondPage(),
  6. },
  7. );
  8. //使用名字打开页面
  9. Navigator.pushNamed(context,"second_page");

打开了一个不存在的路由会怎么办?

  • 对用户进行友好的错误提示,比如跳转到一个统一的 NotFoundScreen 页面,也方便我们对这类错误进行统一收集、上报。
  • 在注册路由表时,Flutter 提供了 UnknownRoute 属性,我们可以对未知的路由标识符进行统一的页面跳转处理。

下面的代码演示了如何注册错误路由处理。和基本路由的使用方法类似,我们只需要返回一个固定的页面即可。

  1. MaterialApp(
  2. ...
  3. //注册路由
  4. routes:{
  5. "second_page":(context)=>SecondPage(),
  6. },
  7. //错误路由处理,统一返回UnknownPage
  8. onUnknownRoute: (RouteSettings setting) => MaterialPageRoute(builder: (context) => UnknownPage()),
  9. );
  10. //使用错误名字打开页面
  11. Navigator.pushNamed(context,"unknown_page");

页面参数

为了解决不同场景下目标页面的初始化需求,Flutter 提供了路由参数的机制,可以在打开路由时传递相关参数,在目标页面通过 RouteSettings 来获取页面参数。

下面的代码演示了如何传递并获取参数:使用页面名称 second_page 打开页面时,传递了一个字符串参数,随后在 SecondPage 中,我们取出了这个参数,并将它展示在了文本中。

  1. //打开页面时传递字符串参数
  2. Navigator.of(context).pushNamed("second_page", arguments: "Hey");
  3. class SecondPage extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. //取出路由参数
  7. String msg = ModalRoute.of(context).settings.arguments as String;
  8. return Text(msg);
  9. }
  10. }

除了页面打开时需要传递参数,对于特定的页面,在其关闭时,也需要传递参数告知页面处理结果。

Flutter 也提供了返回参数的机制。在 push 目标页面时,可以设置目标页面关闭时监听函数,以获取返回参数;而目标页面可以在关闭路由时传递相关参数。

下面的代码演示了如何获取参数:在 SecondPage 页面关闭时,传递了一个字符串参数,随后在上一页监听函数中,我们取出了这个参数,并将它展示了出来。

  1. class SecondPage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return Scaffold(
  5. body: Column(
  6. children: <Widget>[
  7. Text('Message from first screen: $msg'),
  8. RaisedButton(
  9. child: Text('back'),
  10. //页面关闭时传递参数
  11. onPressed: ()=> Navigator.pop(context,"Hi")
  12. )
  13. ]
  14. ));
  15. }
  16. }
  17. class _FirstPageState extends State<FirstPage> {
  18. String _msg='';
  19. @override
  20. Widget build(BuildContext context) {
  21. return new Scaffold(
  22. body: Column(children: <Widget>[
  23. RaisedButton(
  24. child: Text('命名路由(参数&回调)'),
  25. //打开页面,并监听页面关闭时传递的参数
  26. onPressed: ()=> Navigator.pushNamed(context, "third_page",arguments: "Hey").then((msg)=>setState(()=>_msg=msg)),
  27. ),
  28. Text('Message from Second screen: $_msg'),
  29. ],),
  30. );
  31. }
  32. }