Flutter基础-061-Navigator路由导航方法实例

293 阅读9分钟

Flutter中提供了Navigator实现页面跳转功能,一个独立页面就是一个路由,因此称为路由导航。

有两种跳转方式:

  • 通过路由直接跳转,就是说想要跳转到Page,那么直接将Page当作参数传递进去就可以了(类似于安卓的intent直接跳转Activity)。
  • 通过命名路由参数跳转,就是说先将PageA注册到路由表内并起一个名字,然后其他页面通过这个名字进行跳转(类似于安卓的隐式跳转)。

Navigator API方法列表

通过路由直接跳转:

  • push 栈顶入栈一个新的路由
  • pushReplacement 将当前路由替换为一个新的路由
  • pushAndRemoveUntil 根据参数决定栈内路由清除多少,并入栈一个新路由。

通过命名路由跳转:

  • pushNamed 栈顶入栈一个新的路由
  • pushReplacementNamed 将当前路由替换为一个新的路由
  • pushNamedAndRemoveUntil 根据参数决定栈内路由清除多少,并入栈一个新路由

路由主动出栈:

  • pop 当前路由出栈
  • popAndPushNamed 当前路由出栈,入栈一个新路由
  • popUntil 从栈顶开始,逐个pop,直到参数中指定的路由为止
  • canPop 判断当前路由是否可以出栈,如果栈内只有当前路由,返回false,如果当前路由前面还有路由,返回true。也就是说栈底的路由,该方法返回false
  • maybePop 当前路由如能能出栈就出栈,如果不能就什么都不做。

路由删除:

  • removeRoute
  • removeRouteBelow

路由替换:

  • replace
  • replaceRouteBelow

路由判断方法:

  • of 获取当前context 的Navigator的实例.
  • 几乎所有的Navigtor方法实现都是调用of后,再调用具体方法的,如pop:
static bool pop<T extends Object>(BuildContext context, [ T result ]) {
    return Navigator.of(context).pop<T>(result);
  }

通过路由直接跳转

Navigator.push(BuildContext context, Route route)

描述:当前页面不变,跳转到一个新的页面。也就是栈顶入栈一个新的路由。 路由关系:

  • 当前路由顺序为A-B-C
  • 从C push D
  • 路由顺序为A-B-C-D
  • 如果在D页面,pop了,那么路由顺序为A-B-C

页面间传递参数:

  • 通过目标页面的构造方法传递参数
  • 可以从目标页面接收回传的参数

示例:页面跳转,传递参数给下一个页面,并获取下一个页面关闭后返回给当前页面的参数。 当前页面:

       RaisedButton(
              onPressed: () {
                // 接收一个Future的返回值,返回值是一个数组
                var result = Navigator.push(context, MaterialPageRoute(
                  builder: (BuildContext context){
                    return PageA("这是传递给PageA的参数");// 通过构造方法传递参数
                  }
                ));
                // 获取返回值
                result.then((response){
                  this.setState((){
                    text = response[0];
                  });
                });
              },
              child: Text("页面跳转至PageA,传递参数,并接收页面返回值"),
            ),

PageA:

       RaisedButton(
              child: Text("向上个页面回传值"),
              onPressed: (){
                Navigator.pop(context,["来自PageA的回传参数"]);// 往回传一个数组
                // Navigator.pop(context,"来自PageA的回传参数");// 往回传一个字符串
              },
            )
Navigator.pushReplacement(BuildContext context, Route route, { TO result })

路由关系:

  • 当前路由顺序为A-B-C
  • 从C pushReplacement D
  • 路由顺序为A-B-D
  • 如果在D页面pop了,那么路由顺序为A-B

参数关系:

  • 通过目标页面的构造方法传递参数
  • 页面已经不存在,无法从目标页面接收回传的参数
       RaisedButton(
              onPressed: () async {
                // 接收一个Future的返回值,返回值是一个数组
                var result = Navigator.pushReplacement(context, MaterialPageRoute(
                    builder: (BuildContext context){
                      return PageA("这是传递给PageA的参数");
                    }
                ));
                // 获取返回值
                result.then((response){
                  print("response===$response");
                  // 页面已经出栈,不可再次调用
//                  this.setState((){
//                    text = response[0];
//                  });
                });
              },
              child: Text("当前页面替换为PageA,可以传递参数,但无法接收页面返回值"),
Navigator.pushAndRemoveUntil((BuildContext context, Route newRoute, RoutePredicate predicate)

描述:打开一个新的页面,并根据predicate的值的情况来决定如何处理新页面之前的路由,predicate值得情况分三种:

  1. 如果给predicate传递一个方法,方法的返回值是false,那么删除新页面之前的所有路由。
  • 当前路由顺序为A-B-C-D-E
  • 从E pushAndRemoveUntil 页面F,并且predicate返回false
  • 路由顺序为F,A—E 全部出栈了。
  • 如果在F页面pop了,那么app回到桌面。
  1. 如果predicate传递一个方法,方法的返回值为true,那么新页面之前的路由保持不变。
  • 当前路由顺序为A-B-C-D-E
  • 从E pushAndRemoveUntil 页面F,并且predicate返回true
  • 路由顺序为A-B-C-D-E-F,A—E 全部不变。
  • 如果在F页面pop了,那么路由为:A-B-C-D-E。
  1. 如果predicate的值是ModalRoute.withName指定的一个命名路由名称,那么新页面和该指定的路由名称之间的路由全部删除。 路由关系:
  • 当前路由顺序为A-B-C-D-E
  • 从E pushAndRemoveUntil 页面F,并且predicate的值是ModalRoute.withName('C')。(此处有坑,后面再讲)
  • 路由顺序为A-B-C-F,D 、E 出栈。
  • 如果在F页面pop了,那么路由顺序为:A-B-C。

参数关系:

  • 通过目标页面的构造方法传递参数
  • 页面已经不存在,无法从目标页面接收回传的参数

示例,给predicate传入一个方法,返回值是false或true

      RaisedButton(
              onPressed: () async {
                // 接收一个Future的返回值,返回值是一个数组
                var result = Navigator.pushAndRemoveUntil(context,
                    MaterialPageRoute(builder: (BuildContext context) {
                  return PageA("这是传递给PageA的参数");
                }), 
               // predicate参数
               (route) {
                  print('route=$route}');
//                  return false;// PageA之前的路由全部删除
                  return true; // PageA之前的路由保持不变
                });
                // 获取返回值
                result.then((response) {
                  // 页面已经出栈,不可再次调用
//                  this.setState((){
//                    text = response[0];
//                  });
                });
              },

示例,给predicate参数传递ModalRoute.withName,实现步骤:

  1. 假设有T-A-B-C-D五个路由
  2. 在T页面push到A,需要设置settings: RouteSettings(name:"pagea"),相当于给A打了一个标签“pagea”,如下:
             Navigator.push(context,
                    MaterialPageRoute(settings: RouteSettings(name:"pagea"),builder: (BuildContext context) {
                  return PageA("这是传递给PageA的参数");
                }));
  1. 然后A-push-B-push-C,此时路由顺序为:T-A-B-C
  2. 在C页面 pushAndRemoveUntil 到D页面,同时指定ModalRoute.withName("pagea")。
             Navigator.pushAndRemoveUntil(context,
                    MaterialPageRoute(builder: (BuildContext context) {
                      return PageD("这是传递给PageD的参数");
                    }),
                  ModalRoute.withName('pagea')
                );
  1. 此时路由顺序为:T-A-D。
  2. 最重要的是settings: RouteSettings(name:"pagea") ,ModalRoute.withName(name)中的参数name,指的是注册的路由名字,也可以是RouteSetting设定的那么,如果既注册路由了又RouteSetting了,那么RouteSetting的优先。

通过命名路由跳转

首先需要将路由进行注册

在MaterialApp内注册路由

class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Good'),
      routes: {
        "mainpage": (context) => MainPage(),
        "navigatortest": (context) => NavigatorTest(),
        "pagea": (context) => PageA("aaa"),
        "pageb": (context) => PageB("bbb"),
        "pagec": (context) => PageC("cc"),
        "paged": (context) => PageD("dd"),
      },
    );
  }
}
路由跳转
  • Navigator.pushNamed(context, "paged", arguments: "111");//跳转到paged,并传递参数
  • Navigator.pushReplacementNamed(context, "paged",arguments: "111");//用paged替换掉当前路由,并传递参数
  • Navigator.pushNamedAndRemoveUntil(context, "paged", (route) { // return true; return false; }, arguments: "111");//跳转到paged,并根据第三个参数predicate的返回值决定新路由之前的路由的处理方式,为true保持不变,为false则新路由外的所有路由都pop掉。
Navigator.pushNamed(BuildContext context, String routeName, {Object arguments})

描述:与push方法作用一样,从当前页面打开一个新的页面,栈顶入栈一个路由,当前路由不变。

路由关系:

  • 当前路由顺序为A-B-C
  • 从C push D
  • 路由顺序为A-B-C-D
  • 如果在D页面,pop了,那么路由顺序为A-B-C

参数传递:

  • 在调用pushNamed方法时,有个可选命名参数arguments,通过此参数传递给下个页面数据。
  • 下个页面在build方法内获取参数,通过ModalRoute.of(context).settings.arguments接收到参数。
  • pushNamed方法的返回值也是一个Future,可以接收下个页面返回的数据。
RaisedButton(
              onPressed: () {
                var result = Navigator.pushNamed(context, "pagea", arguments: "111");
                result.then((response){
                  // 下个页面的返回值
                });
              },
              child: Text("pushNamed  打开新页面,传递参数和接收返回值"),
            ),

新路由接收参数 在build方法内获取参数,var args=ModalRoute.of(context).settings.arguments;

class _PageDState extends State<PageD> {
  @override
  Widget build(BuildContext context) {
// 接受参数
    var args=ModalRoute.of(context).settings.arguments;
    return Scaffold(
      appBar: AppBar(
        title: Text("PageD"),
        centerTitle: true,
      ),
      body: Center(,
    );
  }
}
Navigator.pushReplacementName()

Navigator.pushReplacementNamed 与Navigator.pushReplacement唯一区别是用注册的路由命名替换了new PageA()式的直接路由。其他使用方法是一样的

Navigator.pushNamedAndRemoveUntil()

Navigator.pushNamedAndRemoveUntil 与Navigator.pushAndRemoveUntil唯一区别是用注册的路由命名替换了new PageA()式的直接路由。其他使用方法是一样的

路由出栈

Navigator.pop(BuildContext context, [ T result ])

描述:关闭当前页面,并将参数传递给来时的页面。也就是,当前路由从栈顶出栈。

  • 当前路由顺序为A-B-C
  • 在C页面pop,那么路由为:A-B
  • 在pop时可以向来时的路由页面传递参数。
       RaisedButton(
              child: Text("向上个页面回传值"),
              onPressed: (){
                Navigator.pop(context,result);// 任何类型的参数
              },
            )
bool canPop(BuildContext context)

描述:判断当前路由是否可以出栈,如果栈内只有当前路由,返回false,如果当前路由前面还有路由,返回true。也就是说栈底的路由,该方法返回false。

               if(Navigator.canPop(context)){
                  Navigator.pop(context);
                }

如果canPop返回false,但是执行了pop方法,那么就会报错。

Navigator.maybePop(BuildContext context, [ T result ])

描述: 当前路由如能能出栈就出栈,如果不能就什么都不做。

  • 是pop的安全返回方式.
  • 参数作用于pop的一样。
Navigator.maybePop(context);
  • 相当于对pop外层加了一层canpop判断,等同于:
               if(Navigator.canPop(context)){
                  Navigator.pop(context);
                }
Navigator.popAndPushNamed<T extends Object, TO extends Object>(BuildContext context,String routeName, {TO result,Object arguments,})

描述:当前路由出栈,栈顶入栈一个新路由。

路由顺序:

  • 当前路由顺序为:A-B-C
  • 在C popAndPushNamed 路由D
  • 那么路由顺序为: A-B-D
       RaisedButton(
              onPressed: () {
                Navigator.popAndPushNamed(context, "paged");
              },
              child: Text("popAndPushNamed"),
            ),
Navigator.popUntil(BuildContext context, RoutePredicate predicate)

描述:栈顶路由逐个出栈,直至predicate指定的条件。 路由顺序:

  • 当前路由顺序为A-B-C-D-E
  • 在E popUntil(context,Module.withName('B')),withName内的名字可以是为路由注册的名字,或是为路由设置的RouteSetting。
  • 那么路由顺序为:A-B
         RaisedButton(
              onPressed: () {
                Navigator.popUntil(context,ModalRoute.withName('pagea'));
              },
              child: Text("popUntil"),
            ),

路由删除和替换

路由的删除和替换,通常是对站内已经存在的路由的处理,因此,想要删除一个路由,需要拿到对该路由的引用。 建立一个Map保存那些已经存在的路由:

class RouteMap {
  static Map<String,MaterialPageRoute> map = new Map();
// 保存路由
  static addRoute(String key,MaterialPageRoute route){
    map[key]=route;
  }
// 获取指定的路由
  static MaterialPageRoute getRoute(String key){
    return map[key];
  }
}

每次跳转到新的路由时,都将新的路由的引用保存在RouteMap中,如:

          RaisedButton(
              onPressed: (){
                MaterialPageRoute route = MaterialPageRoute(builder: (BuildContext context) {
                  return PageA("这是传递给PageA的参数");
                });
                RouteMap.addRoute("pagea", route);// 保存路由
                Navigator.push(context,route);
              },
              child: Text("add Route"),
            ),

我们假设路由的跳转顺序为:A-B-C-D-E-F 那么RouteMap中的值为:

RouteMap.addRoute("pageb", routeb);
RouteMap.addRoute("pagec", routec);
RouteMap.addRoute("paged", routed);
RouteMap.addRoute("pagee", routee);
RouteMap.addRoute("pagef", routef);
Navigator.removeRoute(BuildContext context, Route route)

描述:删除一个指定的路由,如果该路由存在于栈内正常删除,否则报错 路由顺序:

  • 假设当前路由顺序为:A-B-C-D-E-F
  • 在F页面,Navigator.removeRoute(context,RouteMap.getRoute("paged"));
  • 路由顺序为:A-B-C-E-F
       RaisedButton(
              onPressed: () {
                Navigator.removeRoute(context,RouteMap.getRoute("pagea"));
              },
              child: Text("removeRoute"),
            ),
Navigator.removeRouteBelow(BuildContext context, Route anchorRoute)

描述:给定一个路由,在路由表内删除其前一个路由

  • 假设当前路由顺序为:A-B-C-D-E-F
  • 在F页面,removeRouteBelow(context,RouteMap.getRoute("paged"));
  • 路由顺序为:A-B-D-E-F
RaisedButton(
              onPressed: () {
                Navigator.removeRouteBelow(context,RouteMap.getRoute("paged"));
              },
              child: Text("removeRouteBelow"),
            ),
Navigator.replace(BuildContext context, { @required Route oldRoute, @required Route newRoute })

描述:用一个新的路由,将路由表内的一个已存在的路由替换掉

  • 假设当前路由顺序为:A-B-C-D-E
  • 在E页面,replace(context,oldRoute:RouteMap.getRoute("pageb"),newRoute:MaterialPageRoute(PageF());
  • 路由顺序为:A-F-C-D-E
         RaisedButton(
              onPressed: () {
                Navigator.replace(context, oldRoute: RouteMap.getRoute("pageb"), newRoute: MaterialPageRoute(builder: (BuildContext context){
                  return PageF("dd");
                }));
              },
              child: Text("replace"),
            ),
Navigator.replaceRouteBelow(BuildContext context, { @required Route anchorRoute, Route newRoute })

描述:用一个新的路由,将路由表内的一个已存在的路由的前面的路由给替换掉

  • 假设当前路由顺序为:A-B-C-D-E
  • 在E页面,replace(context,oldRoute:RouteMap.getRoute("pagec"),newRoute:MaterialPageRoute(PageF());
  • 路由顺序为:A-F-C-D-E
          RaisedButton(
              onPressed: () {
                Navigator.replaceRouteBelow(context, anchorRoute: RouteMap.getRoute("pagec"),newRoute: MaterialPageRoute(builder: (BuildContext context){
                  return PageF("dd");
                }));
              },
              child: Text("replaceRouteBelow"),
            ),

通过路由导航与通过命名路由导航的异同

路由导航:

  • 直接通过类名跳转,跳转动作分散在各个类的内部。
  • 界面间传递数据必须放在目标类的构造方法内。 命名路由:
  • 所有路由都放到一个地方进行注册,一目了然。
  • 界面间传递数据是通过方法参数传递过去的。
  • 如果在业务场景中必须实时创建一些类的路由,构造方法内的数据是业务关联的,那么命名路由就不太合适了。

因此根据自己的实用场景进行选择,最好不要混用,而是只选择一种使用,我个人倾向于使用命名路由,方便管理。