本文首先講的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的第三方路由框架,並作下整理和總結。