查看原文
其他

Flutter系列之Navigator使用详解

jzman 躬行之 2022-08-26
PS:想做一件事很容易,真正去做一件事很困难。
Navigator 使用堆栈规则来管理 Widget,Navigator 记录了页面访问记录,可以使用 Navigator 来完成页面之间的跳转操作。
在 Android 开发中,我们平时说的跳转都是指的是 Activity 的跳转,也称为页面跳转,在 Fluter 中都是指的是 Route 的跳转,Android 中的页面在 Flutter 中对应 Route,Navigator 负责 Route 对象堆栈的管理,并提供管理堆栈的方法,如 Navigator.push 和 Navigator.pop。
如上 Flutter 提供了 Route 入栈、Route 出栈的方法,Android 中一些设备有返回键,这个返回键是兼容 Flutter 的 Navigator.push 和 Navigator.pop 方法,如果某些设备没有对应的返回键可以在 AppBar 中自行添加返回按钮, Scaffold 中已经添加了返回按钮,触发的时候会调用 Navigator.pop 操作,本文内容如下:
  1. 基本路由导航

  2. 路由参数传递

  3. 其他路由导航

基本路由导航

前面提到 Flutter 中使用堆栈来管理 Widget,入栈、出栈的方法分别是 Navigator.push 和 Navigator.pop,通过这两个方法来完成页面之间的跳转、回退操作。
Navigator.push
Navigator.push 用来执行 Route 的入栈操作,就可以通过指定的 Route 跳转到对应的页面了,方法如下:
/// 参数:(上下文,具体路由)
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
    return Navigator.of(context).push(route);
}
使用 MaterialPageRoute 构建对应页面的路由,使用方式如下:
// 跳转到NavigatorPushPopPage
_navigateToPage(context, NavigatorPushPopPage());

/// Navigator.push
/// 页面跳转
_navigateToPage(BuildContext context, Widget widget) {
  Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
    return widget;
  }));
}
Navigator.pop
Navigator.pop 用来执行 Route 的退栈操作,即页面回退,可以添加可选参数 result 作为页面返回时携带的参数,方法如下:
/// 参数:(上下文,返回时携带的参数(可选参数))
static bool pop<T extends Object>(BuildContext context, [ T result ]) {
    return Navigator.of(context).pop<T>(result);
}
如下,点击 IconButton 退出当前页面:
IconButton(
    icon: Icon(Icons.arrow_back),
    onPressed: () => Navigator.pop(context)),
Navigator.pushnamed
Navigator.pushnamed 用来执行已命名 Route 的入栈操作,可以在通过可选参数 arguments 传递参数,方法如下:
/// 参数:(上下文,路由名称,携带的参数(可选参数))
static Future<T> pushNamed<T extends Object>(
    BuildContext context,
    String routeName, {
    Object arguments,
   }) {
    return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
}
使用时首先在 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(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
      routes: <String, WidgetBuilder>{
        // 对应路由/NavigatorPushNamedPage
        NavigatorPushNamedPage.routeName: (BuildContext context) =>
            NavigatorPushNamedPage(),
      },
    );
  }
}

/// page
/// Navigator.pushNamed
/// 使用已命名路由
class NavigatorPushNamedPage extends StatelessWidget {
  static final routeName = '/NavigatorPushNamedPage';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Navigator.pushNamed"),
        centerTitle: true,
      ),
      body: Center(
        child: Text(
          "Navigator.pushNamed",
          style: TextStyle(fontSize: 18),
        ),
      ),
    );
  }
}
然后就可以使用 Navigator.pushNamed 跳转到 NavigatorPushNamedPage 这个页面了,如下:
// 跳转到NavigatorPushNamedPage
_navigatePushNamedPage(context, NavigatorPushNamedPage.routeName);

/// Navigator.pushNamed
/// 页面跳转
_navigatePushNamedPage(BuildContext context, String routeName,
    [Object arguments]) {
  Navigator.pushNamed(context, routeName, arguments: arguments);
}
以上就是 Flutter 中最基础的页面导航方式,无论是 Navigator.push 还是 Navigator.pushNamed 都是基于 NavigatorState 中的 push 方法实现的,NavigatorState 通过具体的 BuildContext 由 Navigator.of(context) 获取,NavigatorState 的 push 方法如下:
/// NavigatorState.push
Future<T> push<T extends Object>(Route<T> route) {
    assert(!_debugLocked);
    assert(() { _debugLocked = truereturn true; }());
    assert(route != null);
    assert(route._navigator == null);
    final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
    route._navigator = this;
    route.install(_currentOverlayEntry);
    _history.add(route);
    route.didPush();
    route.didChangeNext(null);
    if (oldRoute != null) {
      oldRoute.didChangeNext(route);
      route.didChangePrevious(oldRoute);
    }
    for (NavigatorObserver observer in widget.observers)
      observer.didPush(route, oldRoute);
    RouteNotificationMessages.maybeNotifyRouteChange(_routePushedMethod, route, oldRoute);
    assert(() { _debugLocked = falsereturn true; }());
    _afterNavigation(route);
    return route.popped;
}
下面介绍一下 Fluter 中参数的传递,包括页面跳转时参数的传递以及页面回退时参数的传递。

路由参数传递

页面跳转过程中的参数传递包括页面跳转时携带参数和页面回退时携带参数。
Navigator.push携带参数
使用 Navigator.push 携带参数进行页面跳转,参数是通过对应页面的构造方法接收的,具体使用参考如下:
/// 跳转到NavigatorPushWithParamPage
_navigateToPage(context,
    NavigatorPushWithParamPage(
      param: "this info from last page!",
    ));

/// Navigator.push/pop
/// 页面跳转
_navigateToPage(BuildContext context, Widget widget) {
  Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
    return widget;
  }));
}

/// page
/// Navigator.push携带参数
class NavigatorPushWithParamPage extends StatelessWidget {
  // 参数
  final String param;

  NavigatorPushWithParamPage({
    this.param,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Navigator.push携带参数"),
        centerTitle: true,
      ),
      body: Center(
        child: Text(
          "arguments:${this.param}",
          style: TextStyle(fontSize: 18),
        ),
      ),
    );
  }
}
Navigator.pushNamed携带参数
从上文可知 Navigator.pushNamed(context,routrName,{arguments}) 方法参数中的可选参数 arguments 就是跳转要携带的参数,首先在 MaterialApp 下的 onGenerateRoute 中接收通过 arguments 传递过来的参数,如下:
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
      // 参数接收
      onGenerateRoute: (RouteSettings settings) {
        if (settings.name == NavigatorPushNamedWithParamPage.routeName) {
          return MaterialPageRoute<String>(builder: (BuildContext context) {
            return NavigatorPushNamedWithParamPage(settings.arguments);
          });
        }else{
          return null;
        }
      },
    );
  }
}
然后使用 Navigator.pushNamed 进行页面跳转,参数还是对应页面的构造方法接收,参考如下:
/// 跳转到NavigatorPushNamedWithParamPage
_navigatePushNamedPage(
    context,
    NavigatorPushNamedWithParamPage.routeName,
    "this info from last page!");

/// Navigator.pushNamed携带参数
_navigatePushNamedPage(BuildContext context, String routeName,
    [Object arguments]) {
  Navigator.pushNamed(context, routeName, arguments: arguments);
}

/// page
/// Navigator.pushNamed携带参数
/// 使用已命名路由
class NavigatorPushNamedWithParamPage extends StatelessWidget {
  static final String routeName = '/NavigatorPushNamedWithParamPage';
  final String info;

  NavigatorPushNamedWithParamPage(this.info);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Navigator.pushNamed携带参数"),
          centerTitle: true,
        ),
        body: Center(
          child: Text(
            "arguments:${this.info}",
            style: TextStyle(fontSize: 18),
          ),
        ),
      ),
    );
  }
}
页面返回时携带参数
从上文可知 Navigator.pop(context,[result]) 方法参数中的可选参数 result 就是页面回退时携带的参数,Navigator.push 会返回一个 Future 在 then 语句中处理页面回退的结果,具体使用参考如下:
/// 跳转到NavigatorPopWithParamPage
_navigatePopWithParamPage(context, NavigatorPopWithParamPage());

/// Navigator.pop返回时携带参数
_navigatePopWithParamPage(BuildContext context, Widget widget) {
  Navigator.push<String>(context,
      MaterialPageRoute(builder: (BuildContext context) {
    return widget;
  })).then((result) {
    // 返回时携带参数处理
    Toast.show("Navigator.pop返回时携带参数:" + result, context);
  });
}

/// page
/// Navigator.pop返回时携带参数
class NavigatorPopWithParamPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
        child: Scaffold(
          appBar: AppBar(
            title: Text("Navigator.pop返回时携带参数"),
            centerTitle: true,
          ),
          body: Center(
            child: Text(
              "Navigator.pop返回时携带参数",
              style: TextStyle(fontSize: 18),
            ),
          ),
        ),
        onWillPop: () => _popBack(context));
  }

  /// 页面返回并设置返回参数,类似Android中的setResult方法
  _setResult(BuildContext context) {
    Navigator.pop(context, "this message from NavigatorPopWithParamPage!");
  }

  /// 统一处理返回键
  Future<bool> _popBack(BuildContext context) {
    _setResult(context);
    return Future.value(false);
  }
}

其他路由导航

其他常用路由导航方式如下:
// 从Navigator中移除当前所在路由再跳转到新的路由,相当于finish再startActivity
Navigator.popAndPushNamed
// 根据指定的Route直接返回,在此之前的路由会被清除
Navigator.popUntil
// 跳转到新的Route,并将指定Route之前的的Route清空,pushNamedAndRemoveUntil与之类似
Navigator.pushAndRemoveUntil
// 页面替换,pushReplacementNamed与之类似
Navigator.pushReplacement
其他与路由相关的操作方法就不一一列举了,可以查看相关 API。
推荐阅读:

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存