美文网首页Flutter
Flutter之路由详解

Flutter之路由详解

作者: HuyaRC | 来源:发表于2021-01-14 17:33 被阅读0次

    本文主要包含两个方面:【路由导航】和【路由传值】

    路由传值

    Flutter中管理多个页面时有两个核心概念和类:RouteNavigator
    一个route是一个屏幕或页面的抽象,Navigator是管理routeWidgetNavigator可以通过route入栈和出栈来实现页面之间的跳转。
    路由一般分为静态路由(即命名路由)和动态路由。

    静态路由(即命名路由)

    静态路由在通过Navigator跳转之前,需要在MaterialApp组件内显式声明路由的名称,而一旦声明,路由的跳转方式就固定了。通过在MaterialApp内的routes属性进行显式声明路由的定义。

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          initialRoute: "/", // 默认加载的界面,这里为RootPage
          routes: { // 显式声明路由
           // "/":(context) => RootPage(),
            "/A":(context) => Apage(),
            "/B":(context) => Bpage(),
            "/C":(context) => Cpage(),
          },
          home: RootPage(),
        );
      }
    }
    注意:如果指定了home属性,routes表则不能再包含此属性。
    如上代码中【home: RootPage()】  和 【"/":(context) => RootPage()】两则不能同时存在。
    

    例如:RootPage跳转Apage即:RootPage —>Apage

    Navigator.of(context).pushNamed("/A");
    

    一般方法中带有Name多数是通过静态路由完成跳转的,如pushNamedpushReplacementNamedpushNamedAndRemoveUntil等。

    动态路由

    动态路由无需在MaterialApp内的routes中注册即可直接使用:RootPage —> Apage

     Navigator.of(context).push(MaterialPageRoute(
       builder: (context) => Apage(),
     ));
    

    动态路由中,需要传入一个Route,这里使用的是MaterialPageRoute,它可以使用和平台风格一致的路由切换动画,在iOS上左右滑动切换,Android上会上下滑动切换。也可以使用CupertinoPageRoute实现全平台的左右滑动切换。
    当然也可以自定义路由切换动画,使用PageRouteBuilder:使用FadeTransition
    做一个渐入过渡动画。

    Navigator.of(context).push(
      PageRouteBuilder(
        transitionDuration: Duration(milliseconds: 250), // //动画时间为0.25秒
        pageBuilder: (BuildContext context,Animation animation,
            Animation secondaryAnimation){
          return FadeTransition( //渐隐渐入过渡动画
            opacity: animation,
            child: Apage()
           );
         }
      )
    );
    

    到现在为止,可能对路由有了一定的认识,,下面就结合具体方法来详细说明。
    在这之前有必要说明:
    Navigator.of(context).pushNavigator.push两着并没有特别的区别,看源码也得知,后者其实就是调用了前者。
    of:获取Navigator当前已经实例的状态。

    pop

    返回当前路由栈的上一个界面。
    Navigator.pop(context);

    push / pushNamed :

    见上,两者运行效果相同,只是调用不同,都是将一个page压入路由栈中。直白点就是push是把界面直接放入,pushNames是通过路由名的方式,通过router使界面进入对应的栈中。
    结果:直接在原来的路由栈上添加一个新的 page

    pushReplacement / pushReplacementNamed / popAndPushNamed

    替换路由,顾名思义替换当前的路由。
    例如


    Replacement.png

    由图可知在BPage使用替换跳转到Cpage的时候,BpageCpage替换了在堆栈中的位置而移除栈,CPage默认返回的是APage

    pushReplacement 使用的动态路由方式跳转:
    Navigator.of(context).pushReplacement(MaterialPageRoute(
      builder: (context) => Cpage(),
    ));
    
    pushReplacementNamed 使用的静态路由方式,
    Navigator.of(context).pushReplacementNamed("/C");
    

    两者运行效果相同。

    popAndPushNamed:
    Navigator.of(context).popAndPushNamed("/C");
    

    其实和上面两个方法运行的结果也是一致,区别就是动画效果不一样:BPage —>CPage的时候,CPage会同时有pop的转场效果和从BPagepush的转场效果。简单来说就是CPagepopBPage,在pushCPage。(不知道是不是卡顿的原因,笔者看起来区别不大)

    综上:3中方法结果一样,只是调用方式和过渡动画的区别,开发者自行选择。

    pushAndRemoveUntil / pushNamedAndRemoveUntil

    在使用上述方式跳转时,会按次序移除其他的路由,直到遇到被标记的路由(predicate函数返回了true)时停止。若 没有标记的路由,则移除全部。
    当路由栈中存在重复的标记路由时,默认移除到最近的一个停止。

    第一种
    // 移除全部
    Navigator.pushAndRemoveUntil(context,
                    MaterialPageRoute(builder: (_) => CPage()), (Route router) => router == null);
    

    // 移除全部
    Navigator.of(context).pushNamedAndRemoveUntil("/C", (Route router) => router == null);
    

    此时的路由栈示意图:


    RemoveUntil_all.png

    可知出了要pushCPage,当前路由栈中所有的路由都被移除,CPage变成根路由。

    第二种:移除到RootPage停止
    // "/"即为RootPage,标记后,移除到该路由停止移除
    Navigator.pushAndRemoveUntil(context,
                    MaterialPageRoute(builder: (_) => CPage()), ModalRoute.withName('/'))
    或
    Navigator.pushAndRemoveUntil(context,
                    MaterialPageRoute(builder: (_) => CPage()), (Route router) => router.settings.name == "/");
    // 只是写法不一样
    

    Navigator.of(context).pushNamedAndRemoveUntil("/C", (Route router) => router.settings.name == "/");
    或
    Navigator.of(context).pushNamedAndRemoveUntil("/C", ModalRoute.withName("/"));
    

    此时的路由栈示意图:


    RemoveUntil_until.png

    pushCPage的时候,移除到RootPage停止,CPage默认返回RootPage

    popUntil

    返回到指定的标记路由,若标记的路由为null,则程序退出,慎用!!!
    有时候我们需要根据业务需求判断:可能返回上一级路由,也可能返回上上级路由或是返回指定的路由等。这个时候就不能使用Replacement和RemoveUntil来替换、移除路由了。
    例如:

    until.png
    Navigator.of(context).popUntil((route) => route.settings.name == "/");
    或
    Navigator.of(context).popUntil(ModalRoute.withName("/"));
    

    再例如:


    要实现上述功能,从CPage返回到APage,并且不在MaterialApp内的routes属性进行显式声明路由。因为笔者觉得一个应用程序的界面太多了,如果每个界面都要显示声明路由,实在是不优雅。
    因为需要返回APage,还是需要标记路由,所有我们在之前跳转APage的时候设置RouteSettings,如下:
    // 设置APage的RouteSettings
    Navigator.of(context).push(MaterialPageRoute(
      settings: RouteSettings(name:"/A"),
      builder: (context) => APage(),
    ));
    

    CPage需要返回的时候,调用就行:

    Navigator.of(context).popUntil(ModalRoute.withName("/A"));
    

    这样代码看起来很优雅,不会冗余。
    另:

    // 返回根路由
    Navigator.of(context).popUntil((route) => route.isFirst);
    

    canPop

    用来判断是否可以导航到新页面,返回的bool类型,一般是在设备带返回的物理按键时需要判断是否可以pop

    maybePop

    可以理解为canPop的升级,maybePop会自动判断。如果当前的路由可以pop,则执行当前路由的pop操作,否则将不执行。

    removeRoute/removeRouteBelow

    删除路由,同时执行Route.dispose操作,无过渡动画,正在进行的手势也会被取消。

    removeRoute
    removeRoute.png

    BPage被移除了当前的路由栈。
    如果在当前页面调用removeRoute,则类似于调用pop方法,区别就是无过渡动画,所以removeRoute也可以用来返回上一页。

    removeRouteBelow

    移除指定路由底层的临近的一个路由,并且对应路由不存在的时候会报错。
    同上。

    综上:这个两个方法一般情况下很少用,而且必须要持有对应的要移除的路由。
    一般用于立即关闭,如移除当前界面的弹出框等。


    路由传值

    常见的路由传值分为两个方面:

    • 向下级路由传值
    • 返回上级路由时传值

    要注意的是,我们一般说静态路由不能传值,并不是说一定不能用于传值,而是因为静态路由一般需要在MaterialApp内的routes属性进行显式声明,在这里使用构造函数传值无实际意义。
    如:

     MaterialApp(
          initialRoute: "/", // 默认加载的界面,这里为RootPage
          routes: { // 显式声明路由
            "/":(context) => RootPage(),
            "/A":(context) => APage("title"),  // 在这里传参无实际意义,一般需要传入的参数都是动态变化的
            "/B":(context) => BPage(),
            "/C":(context) => CPage(),
          },
         // home: RootPage(),
        );
    

    向下级路由传值

    1、构造函数传值

    首先构造一个可以带参数的构造函数:

    class APage extends StatefulWidget {
      String title;
      APage(this.title);
      @override
      _APageState createState() => _APageState();
    }
    

    在路由跳转的时候传值:

    Navigator.of(context).push(MaterialPageRoute(
      builder: (context) => APage("这是传入的参数"),
    ));
    

    在APage拿到传入的值:

    // 在 StatefulWidget 使用[widget.参数名]
    Container(
      child: Text(widget.title),
    )
    
    2、ModalRoute 传值

    Navigator.of(context).push的跳转方式中,MaterialPageRoute的构造参数中 可以看到有RouteSettings的属性,RouteSettings就是当前路由的基本信息

    const RouteSettings({
        this.name,
        this.isInitialRoute = false,
        this.arguments, // 存储路由相关的参数Object
      });
    

    路由跳转时设置传递参数:

    Navigator.of(context).push(MaterialPageRoute(
      settings: RouteSettings(name:"/A",arguments: {"argms":"这是传入A的参数"}),
      builder: (context) => APage(),
    ));
    或使用静态路由pushName:
    Navigator.of(context).pushNamed("/A",arguments:{"argms":"这是传入A的参数"});
    

    APage中取值:

    Map argms = ModalRoute.of(context).settings.arguments;
    print(argms["argms"]);
    

    返回上级路由时传值

    就是在调用APage中调用pop返回路由的时候传参

    Navigator.of(context).pop("这是pop返回的参数值");
    

    在上一级路由获取:

    Navigator.of(context).push(MaterialPageRoute(
      builder: (context) => APage(),
    )).then((value){ // 获取pop的传值
      print(value);
    });
    或
    String value = await Navigator.of(context).pushNamed('/xxx');
    

    个人浅见,有误的地方欢迎指正

    相关文章

      网友评论

        本文标题:Flutter之路由详解

        本文链接:https://www.haomeiwen.com/subject/qubmaktx.html