本文首先讲的Flutter中的路由,而后主要讲下Flutter中栈管理的几种方法。git
在Flutter中,咱们须要在不一样屏幕或者页面之间进行切换和发送数据,这些“screens”或者“pages”被称为Route(路由),是由一个Navigator的小部件进行管理。github
Navigator能够管理包含若干Route对象的堆栈,并提供了管理的方法,日常咱们常常用的就是[Navigator.push]和[Navigator.pop]。bash
尽管咱们能够本身直接建立一个navigator,可是当咱们建立一个WidgetsApp或者MaterialApp,Flutter会自动默认建立一个Navigator。 因此咱们通常是使用由[WidgetsApp]或者[MaterialApp]所建立的Navigator就好了,而后经过调用[Navigator.of] 来拿到当前的Navigator的状态NavigatorState,而后调用它的pop或者push方法。框架
好比要导航到一个新的页面,咱们能够建立一个[MaterialPageRoute]的实例,而后调用Navigator.of(context).push()方法就将新页面添加到堆栈的顶部。less
返回上一个页面,则调用Navigator.pop(context)就能够从堆栈中删除这个屏幕;ide
Navigator.of(context)
.push(new MaterialPageRoute(builder: (context) {
return new DetailPage();
}));
Navigator.pop(context);
复制代码
若是每次跳转到一个新的路由页面,都要跟上面同样的写法,建立MaterialPageRoute实例而后调用push方法,这样的话就太麻烦了。post
因此,Flutter提供了另一种方式来管理路由,可使用命名路由,而后使用Navigator.pushNamed()方法来弹出路由。学习
建立MaterialApp的时候须要传入一个routes参数,routes本质上是一个Map<String,WidgetBuilder>,key值对应自定义的路径名字,value值会映射到对应的WidgetBuilder,咱们能够在WidgetBuilder中建立对应的页面。动画
Navigator.pushNamed()方法有两个参数(BuildContext,String),第一个是上下文,第二个是在路由中预约义的string。ui
特殊状况处理:当push一个不存在的路由页面的时候,须要进行提示操做。可使用UnknownRoute的属性。好比下面的例子, 当push一个不存在的路由的时候,会跳转到NotFoundPage的页面。
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
routes: {
//Map<String, WidgetBuilder>
"/splash": (context) => new SplashPage(),
"/login": (context) => new LoginPage(),
"/home": (context) => new HomePage(),
"/detail": (context) => new DetailPage(),
},
onUnknownRoute: (RouteSettings setting) {
String name = setting.name;
print("onUnknownRoute:$name");
return new MaterialPageRoute(builder: (context) {
return new NotFoundPage();
});
},
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SplashPage(),
);
}
}
//弹出路由,跳转到其余页面
Navigator.of(context).pushNamed("/detail");
复制代码
好比咱们上面使用的是MaterialPageRoute,在页面切换的时候,会有默认的自适应平台的过渡动画。 若是想自定义页面的进场和出场动画,那么须要使用PageRouteBuilder来建立路由。 PageRouteBuilder是主要的部分,一个是“pageBuilder”,用来建立所要跳转到的页面,另外一个是“transitionsBuilder”,也就是咱们能够自定义的转场效果。
PageRouteBuilder({
RouteSettings settings,
@required this.pageBuilder,//构造页面
this.transitionsBuilder = _defaultTransitionsBuilder,//建立转场动画
this.transitionDuration = const Duration(milliseconds: 300),//转场动画的持续时间
this.opaque = true,//是不是透明的
this.barrierDismissible = false,//举个例子,好比AlertDialog也是利用PageRouteBuilder进行建立的,barrierDismissible若为false,点击对话框周围,对话框不会关闭;若为true,点击对话框周围,对话框自动关闭。
this.barrierColor,
this.barrierLabel,
this.maintainState = true,
}
复制代码
只修改单独一个页面的过渡动画,能够这样操做,例以下面的代码,
// 自定义跳转动画
Navigator.push(
context,
PageRouteBuilder(
opaque: false,
pageBuilder: (BuildContext context, _, __) {
return new HomePage();
},
transitionsBuilder: (___, Animation<double> animation,
____, Widget child) {
return FadeTransition(
opacity: animation,
child: RotationTransition(
turns: Tween<double>(begin: 0.5, end: 1.0)
.animate(animation),
child: child,
),
);
}));
复制代码
在Flutter中,咱们会使用到这些方法,例如[showDialog()], [showMenu()], and [showModalBottomSheet()]等,这些方法其实本质上是建立了一个路由的页面后,并调用Navigator的push方法去push到当前的屏幕上。
showDialog()实际上是调用了showGeneralDialog(),因此下面贴了showGeneralDialog的源码,能够看出,也是利用了Navigator的push方法的。
这里插一下,关于对话框的使用,好比列表对话框,自定义对话框的使用和踩坑,能够看下个人另一篇文章:Flutter之Dialog使用和踩坑
Future<T> showGeneralDialog<T>({
@required BuildContext context,
@required RoutePageBuilder pageBuilder,
bool barrierDismissible,
String barrierLabel,
Color barrierColor,
Duration transitionDuration,
RouteTransitionsBuilder transitionBuilder,
}) {
return Navigator.of(context, rootNavigator: true).push<T>(_DialogRoute<T>(
pageBuilder: pageBuilder,
barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel,
barrierColor: barrierColor,
transitionDuration: transitionDuration,
transitionBuilder: transitionBuilder,
));
}
复制代码
相信你们对栈Stack都有必定的了解,push方法是将元素添加到堆栈的顶部,pop方法是删除顶部元素。
下面用图文的方式来说解Flutter中几个管理栈的方法之间的区别。
push(),就是直接将一个元素插入到堆栈的顶部。
这个方法很简单,而且咱们会常常用到。好比从Screen1中利用navigator的push方法,将Screen2的路由弹到堆栈的顶部。堆栈的状况以下图:
能够利用下面的两个push方法,实现这个目的。
Navigator.of(context).pushNamed("/111");
Navigator.of(context).push(route);
复制代码
pop(), 就是将堆栈的顶部元素进行删除,回退到上一个界面。
好比上面的例子,在Screen2中利用pop()将顶部的Screen2从堆栈中移除,以后的堆栈以下图:
Navigator.of(context).pop();
复制代码
注意:
有下面的这种场景,咱们进入到Screen3页面后,要跳转到Screen4页面,不过点击返回按钮,并不想回退到Screen3页面。也就是想将Screen4的元素插入栈顶的同时,将Screen3的元素夜进行移除。
这个时候,咱们就要用到pushReplacementNamed()或者popAndPushNamed(),pushReplacement()均可以实现这个目的。
Navigator.of(context).pushReplacementNamed('/screen4');
Navigator.popAndPushNamed(context, '/screen4');
Navigator.of(context).pushReplacement(newRoute);
复制代码
通常会有这种场景,咱们在已经登陆的状况下,在设置界面会有个退出用户登陆的按钮,点击后会注销用户退出登陆,而且会跳转到登陆界面。那么路由栈的变化应该会以下图所示:
若是只是简单的进行push一个LoginScreen的操做的话,那么按返回键的话,会回到上一个页面,这样的逻辑是不对的。
因此咱们应该删除掉路由栈中的全部route,而后再弹出LoginScreen。这个时候就要用到pushNamedAndRemoveUntil()或者pushAndRemoveUntil()了。
typedef RoutePredicate = bool Function(Route<dynamic> route);
//第一个参数context是上下文的context,
//第二个参数newRouteName是新的路由所命名的路径
//第三个参数predicate,返回值是bool类型,按照个人理解,就是用来判断Until所结
//束的时机,若是为false的话,就会一直继续执行Remove的操做,直到为true的时候,中止Remove操做,而后才执行push操做。
static Future<T> pushNamedAndRemoveUntil<T extends Object>(BuildContext context, String newRouteName, RoutePredicate predicate) {
return Navigator.of(context).pushNamedAndRemoveUntil<T>(newRouteName, predicate);
}
复制代码
Navigator.of(context).pushNamedAndRemoveUntil('/LoginScreen', (Route<dynamic> route) => false);
复制代码
利用ModalRoute.withName(name),来执行判断,能够看下面的源码,当所传的name跟堆栈中的路由所定义的时候,会返回true值,不匹配的话,则返回false。
Navigator.of(context).pushNamedAndRemoveUntil('/screen4',ModalRoute.withName('/screen1'));
//ModalRoute.withName的源码
static RoutePredicate withName(String name) {
return (Route<dynamic> route) {
return !route.willHandlePopInternally
&& route is ModalRoute
&& route.settings.name == name;
};
}
复制代码
popUntil()方法的过程其实跟上面差很少,就是是少了push一个新页面的操做,只是单纯的进行移除路由操做。
popUntil(RoutePredicate predicate);
Navigator.of(context).popUntil(ModalRoute.withName("/XXX"));
复制代码
这里写了一个Demo,将上面的几种管理栈的用法都运用了一下。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
routes: {
//Map<String, WidgetBuilder>
"/splash": (context) => new SplashPage(),
"/login": (context) => new LoginPage(),
"/home": (context) => new HomePage(),
"/detail": (context) => new DetailPage(),
},
onUnknownRoute: (RouteSettings setting) {
String name = setting.name;
print("onUnknownRoute:$name");
return new MaterialPageRoute(builder: (context) {
return new NotFoundPage();
});
},
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SplashPage(),
);
}
}
复制代码
首先是定义在MaterialApp中定义多个路由,Demo中有多个路由:
下面简单讲一下代码:
用的都是pushReplacementNamed()。由于跳到登陆界面后,不须要返回到SplashPage,因此须要将SplashPage从路由栈中移除。LoginPage->HomePage,也是一样的道理。
Navigator.of(context).pushReplacementNamed("/login");
Navigator.of(context).pushReplacementNamed("/home");
复制代码
使用的是简单的pushNamed()就能够了,不必移除HomePage,由于从DetailPage点击返回后,须要返回到HomePage界面。
//下面的两种写法都是能够的
Navigator.of(context).pushNamed("/detail");
Navigator.of(context)
.push(new MaterialPageRoute(builder: (context) {
return new DetailPage();
}));
复制代码
利用的是从DetailPage点击退出登陆按钮,会弹出路由栈中的全部路由页面,而后再将LoginPage的路由插入到栈顶。这样的话,路由栈中就只剩下LoginPage了,如果点击返回按钮的话,默认就会退出应用程序了,由于堆栈为空了。
Navigator.of(context).pushNamedAndRemoveUntil("/login", (Route<dynamic> route) => false);
复制代码
欢迎你们关注个人公众号,会推送关于Flutter和Android学习的一些文章
学习使用fluro的第三方路由框架,并作下整理和总结。