Flutter - 路由管理 - 01 - 基础使用

参考 book.flutterchina.club/chapter2/fl…

1. 示例

路由(Route)在移动开发中通常指页面(Page),在Android中通常指一个Activity。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。这和原生开发类似,无论是Android还是iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: MainRoute(),
    );
  }
}

class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("主页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //导航到新路由
              Navigator.push(context, MaterialPageRoute(builder: (context) {
                return SecondRoute();
              }));
            },
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context);
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

复制代码

MaterialPageRoute继承自PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。MaterialPageRouteMaterial组件库的一个Widget,它可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画:

对于Android,当打开新页面时,新的页面会从屏幕底部滑动到屏幕顶部;当关闭页面时,当前页面会从屏幕顶部滑动到屏幕底部后消失,同时上一个页面会显示到屏幕上。 对于iOS,当打开页面时,新的页面会从屏幕右侧边缘一致滑动到屏幕左边,直到新页面全部显示到屏幕上,而上一个页面则会从当前屏幕滑动到屏幕左侧而消失;当关闭页面时,正好相反,当前页面会从屏幕右侧滑出,同时上一个页面会从屏幕左侧滑入。 下面我们介绍一下MaterialPageRoute构造函数的各个参数的意义:

  MaterialPageRoute({
    WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  })
复制代码

builder是一个WidgetBuilder类型的回调函数,它的作用是构建路由页面的具体内容,返回值是一个widget。我们通常要实现此回调,返回新路由的实例。

settings包含路由的配置信息,如路由名称、是否初始路由(首页)。

maintainState:默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainStatefalse

fullscreenDialog表示新的路由页面是否是一个全屏的模态对话框,在iOS中,如果fullscreenDialogtrue,新页面将会从屏幕底部滑入(而不是水平方向)。

2. Navigator

Navigator是一个路由管理的widget,它通过一个栈来管理一个路由widget集合。通常当前屏幕显示的页面就是栈顶的路由。Navigator提供了一系列方法来管理路由栈,在此我们只介绍其最常用的两个方法:

1. Future push(BuildContext context, Route route)

将给定的路由入栈(即打开新的页面),返回值是一个Future对象,用以接收新路由出栈(即关闭)时的返回数据。

2. bool pop(BuildContext context, [ result ])

将栈顶路由出栈,result为页面关闭时返回给上一个页面的数据。

Navigator还有很多其它方法,如Navigator.replaceNavigator.popUntil等,详情请参考API文档或SDK源码注释,在此不再赘述。

3. 实例方法

Navigator类中第一个参数为context的静态方法都对应一个Navigator的实例方法, 比如Navigator.push(BuildContext context, Route route)等价于Navigator.of(context).push(Route route) ,下面命名路由相关的方法也是一样的。

3. 命名路由

​ 命名路由(Named Route)即给路由起一个名字,然后可以通过路由名字直接打开新的路由。这为路由管理带来了一种直观、简单的方式。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      //home: MainRoute(),
      //注册路由表
      routes: {
          /// '/'是特殊地址,第一个页面
        "/" :(context) => MainRoute(),
        "new_page": (context) => SecondRoute(),
      },
    );
  }
}

class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("主页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () async {
              //导航到新路由
              var result = await Navigator.pushNamed(context, "new_page");
              debugPrint("返回:$result");
            },
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context, "结束");
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}
复制代码

命名路由的最大优点是直观,我们可以通过语义化的字符串来管理路由。

但其有一个明显的缺点:不能直接传递路由参数。假设SecondRoute,需要接受一个字符串参数tip,然后再在屏幕中心将tip的内容显示出来。因为命名路由需要提前注册到路由表中,所以就无法动态修改tip参数。但是后面的版本已经支持了参数,看下面

命名路由参数

在Flutter最初的版本中,命名路由是不能传递参数的,后来才支持了参数;下面展示命名路由如何传递并获取路由参数:

我们先注册一个路由:

 routes:{
   "new_page": (context) => SecondRoute(),
  } ,
复制代码

在打开路由时传递参数

Navigator.of(context).pushNamed("new_page", arguments: "hi");
复制代码

在路由页通过RouteSetting对象获取路由参数:

class SecondRoute extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    //获取路由参数  
    var args=ModalRoute.of(context).settings.arguments
    //...省略无关代码
  }
}
复制代码

2. 自定义路由切换动画

Material库中提供了MaterialPageRoute,它在Android上会上下滑动切换。如果想自定义路由切换动画,可以使用PageRouteBuilder

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: MainRoute(),
    );
  }
}

class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("主页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () async {
              //导航到新路由
              var result = await Navigator.push(
                context,
                PageRouteBuilder(
                  ///动画时间
                  transitionDuration: Duration(milliseconds: 500),
                  pageBuilder: (BuildContext context, Animation animation,
                      Animation secondaryAnimation) {
                    ///平移
                    return SlideTransition(
                      ///Tween:在补间动画中,定义开始点结束点
                      position: new Tween<Offset>(
                        begin: const Offset(1.0, 0.0),
                        end: const Offset(0.0, 0.0),
                      ).animate(animation),
                      child: SecondRoute(),
                    );
                  },
                ),
              );
              debugPrint("返回:$result");
            },
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context, "结束");
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

复制代码

同时我们也可以对动画进行组合

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: MainRoute(),
    );
  }
}

class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("主页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () async {
              //导航到新路由
              var result = await Navigator.push(
                context,
                PageRouteBuilder(
                  ///动画时间
                    transitionDuration: Duration(milliseconds: 500),
                    pageBuilder: (BuildContext context, Animation animation,
                        Animation secondaryAnimation) {
                      ///透明渐变与旋转
                      return new FadeTransition(
                        opacity: animation,
                        child: new RotationTransition(
                          turns: new Tween<double>(begin: 0.5, end: 1.0)
                              .animate(animation),
                          child: SecondRoute(),
                        ),
                      );
                    },),
              );
              debugPrint("返回:$result");
            },
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context, "结束");
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

复制代码

3. 注意点

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text("主页"),
        ),
        body: Column(
          children: <Widget>[
            Text("第一个页面"),
            RaisedButton(
              onPressed: ()  {
                  ///Navigator.push内部其实就是 Navigator.of(context).push
                  Navigator.of(context).push(MaterialPageRoute(builder: (_){
                    return new SecondRoute();
                  }));
              },
              child: Text("进入第二页"),
            )
          ],
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context);
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

复制代码

这段代码运行会出现错误:

​ 问题关键点在于Navigator operation requested with a context that does not include a Navigator.(导航操作请求使用了不包含Navigator的上下文context)

Navigator实际上也是一个Widget,这个异常出现在Navigator.of(context)路由器的获取上,而这句代码会从当前的context的父级一层层向上去查找一个Navigator,我们当前传递的context就是MyApp,它的父级是root——UI根节点。Navigator这个widget的并不是由root创建的,因此在root下一级的上下文中无法获得Navigator

在之前所有的路由案例中,我们的上下文是MainRoute,它的父级是MaterialApp。MaterialApp内部就会创建一个Navigator。

MaterialApp->_MaterialAppState->WidgetsApp->_WidgetsAppState

​ 所以问题就在于,Navigator需要通过MaterialApp或者它孩子的上下文。

1. 解决一

按照此笔记最开始的正常路由演示案例来进行修改。

2. 解决二

使用Builder

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text("主页"),
        ),
        body: Column(
          children: <Widget>[
            Text("第一个页面"),
            ///
            Builder(builder: (context){
              return RaisedButton(
                onPressed: ()  {
                  Navigator.of(context).push(MaterialPageRoute(builder: (_){
                    return new SecondRoute();
                  }));
                },
                child: Text("进入第二页"),
              );
            })
          ],
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context);
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

复制代码

​ 使用Builder嵌套,Builder的参数可以看成一个回调,接收自身的context并返回布局配置。现在路由是从Builder的父亲开始查找啦,自然能找到Navigator。

3. 解决三

使用navigatorKey

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        ///指定路由器widget的key
      navigatorKey: navigatorKey,
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text("主页"),
        ),
        body: Column(
          children: <Widget>[
            Text("第一个页面"),
            RaisedButton(
              onPressed: ()  {
                ///输出Navigator
                debugPrint(navigatorKey.currentWidget.runtimeType.toString());
                navigatorKey.currentState.push(MaterialPageRoute(builder: (_){
                  return new SecondRoute();
                }));
              },
              child: Text("进入第二页"),
            )
          ],
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context);
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

复制代码

​ 在创建Navigator的时候,会给一个key,这个key可以看成一个Widget的id。这里的**_navigator就是我们指定的navigatorKey**(如果我们没指定,会给默认值的,所以不要疑惑不指定是不是就不创建Navigator了)。而通过这个key,就能够获得这个Navigator。直接获得了路由自然不需要再去查找了!

转载于:https://juejin.im/post/5d00f8a6f265da1bd260e511

猜你喜欢

转载自blog.csdn.net/weixin_33819479/article/details/93164402
今日推荐