該文已授權(quán)公眾號 「碼個蛋」,轉(zhuǎn)載請指明出處
上一節(jié)擼了個界面,雖然比較簡單亮元,但是把前面講的知識串聯(lián)了下汁掠,但是界面之間的跳轉(zhuǎn)一直沒說,這節(jié)就講下 Flutter
中的「路由」來管理界面魂务。
Navigator
Flutter
通過 Navigator
來進行頁面之間的跳轉(zhuǎn)曼验,分為 push
系列和 pop
系列操作泌射,帶 push
方法為入棧操作,帶 pop
方法為出棧操作鬓照。Navigator
的 push
方法分兩類熔酷,一類是帶 Name
的,需要在 MaterialApp
下將 routers
屬性進行注冊豺裆,否則將會找不到該路由拒秘,還有一個是不帶 Name
的,可以通過 Router
直接跳轉(zhuǎn)臭猜。
說那么多相信還不如直接上代碼和圖來的更直接躺酒。因為需要展示所有的跳轉(zhuǎn)至少需要 3 個頁面,所以我們創(chuàng)建最簡單的三個界面蔑歌,通過文字來區(qū)別不同的頁面羹应,因為需要調(diào)用帶有 Name
的方法,所以需要先在 MaterialApp
對路由進行注冊次屠。
class DemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Learning Demo',
// 在這里注冊路由园匹,關(guān)聯(lián) name 和界面
// '/' 表示根頁面,也就是 home 所對應(yīng)的頁面劫灶,這邊就不需要配置 home 屬性了
routes: {'/': (_) => APage(), '/page_b': (_) => BPage(), '/page_c': (_) => CPage()},
debugShowCheckedModeBanner: false,
);
}
}
/// Page A裸违,Button 的跳轉(zhuǎn)事件等會進行修改,目前先空著
class APage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page A'),
),
body: Center(child: RaisedButton(onPressed: () {}, child: Text('To Page B'))),
);
}
}
/// Page B
class BPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page B'),
),
body: Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
RaisedButton(onPressed: () {}, child: Text('To Page C')),
RaisedButton(onPressed: () {}, child: Text('Back Page A'))
])),
);
}
}
/// Page C
class CPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page C'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[RaisedButton(onPressed: () {}, child: Text('Back Last Page'))])),
);
}
}
push / pushNamed 方式跳轉(zhuǎn)
我們在 APage
的 RaiseButton
的 onPressed
方法加入如下代碼
Navigator.push(context, MaterialPageRoute(builder: (_) => BPage()));
或者
Navigator.pushNamed(context, '/page_b');
效果相同浑此。跳轉(zhuǎn)后累颂,可以發(fā)現(xiàn),在 BPage
的 AppBar
上有個返回按鈕凛俱,點擊可以返回 APage
紊馏,那么也就是說通過 push
或者 pushNamed
方式跳轉(zhuǎn)的時候,界面堆棧的變化是直接在原來的堆棧上添加一個新的 page
為了凸顯堆棧的變化蒲犬,所以繪制的圖中朱监,會比使用的實際頁面多一個,下圖同
pushReplacement / pushReplacementNamed / popAndPushNamed
將 APage
中的跳轉(zhuǎn)方式進行替換
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => BPage()));
或者
Navigator.pushReplacementNamed(context, '/page_b');
或者
// 如果是第一個界面跳轉(zhuǎn)到下個界面原叮,勿用赫编,`BPage` 會顯示返回按鈕,但是點擊后奋隶,界面會變黑
// 因為 `APage` 已經(jīng)不在堆棧中了擂送,點擊后堆棧就沒有 `Page` 了,所以界面變黑
Navigator.popAndPushNamed(context, '/page_b');
效果相同唯欣,跳轉(zhuǎn)后嘹吨,可以發(fā)現(xiàn) BPage
的返回按鈕消失了,消失了境氢,消失了蟀拷,我們可以試下點擊返回按鍵碰纬,發(fā)現(xiàn) App
直接退出了,也就是說问芬,BPage
替代了 APage
在堆棧中的位置悦析。那么堆棧的變化圖就是這樣的
pushAndRemoveUntil / pushNamedAndRemoveUntil
CASE 1
這個跳轉(zhuǎn)方式需要通過 CPage
來協(xié)助完成,將 APage
的跳轉(zhuǎn)方式修改為 push
方式此衅,然后在 BPage
的第一個按鈕加入如下代碼
Navigator.pushAndRemoveUntil(context,
MaterialPageRoute(builder: (_) => CPage()), (Route router) => false);
或者
Navigator.pushNamedAndRemoveUntil(context, '/page_c', (Route router) => false);
效果相同强戴,點擊 BPage
的跳轉(zhuǎn) CPage
按鈕后,界面來到 CPage
炕柔,然后發(fā)現(xiàn)還是沒有返回按鈕酌泰,沒有返回按鈕,沒有返回按鈕匕累,點擊下返回按鍵陵刹,然后發(fā)現(xiàn) App
直接退出了,退出了欢嘿,退出了衰琐,那么堆棧變化如圖
CASE 2
你以為這兩個方法只是為了把堆棧都清空嗎,那就太圖樣圖森破了炼蹦,這邊展示另一種羡宙。修改跳轉(zhuǎn)的代碼
Navigator.pushAndRemoveUntil(context,
MaterialPageRoute(builder: (_) => CPage()), ModalRoute.withName('/'));
或者
Navigator.pushNamedAndRemoveUntil(context, '/page_c', ModalRoute.withName('/'));
點擊跳轉(zhuǎn) CPage
以后,發(fā)現(xiàn)返回按鈕又回來了...就這么回來了...只是修改了一個參數(shù)掐隐,點擊返回按鈕狗热,又回到了 APage
,你可以在 APage
跳轉(zhuǎn) BPage
中加入DPage
EPage
等等更多的界面虑省,只要保證 BPage
跳轉(zhuǎn) CPage
的方式不變匿刮,點擊 CPage
的返回按鈕,又回到 APage
了探颈,所以...堆棧的變化圖如下
SUMMARY
為什么會這樣變化呢熟丸,還記得在 MaterialApp
中注冊的 router
么,APage
的 name
對應(yīng)的為 '/'
伪节,也就是說光羞,該方法會把堆棧中在 ModalRoute.withName
所對應(yīng)的 page 上的所有都 pop 出堆棧,如果把參數(shù)換成 /page_b
怀大,然后在跳轉(zhuǎn) CPage
之前加入更多的界面纱兑,點擊 CPage
的返回按鈕,就會回到 BPage
QUESTION
這邊再提個小問題化借,有頁面 A潜慎,B,C,D勘纯,其路由的 name 分別為 '/','page_b'钓瞭,'page_c'驳遵,'page_d',啟動順序為 A -> B -> C -> C -> D山涡,那么在 D 頁面使用
Navigator.pushNamedAndRemoveUntil(context, '/page_c', ModalRoute.withName('/page_c'));
那么堆棧最后剩下的頁面是 ABCC
還是ABC
呢堤结?答案會在最后公布,小伙伴可以先自己嘗試著實現(xiàn)鸭丛。
pop
在 BPage
的第二個按鈕中加入 pop
操作
Navigator.pop(context);
跳轉(zhuǎn)到 BPage
后點擊該按鈕竞穷,界面回到 APage
,那么堆棧的變化很明顯了鳞溉,如圖
popUntil
這個方法還需要借助 CPage
瘾带,在 CPage
的按鈕中加入
Navigator.popUntil(context, ModalRoute.withName('/'));
點擊返回按鈕,界面跳過 BPage
回到了 APage
熟菲,解釋同 pushAndRemoveUntil
那么堆棧的變化也顯而易見咯
Navigator 傳值
CASE 1 傳值給下個界面
修改下 BPage
和 APage
的按鈕點擊事件
class BPage extends StatelessWidget {
final String message;
BPage({Key key, @required this.message}) : super(key: key);
@override
Widget build(BuildContext context) {
print('passed value: $message');
return Scaffold(
// 省略相同代碼
);
}
}
// APage 跳轉(zhuǎn)事件
Navigator.push(context, MaterialPageRoute(builder:
(_) => BPage(message: 'Message From Page A')));
點擊 APage
可以查看控制臺有輸出
2019-03-17 00:04:06.854 12868-12888/com.kuky.demo.flutterartsdemosapp I/flutter: passed value: Message From Page A
也就是成功把值傳遞過來了看政。但是,需要傳遞參數(shù)的話抄罕,之前在 MaterialApp
下注冊的路由就需要去除了允蚣。
CASE 2 傳值給上個界面
這邊可以查看下 pop
方法
@optionalTypeArgs
// pop 可以傳入一個可選參數(shù) result,這個 result 也就是回傳給上個頁面的參數(shù)值了
static bool pop<T extends Object>(BuildContext context, [ T result ]) {
return Navigator.of(context).pop<T>(result);
}
既然知道 pop
如何傳遞值給上個界面呆贿,那么如何在上個界面接收這個參數(shù)呢嚷兔,還是看下 push
方法
@optionalTypeArgs
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
return Navigator.of(context).push(route);
}
///
@optionalTypeArgs
Future<T> push<T extends Object>(Route<T> route) {
// ...省略無關(guān)代碼
// 這邊返回一個 Future 值,`pop` 所傳遞的值會在這邊返回
return route.popped;
}
/// The future completes with the value given to [Navigator.pop], if any.
Future<T> get popped => _popCompleter.future;
官方的注釋非常明白的指出做入,會在 Future
中攜帶 pop
傳遞的參數(shù)冒晰,那么我們對 APage
跳轉(zhuǎn) BPage
以及 BPage
返回 APage
的邏輯進行修改
/// APage
Navigator.push(context, MaterialPageRoute(builder: (_)
=> BPage(message: 'Message From Page A')))
.then((value) => print('BACK MESSAGE => $value'));
/// BPage
Navigator.pop(context, 'Message back to PageA From BPage');
點擊返回后,能夠在控制臺發(fā)現(xiàn)有如下輸入
2019-03-17 16:35:53.820 13417-13442/com.kuky.demo.flutterartsdemosapp I/flutter: BACK MESSAGE => Message back to PageA From BPage
上個頁面成功接收到下個頁面回傳的數(shù)據(jù)母蛛。
CASE 3 通過系統(tǒng)返回按鈕傳值
在 CASE 2
情況下翩剪,通過按鈕對返回事件進行監(jiān)聽,那加入我們需求沒有這個按鈕彩郊,只能通過系統(tǒng)默認的返回按鈕前弯,或者物理返回按鍵,那該如何傳值呢秫逝,這里就需要用 WillpopScope
對系統(tǒng)的返回按鈕進行監(jiān)聽恕出。我們對 CPage
做下修改,在 Scaffold
外面包裹一個 WillpopScope
class CPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
appBar: AppBar(
title: Text('Page C'),
),
body: Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
RaisedButton(
onPressed: () {
Navigator.popUntil(context, ModalRoute.withName('/'));
},
child: Text('Back Last Page'))
])),
),
// 這里對系統(tǒng)返回按鈕做監(jiān)聽..
// 如果返回的是 `true` 則相當于 `pop` 操作违帆,返回 `false` 則只執(zhí)行上一步的 `pop` 操作
// 例如雙擊返回退出浙巫,也是通過 `WillpopScope` 來進行監(jiān)聽
onWillPop: () async {
Navigator.pop(context, 'Hello~');
return false;
});
}
}
通過返回按鈕,BPage
會成功收到從 CPage
返回的 Hello~
值
以上代碼查看 router_main.dart
文件
路由切換動畫
假如說我們不想用系統(tǒng)自帶的切換動畫,需要弄一些比較酷炫的效果該怎么辦的畴,那就需要用到自定義路由切換動畫了渊抄。直接修改 BPage
跳轉(zhuǎn) CPage
的代碼
Navigator.push(
context,
PageRouteBuilder(
// 返回目標頁面
pageBuilder: (context, anim, _) => CPage(),
// 切換動畫的切換時長
transitionDuration: Duration(milliseconds: 500),
// 切換動畫的切換效果,系統(tǒng)自帶的常用 Transition
// ScaleTransition: 縮放 SlideTransition: 滑動
// RotationTransition: 旋轉(zhuǎn) FadeTransition: 透明度
transitionsBuilder: (context, anim, _, child) => ScaleTransition(
// Tween 是 flutter 的補間動畫丧裁,等講到動畫的時候再提吧护桦,這邊先記住這么使用
scale: Tween(begin: 0.0, end: 1.0).animate(anim),
// 這個值必須記得要傳,否則會不顯示界面
child: child,
)));
當再次點擊跳轉(zhuǎn)的時候煎娇,切換的動畫就有開始自帶的平滑效果變成縮放效果了二庵。那如果要實現(xiàn)多個動畫呢,例如邊縮放缓呛,邊改變透明度催享,也很容易實現(xiàn),只需要將 child
替換成 Transition
即可
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, anim, _) => CPage(),
transitionDuration: Duration(milliseconds: 500),
transitionsBuilder: (context, anim, _, child) => ScaleTransition(
scale: Tween(begin: 0.0, end: 1.0).animate(anim),
// 替換即可哟绊,如果要加入更多的動畫因妙,替換 `child` 屬性就可以了
child: FadeTransition(
opacity: Tween(begin: 0.0, end: 1.0).animate(anim),
child: child,
),
)));
當然,為了方便重復(fù)利用票髓,需要進行封裝兰迫,例如我們要封裝上面的縮放動畫效果
class ScalePageRoute extends PageRouteBuilder {
final Widget widget;
ScalePageRoute(this.widget)
: super(
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (context, anim, _) => widget,
transitionsBuilder: (context, anim, _, child) => ScaleTransition(
scale: Tween(begin: 0.0, end: 1.0).animate(anim),
child: child,
));
}
然后直接在 Navigator
跳轉(zhuǎn)的時候調(diào)用該 Route
就可以了
該部分代碼查看 custom_routes.dart
文件
還記得我們之前寫的 demo
都是單個文件寫一個入口的嗎,現(xiàn)在我們就可以寫一個統(tǒng)一管理的頁面炬称,對這些界面進行管理了汁果,這個工作就交給大家伙自己了,當然我也在源碼做了修改玲躯,可以查看 main.dart
文件
在前面有提出一個問題据德,這邊公布下答案:堆棧中的頁面應(yīng)該為 ABCC
。你答對沒有呢~
最后代碼的地址還是要的:
文章中涉及的代碼:demos
基于郭神
cool weather
接口的一個項目跷车,實現(xiàn)BLoC
模式棘利,實現(xiàn)狀態(tài)管理:flutter_weather一個課程(當時買了想看下代碼規(guī)范的,代碼更新會比較慢朽缴,雖然是跟著課上的一些寫代碼善玫,但是還是做了自己的修改,很多地方看著不舒服密强,然后就改成自己的實現(xiàn)方式了):flutter_shop
如果對你有幫助的話茅郎,記得給個 Star,先謝過或渤,你的認可就是支持我繼續(xù)寫下去的動力~