flutter 开发技巧--路由详解

209 阅读7分钟

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

路由导航

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

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

2675141-de7b4cb63fcb1d83.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);

此时的路由栈示意图:

2675141-364a8005348f7aec.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("/"));

此时的路由栈示意图:

image.png

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

popUntil

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

image.png

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

再例如:

image.png

要实现上述功能,从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

image.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(),
));
或使用静态路由pushNameNavigator.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');