从零开始的Flutter之旅: StatelessWidgetgit
从零开始的Flutter之旅: StatefulWidgetgithub
从零开始的Flutter之旅: InheritedWidgetweb
从零开始的Flutter之旅: Providersegmentfault
这篇文章是从零开始系列的第五期,前面咱们讲到了Widget与结合数据共享的Provider处理。微信
此次咱们接着来了解一下路由导航Navigator的相关信息。网络
Flutter中的路由管理与原生开发相似,都会维护一个路由栈,经过push入栈打开一个新的页面,而后再经过pop出栈关闭老的页面。架构
咱们直接到 flutter_github中找个简单的实例。框架
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页面。less
用法很简单经过push传递一个Route。这里对应的是MaterialPageRoute。它会提供一个builder方法,咱们直接在builder中返回想要跳转的页面实例便可。async
它继承于PageRoute,PageRoute是一个抽象类,它提供了路由切换时的过渡动画效果与相应的接口。而MaterialPageRoute经过这些接口来实现不一样平台上对应风格的路由切换动画效果。例如:
若是想自定义切换动画,能够仿照MaterialPageRoute,继承于PageRoute来实现。
须要注意的是,push操做会返回一个Future,它是用来接收新的路由关闭时返回的数据。在Android中对应的就是startActivityForResult() 和 onActivityResult()API。
@optionalTypeArgs Future<T> push<T extends Object>(Route<T> route) { assert(!_debugLocked); assert(() { _debugLocked = true; return true; }()); .... .... }
对应的另外一个是pop操做,出栈是能够向以前的页面传递数据,在Android中对应的就是setResult() Api
@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; ... ... }
除了上面两个经常使用的,还有下面几个特殊的操做
这些都是根据特定场景使用,例如文章最开始的登陆判断示例。这段判断代码其实在App启动时的引导页面中,因此无论最终跳转到哪一个页面,最终这个引导页面都须要从路由中消失,因此这里就能够经过pushReplacement来开启新的路由页面。
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context){ return HomePage(); }));
路由跳转页面天然少不了参数的传递,经过上面的方式进行路由跳转,传参也很是简单,能够直接经过实例类进行传参。
我这里以flutter_github中的WebViePage为例。
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参数的接收,直接经过实例化进行参数传递
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对象,将其传递到跳转的页面中。
将上面的代码进行改版
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中对参数的接收处理
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来定义
final Map<String, WidgetBuilder> routes;
经过定义,应该很好理解。它是一个map,key表明路由名称,value表明具体的页面实例。
以flutter_github中的GithubApp为例。
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(), }, ); } }
须要注意的有两点
为了方便管理路由的跳转,这里使用了AppRoutes来统一管理路由的名称
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');
如今咱们已经注册好了须要跳转的页面路由,接下来使用命名路由的方式来替换以前介绍的路由方式。
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()
对于命名路由的参数传递与以前最后面介绍的参数传递方式相似,例如
Navigator.of(context).pushNamed(webViewRoute.routeName, arguments: {WebViewPage.ARGS_TITLE: item.subject?.title ?? '', WebViewPage.ARGS_REQUEST_URL: item.subject?.url ?? ''});
基本上相似,也是传递一个arguments,对应的页面接收参数的方式保存不变。
命名路由中还有一个须要注意的是onGenerateRoute
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方面的知识就介绍到这里,若是文章中有不足的地方欢迎指出,或者说你这其中有什么疑问也能够留言与我,我将力所能及的进行解答。
下面介绍一个完整的Flutter项目,对于新手来讲是个不错的入门。
flutter_github,这是一个基于Flutter的Github客户端同时支持Android与IOS,支持帐户密码与认证登录。使用dart语言进行开发,项目架构是基于Model/State/ViewModel的MSVM;使用Navigator进行页面的跳转;网络框架使用了dio。项目正在持续更新中,感兴趣的能够关注一下。
固然若是你想了解Android原生,相信flutter_github的纯Android版本AwesomeGithub是一个不错的选择。
若是你喜欢个人文章模式,或者对我接下来的文章感兴趣,建议您关注个人微信公众号:【Android补给站】
或者扫描下方二维码,与我创建有效的沟通,同时更快更准的收到个人更新推送。