Flutter 路由管理(一)

3,188 阅读6分钟

路由在移动端中,通常指的是页面(page)。所谓路由管理,其实就是管理页面直接的跳转。而在Flutter中,我们使用Navigator来管理路由的跳转;使用PageRoute来描述我们要跳转到那个界面、转场的动画效果。

Navigator

Navigator是一个路由管理的widget,它通过一个栈来管理一个路由widget集合。通常当前屏幕显示的页面就是栈顶的路由。下面我们通过学习Navigator提供了一系列方法,来了解它是如何管理路由栈。

1. PushNamed

static Future<T> pushNamed<T extends Object>(
  BuildContext context,
  String routeName, {
  Object arguments,
 }) {
  return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
}

根据路由名称(routeName),找寻对应的route,并实现见面的跳转。其中arguments用来给跳转的route传值。

2. PushReplacementNamed

static Future<T> pushReplacementNamed<T extends Object, TO extends Object>(
  BuildContext context,
  String routeName, {
  TO result,
  Object arguments,
  }) {
   return Navigator.of(context).pushReplacementNamed<T, TO>(routeName, arguments: arguments, result: result);
}

PushReplacementNamedPushName的作用都是根据路由名称(routeName),找寻对应的route,并实现见面的跳转。二者的区别在于PushReplacementNamed会替换当前的路由,也就是说新push进入的route会替换原来route。

3. PopAndPushNamed

static Future<T> popAndPushNamed<T extends Object, TO extends Object>(
  BuildContext context,
  String routeName, {
  TO result,
  Object arguments,
 }) {
   return Navigator.of(context).popAndPushNamed<T, TO>(routeName, arguments: arguments, result: result);
}

PopAndPushNamedPushReplacementNamed的作用类型,但是PopAndPushNamed是先pop原来的route,再push新的route。

4. Push

static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
    return Navigator.of(context).push(route);
}

PushPushName的作用完全一样;二者的区别在于Push直接跳转route,而PushName需要根据routName找寻相应的route后再实现跳转。

5. PushReplacement

static Future<T> pushReplacement<T extends Object, TO extends Object>(BuildContext context, Route<T> newRoute, { TO result }) {
    return Navigator.of(context).pushReplacement<T, TO>(newRoute, result: result);
}

PushReplacementPushReplacementNamed的作用完全一样;二者的区别在于PushReplacementNamed需要根据routName找寻相应的route,PushReplacement不需要。

6. PushAndRemoveUntil

static Future<T> pushAndRemoveUntil<T extends Object>(BuildContext context, Route<T> newRoute, RoutePredicate predicate) {
    return Navigator.of(context).pushAndRemoveUntil<T>(newRoute, predicate);
}

跳转到新的路由,并删除路由栈中的其他路由,直到predicate为true后,停止删除。该方法的应用场景:可以应用于支付流程。在支付的过程中,我们会跳转多个界面(填写金额、选择支付方式、输入密码、支付结果...),当我们支付成功后,pop时不应该一层一层的,而应该直接返回根界面。

7. canPop 和 maybePop


static bool canPop(BuildContext context) {
  final NavigatorState navigator = Navigator.of(context, nullOk: true);
  return navigator != null && navigator.canPop();
}

static Future<bool> maybePop<T extends Object>(BuildContext context, [ T result ]) {
    return Navigator.of(context).maybePop<T>(result);
}

canPop 判断当前route是否可以pop,返回bool;maybePop先判断当前route是否可以pop,可以则直接pop,不行则没有任何效果。

8. pop 和 popUntil


static bool pop<T extends Object>(BuildContext context, [ T result ]) {
    return Navigator.of(context).pop<T>(result);
}

static void popUntil(BuildContext context, RoutePredicate predicate) {
    Navigator.of(context).popUntil(predicate);
}

pop 返回上一个route;popUntil一直返回route,直到predicate返回true时停止。

MaterialPageRoute

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

  • 对于Android,当打开新页面时,新的页面会从屏幕底部滑动到屏幕顶部;当关闭页面时,当前页面会从屏幕顶部滑动到屏幕底部后消失,同时上一个页面会显示到屏幕上。
  • 对于iOS,当打开页面时,新的页面会从屏幕右侧边缘一致滑动到屏幕左边,直到新页面全部显示到屏幕上,而上一个页面则会从当前屏幕滑动到屏幕左侧而消失;当关闭页面时,正好相反,当前页面会从屏幕右侧滑出,同时上一个页面会从屏幕左侧滑入。
MaterialPageRoute({
  @required this.builder,   // 创建需要跳转的界面
  RouteSettings settings,   // 包含路由的配置信息,如路由名称、是否初始路由(首页)。
  this.maintainState = true,    // 默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState为false。
  bool fullscreenDialog = false,    // 表示新的路由页面是否是一个全屏的模态对话框,在iOS中,如果fullscreenDialog为true,新页面将会从屏幕底部滑入(而不是水平方向)。
}) : assert(builder != null),
     assert(maintainState != null),
     assert(fullscreenDialog != null),
     assert(opaque),
     super(settings: settings, fullscreenDialog: fullscreenDialog);

注意:如果想自定义路由切换动画,可以自己继承PageRoute来实现,我们将在后面介绍动画时,实现一个自定义的路由Widget。

如何使用 Navigator 来管理 Route?

  1. Navigator 使用 push 将 route 添加到路由栈中
  2. Navigator 使用 pop 将 route 从路由栈中移除
/// Push
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
    return NewRoute();
}));

/// Pop
Navigator.pop(context);

命名路由

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

要想使用命名路由,我们必须先提供并注册一个路由表(routing table),这样应用程序才知道哪个名称与哪个路由Widget对应。路由表的定义如下:

Map<String, WidgetBuilder> routes;

它是一个Map, key 为路由的名称,是个字符串;value是个builder回调函数,用于生成相应的路由Widget。我们在通过路由名称入栈新路由时,应用会根据路由名称在路由表中找到对应的WidgetBuilder回调函数,然后调用该回调函数生成路由widget并返回。

如何注册一个路由表

我们需要先注册路由表后,我们的Flutter应用才能正确处理命名路由的跳转。注册方式很简单,在我们的项目main.dart文件中找到MaterialApp,添加routes属性,代码如下:

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
      routes: {
        "new_page":(context)=>NewRoute(),
      },
    );
  }
}

现在我们就完成了路由表的注册。

如何使用命名路由

通过上面的学习,我们知道可以使用NavigatorpushNamed来通过路由名称,完成路由跳转。

onPressed: () {
  Navigator.pushNamed(context, "new_page");
},

如何给命名路由传递参数

// 注册路由
routes:{
    "new_page":(context)=>EchoRoute(),
},

// 通过RouteSetting对象获取路由参数
class EchoRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //获取路由参数  
    var args=ModalRoute.of(context).settings.arguments
    //...省略无关代码
  }
}

// Navigator 跳转路由
Navigator.of(context).pushNamed("new_page", arguments: "hi");