前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从零开始的Flutter之旅: Navigator

从零开始的Flutter之旅: Navigator

作者头像
Rouse
发布2020-07-01 15:39:22
7120
发布2020-07-01 15:39:22
举报
文章被收录于专栏:Android补给站Android补给站

读完需要

17分钟

速读仅需 6 分钟

这篇文章是从零开始系列的第五期,前面我们讲到了Widget与结合数据共享的Provider处理。

这次我们接着来了解一下路由导航Navigator的相关信息。

Flutter中的路由管理与原生开发类似,都会维护一个路由栈,通过push入栈打开一个新的页面,然后再通过pop出栈关闭老的页面。

示例

我们直接到 flutter_github中找个简单的实例。

代码语言:javascript
复制
  void _goToLogin() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    String authorization = prefs.getString(SP_AUTHORIZATION);
    String token = prefs.getString(SP_ACCESS_TOKEN);
    if ((authorization != null && authorization.isNotEmpty) ||
        (token != null && token.isNotEmpty)) {
      Navigator.of(context).push(MaterialPageRoute(builder: (context) {
        return HomePage();
      }));
    } else {
      Navigator.of(context).push(MaterialPageRoute(builder: (context) {
        return LoginPage();
      }));
    }
  }

上面的方法是判断是否已经登录了。如果登录了通过过Navigator跳转到HomePage页面,否则跳转到LoginPage页面。

用法很简单通过push传递一个Route。这里对应的是MaterialPageRoute。它会提供一个builder方法,我们直接在builder中返回想要跳转的页面实例即可。

它继承于PageRoute,PageRoute是一个抽象类,它提供了路由切换时的过渡动画效果与相应的接口。而MaterialPageRoute通过这些接口来实现不同平台上对应风格的路由切换动画效果。例如:

  1. Android平台,push时页面会从屏幕底部滑动到顶部进入,pop时页面会从屏幕顶部滑动到屏幕底部退出。
  2. Ios平台,push时页面会从屏幕右侧滑动到屏幕左侧进入,pop时页面会从屏幕左侧滑动到屏幕右侧退出。

如果想自定义切换动画,可以仿照MaterialPageRoute,继承于PageRoute来实现。

Navigator

需要注意的是,push操作会返回一个Future,它是用来接收新的路由关闭时返回的数据。在Android中对应的就是startActivityForResult() 和 onActivityResult()API。

代码语言:javascript
复制
 @optionalTypeArgs
  Future<T> push<T extends Object>(Route<T> route) {
    assert(!_debugLocked);
    assert(() {
      _debugLocked = true;
      return true;
    }());
    ....
    ....
  }

对应的另一个是pop操作,出栈是可以向之前的页面传递数据,在Android中对应的就是setResult() Api

代码语言:javascript
复制
  @optionalTypeArgs
  bool pop<T extends Object>([ T result ]) {
    assert(!_debugLocked);
    assert(() {
      _debugLocked = true;
      return true;
    }());
    final Route<dynamic> route = _history.last;
    assert(route._navigator == this);
    bool debugPredictedWouldPop;
    ...
    ...
  }

除了上面两个常用的,还有下面几个特殊的操作

  1. pushReplacement: 将当前的路由页面进行替换成新的路由页面, 之前的路由将会失效。
  2. pushAndRemoveUntil: 加入一个新的路由,同时它接收一个判断条件,如果满足条件将会移除之前所有的路由。

这些都是根据特定场景使用,例如文章最开始的登录判断示例。这段判断代码其实在App启动时的引导页面中,所以不管最终跳转到哪个页面,最终这个引导页面都需要从路由中消失,所以这里就可以通过pushReplacement来开启新的路由页面。

代码语言:javascript
复制
     Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context){
        return HomePage();
      }));

传参

路由跳转页面自然少不了参数的传递,通过上面的方式进行路由跳转,传参也非常简单,可以直接通过实例类进行传参。

我这里以flutter_github中的WebViePage为例。

代码语言:javascript
复制
class WebViewPage extends BasePage<_WebViewState> {
  final String url;
  final String requestUrl;
  final String title;
 
  WebViewPage({@required this.title, this.url = '', this.requestUrl = ''});
 
  @override
  _WebViewState createBaseState() => _WebViewState(title, url, requestUrl);
}

上面是WebViewPage参数的接收,直接通过实例化进行参数传递

代码语言:javascript
复制
  contentTap(int index, BuildContext context) {
    NotificationModel item = _notifications[index];
    if(item.unread) _markThreadRead(index, context);
    Navigator.push(context, MaterialPageRoute(builder: (_) {
      return WebViewPage(
        title: item.subject?.title ?? '',
        requestUrl: item.subject?.url ?? '',
      );
    }));
  }

这里是通过点击文本跳转到WebViewPage页面,使用push操作来导航到WebViewPage页面,同时在实例化时将相应的参数传递过去。

以上是相对比较原始的方法进行参数传递,还有另一种

做个Android的朋友都知道在Activity页面跳转时可以同Intent进行参数传递,而接受页面也可以通过Intent来获取传递过来的参数。

在Flutter中也有类似的传参方式。我们可以通过MaterialPageRoute中的settings来构建一个arguments对象,将其传递到跳转的页面中。

将上面的代码进行改版

代码语言:javascript
复制
  contentTap(int index, BuildContext context) {
    NotificationModel item = _notifications[index];
    if (item.unread) _markThreadRead(index, context);
    Navigator.push(
        context,
        MaterialPageRoute(
            builder: (_) {
              return WebViewPage();
            },
            settings: RouteSettings(
                arguments: {WebViewPage.ARGS_TITLE: item.subject?.title ?? '', WebViewPage.ARGS_REQUEST_URL: item.subject?.url ?? ''})));
  }

这是参数传递,下面是WebViewPage中对参数的接收处理

代码语言:javascript
复制
    Map<String, String> arguments = ModalRoute.of(context).settings.arguments;
    _title = arguments[WebViewPage.ARGS_TITLE];
    _url = arguments[WebViewPage.ARGS_URL];
    vm.requestUrl = arguments[WebViewPage.ARGS_REQUEST_URL];

在接收页面参数是通过ModalRoute来获取的,获取到的arguments就是上面传递过来的参数map数据。

ModalRoute.of()内部运用的是context.dependOnInheritedWidgetOfExactType()

是不是有点眼熟?如果不记得的话推荐重新温习一遍从零开始的Flutter之旅: InheritedWidget

以上都是非命名路由,下面我们再来了解一下命名路由的使用与参数方式。

命名路由

命名路由,顾名思义通过提前注册好的名称来跳转到对应的页面。

首页我们需要注册一个路由表,约定好名称与页面的一一对应。

而路由表可以通过routes来定义

代码语言:javascript
复制
  final Map<String, WidgetBuilder> routes;

通过定义,应该很好理解。它是一个map,key代表路由名称,value代表具体的页面实例。

以flutter_github中的GithubApp为例。

代码语言:javascript
复制
class _GithubAppState extends State<GithubApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Github',
      theme: ThemeData.light(),
      initialRoute: welcomeRoute.routeName,
      routes: {
        welcomeRoute.routeName: (BuildContext context) => WelcomePage(),
        loginRoute.routeName: (BuildContext context) => LoginPage(),
        homeRoute.routeName: (BuildContext context) => HomePage(),
        repositoryRoute.routeName: (BuildContext context) => RepositoryPage(),
        followersRoute.routeName: (BuildContext context) =>
            FollowersPage(followersRoute.pageType),
        followingRoute.routeName: (BuildContext context) =>
            FollowersPage(followingRoute.pageType),
        webViewRoute.routeName: (BuildContext context) => WebViewPage(),
      },
    );
  }
}

需要注意的有两点

  1. initialRoute是用来初始化路由页面,它接收的也是对应路由页面的注册名称
  2. routes就是注册的路由表,只需通过key、value的方式来注册对应的路由页面。

为了方便管理路由的跳转,这里使用了AppRoutes来统一管理路由的名称

代码语言:javascript
复制
class AppRoutes {
  final String routeName;
  final String pageTitle;
  final String pageType;
 
  const AppRoutes(this.routeName, {this.pageTitle, this.pageType});
}
 
class PageType {
  static const String followers = 'followers';
  static const String following = 'following';
}
 
const AppRoutes welcomeRoute = AppRoutes('/');
 
const AppRoutes loginRoute = AppRoutes('/login');
 
const AppRoutes homeRoute = AppRoutes('/home');
 
const AppRoutes repositoryRoute =
    AppRoutes('/repository', pageTitle: 'repository');
 
const AppRoutes followersRoute = AppRoutes('/followers',
    pageTitle: 'followers', pageType: PageType.followers);
const AppRoutes followingRoute = AppRoutes('/following',
    pageTitle: 'following', pageType: PageType.following);
 
const AppRoutes webViewRoute = AppRoutes('/webview', pageTitle: 'WebView');

现在我们已经注册好了需要跳转的页面路由,接下来使用命名路由的方式来替换之前介绍的路由方式。

代码语言:javascript
复制
  void _goToLogin() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    String authorization = prefs.getString(SP_AUTHORIZATION);
    String token = prefs.getString(SP_ACCESS_TOKEN);
    if ((authorization != null && authorization.isNotEmpty) ||
        (token != null && token.isNotEmpty)) {
      Navigator.pushReplacementNamed(context, homeRoute.routeName);
    } else {
      Navigator.pushReplacementNamed(context, loginRoute.routeName);
    }
  }

在登录状态判断跳转的过程中,可以直接通过pushReplacementNamed()来跳转到对应的页面。与之前的区别是,我们只需传递对应跳转页面的路由名称。因为已经有了路由注册表,所以会自己转变成相应的页面。

对应的方法还有pushNamed()与pushNamedAndRemoveUntil()

对于命名路由的参数传递与之前最后面介绍的参数传递方式类似,例如

代码语言:javascript
复制
    Navigator.of(context).pushNamed(webViewRoute.routeName, 
        arguments: {WebViewPage.ARGS_TITLE: item.subject?.title ?? '', WebViewPage.ARGS_REQUEST_URL: item.subject?.url ?? ''});

基本上类似,也是传递一个arguments,对应的页面接收参数的方式保存不变。

onGenerateRoute

命名路由中还有一个需要注意的是onGenerateRoute

代码语言:javascript
复制
class _GithubAppState extends State<GithubApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
    ...
      onGenerateRoute: (RouteSettings setting) {
        return MaterialPageRoute(builder: (context) {
          String routeName = setting.name;
          // todo navigator
        });
      },
    );
  }
}

它的回调条件是:跳转的页面没有在routes中进行路由注册

通过该回调方法,我们可以在这里进行路由拦截,再统一做一些页面跳转的逻辑处理。

Navigator方面的知识就介绍到这里,如果文章中有不足的地方欢迎指出,或者说你这其中有什么疑问也可以留言与我,我将力所能及的进行解答。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android补给站 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 示例
  • Navigator
  • 传参
  • 命名路由
  • onGenerateRoute
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档