Flutter 入門指北(Part 6) 之路由

該文已授權(quán)公眾號 「碼個蛋」,轉(zhuǎn)載請指明出處

上一節(jié)擼了個界面,雖然比較簡單亮元,但是把前面講的知識串聯(lián)了下汁掠,但是界面之間的跳轉(zhuǎn)一直沒說,這節(jié)就講下 Flutter 中的「路由」來管理界面魂务。

Navigator

Flutter 通過 Navigator 來進行頁面之間的跳轉(zhuǎn)曼验,分為 push 系列和 pop 系列操作泌射,帶 push 方法為入棧操作,帶 pop 方法為出棧操作鬓照。Navigatorpush 方法分兩類熔酷,一類是帶 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)

我們在 APageRaiseButtononPressed 方法加入如下代碼

Navigator.push(context, MaterialPageRoute(builder: (_) => BPage()));

或者

Navigator.pushNamed(context, '/page_b');

效果相同浑此。跳轉(zhuǎn)后累颂,可以發(fā)現(xiàn),在 BPageAppBar 上有個返回按鈕凛俱,點擊可以返回 APage 紊馏,那么也就是說通過 push 或者 pushNamed 方式跳轉(zhuǎn)的時候,界面堆棧的變化是直接在原來的堆棧上添加一個新的 page

為了凸顯堆棧的變化蒲犬,所以繪制的圖中朱监,會比使用的實際頁面多一個,下圖同

Navigator_push.png
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 在堆棧中的位置悦析。那么堆棧的變化圖就是這樣的

Navigator_pushReplacement.png
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 直接退出了,退出了欢嘿,退出了衰琐,那么堆棧變化如圖

Navigator_pushAndRemoveUnit1.png
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 了探颈,所以...堆棧的變化圖如下

Navigator_pushAndRemoveUnit2.png
SUMMARY

為什么會這樣變化呢熟丸,還記得在 MaterialApp 中注冊的 router 么,APagename 對應(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,那么堆棧的變化很明顯了鳞溉,如圖

Navigator_pop.png
popUntil

這個方法還需要借助 CPage 瘾带,在 CPage 的按鈕中加入

Navigator.popUntil(context, ModalRoute.withName('/'));

點擊返回按鈕,界面跳過 BPage 回到了 APage熟菲,解釋同 pushAndRemoveUntil 那么堆棧的變化也顯而易見咯

Navigator_popUntil.png

Navigator 傳值

CASE 1 傳值給下個界面

修改下 BPageAPage 的按鈕點擊事件

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。你答對沒有呢~

最后代碼的地址還是要的:

  1. 文章中涉及的代碼:demos

  2. 基于郭神 cool weather 接口的一個項目跷车,實現(xiàn) BLoC 模式棘利,實現(xiàn)狀態(tài)管理:flutter_weather

  3. 一個課程(當時買了想看下代碼規(guī)范的,代碼更新會比較慢朽缴,雖然是跟著課上的一些寫代碼善玫,但是還是做了自己的修改,很多地方看著不舒服密强,然后就改成自己的實現(xiàn)方式了):flutter_shop

如果對你有幫助的話茅郎,記得給個 Star,先謝過或渤,你的認可就是支持我繼續(xù)寫下去的動力~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末系冗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子薪鹦,更是在濱河造成了極大的恐慌掌敬,老刑警劉巖惯豆,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奔害,居然都是意外死亡楷兽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門华临,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拄养,“玉大人,你說我怎么就攤上這事银舱。” “怎么了跛梗?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵寻馏,是天一觀的道長。 經(jīng)常有香客問我核偿,道長诚欠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任漾岳,我火速辦了婚禮轰绵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尼荆。我一直安慰自己左腔,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布捅儒。 她就那樣靜靜地躺著液样,像睡著了一般。 火紅的嫁衣襯著肌膚如雪巧还。 梳的紋絲不亂的頭發(fā)上鞭莽,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音麸祷,去河邊找鬼澎怒。 笑死,一個胖子當著我的面吹牛阶牍,可吹牛的內(nèi)容都是我干的喷面。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼走孽,長吁一口氣:“原來是場噩夢啊……” “哼乖酬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起融求,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤咬像,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體县昂,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡肮柜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了倒彰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片审洞。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖待讳,靈堂內(nèi)的尸體忽然破棺而出芒澜,到底是詐尸還是另有隱情,我是刑警寧澤创淡,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布痴晦,位于F島的核電站,受9級特大地震影響琳彩,放射性物質(zhì)發(fā)生泄漏誊酌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一露乏、第九天 我趴在偏房一處隱蔽的房頂上張望碧浊。 院中可真熱鬧,春花似錦瘟仿、人聲如沸箱锐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瑞躺。三九已至,卻和暖如春兴想,著一層夾襖步出監(jiān)牢的瞬間幢哨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工嫂便, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捞镰,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓毙替,卻偏偏與公主長得像岸售,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子厂画,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容