iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 9
0
Mobile Development

30天手滑用Google Flutter解鎖Hybrid App成就系列 第 9

30天Flutter手滑系列 - 導航與路由(Navigation & Routing)

在結束上一篇30天Flutter手滑系列 - 布局組件(Layout Widgets)(下)後,基礎的UI概念應該已經有了,接下來會介紹同樣很重要的導航與路由。

認識導航Navigator與路由Router

很多時候不管在做Web或Mobile,都需要做跳轉頁面的功能。基本的場景就是當點擊一個連結後,當前頁面會被跳轉到另一個頁面去,例如點擊註冊按鈕後會從首頁/ 跳到註冊頁 /register
而這些被定義的//home/register名稱都是路由(Router)系統的一部分。
管理這些Route的進出,就是導航(Navigator),在這裡也是一個Navigator Widget。


Navigator

用來管理進出頁面的機制,本身是一個Stack(堆疊),利用pushpop操作這個堆疊裡的目標。

假設我有一個App,首先我點擊註冊按鈕 -> 點擊返回上一頁 -> 點擊登入按鈕,這時候的Stack其內容的變化會是如下圖所示。
https://ithelp.ithome.com.tw/upload/images/20190916/20120028uySoYEQlXw.png

下面實做一個跳轉的畫面:

  • 從首頁點擊Register,進到Register Page。
  • 此時Register會被push到Stack。
  • 進到Register Page,Scaffold Widget會自己產生一個返回上一頁的按鈕。
  • 點擊返回。
  • 此時Register會被從Stack中pop出去
  • 回到首頁。
    https://upload.cc/i1/2019/09/16/Ms4LD2.gif
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Navigation Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Page')),
      body: Center(
        child: RaisedButton(
          child: Text('Register'),
          onPressed: () {
            Navigator.push(context,
                MaterialPageRoute(builder: (context) => RegisterPage()));
          },
        ),
      ),
    );
  }
}

class RegisterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Register Page'),
      ),
      body: Center(),
    );
  }
}


Router

路由是對頁面的抽象。
簡單來說,每一頁面都有對應的識別名稱,例如/home/login/register,讓使用者可以被引導到正確的頁面。

路由又分成兩種:

1. 靜態路由

需要直接註冊路由名稱,不能夠被傳遞參數,但可以接收下一頁返回的值。

我們修改上面的範例,加入routes,並在裡面定義一個/register

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Navigation Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
      routes: <String, WidgetBuilder>{'/register': (_) => new RegisterPage()},
    );
  }
}

然後在onPressed事件稍做修改,改調用Navigator.pushNamed這個方法,內容的字串需要跟routes裡面的名稱完全符合。

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Page')),
      body: Center(
        child: RaisedButton(
          child: Text('Register'),
          onPressed: () {
            Navigator.of(context).pushNamed('/register');
          },
        ),
      ),
    );
  }
}

這個結果是跟上面只用push的效果是一樣依樣的。
https://upload.cc/i1/2019/09/17/ouijmq.gif

2. 動態路由

可以傳遞參數,需要自己創建實例。

在這裡示範由HomePage傳遞一段字串到下一頁的效果,然後返回另一段字串內容。

- 傳遞參數

首先,我們修改上一個範例,在pushNamed加入第二個參數arguments,這邊傳遞一個靜態的物件到下一個路由。

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Page')),
      body: Center(
        child: RaisedButton(
          child: Text('Register'),
          onPressed: () {
            Navigator.of(context).pushNamed('/register',
                arguments: {'name': 'Raymond'}).then((value) {
              // 新增第二個變數arguments
              showDialog(
                  // 新增一個對話框,用來顯示回傳的值
                  context: context,
                  child: AlertDialog(
                    content: Text(value),
                  ));
            });
          },
        ),
      ),
    );
  }
}

- 接收參數

在RegisterPage先新增一個變數,用來儲存接收到的參數,然後新增一個按鈕,讓按下按鈕時,可以回傳一組字串。

class RegisterPage extends StatelessWidget {
  String name; // 宣告一個字串變數

  @override
  Widget build(BuildContext context) {
    dynamic obj = ModalRoute.of(context).settings.arguments;
    name = obj["name"]; // 把接收到的參數存到變數

    return Scaffold(
      appBar: AppBar(
        title: Text('Register Page'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Confirm'),
          onPressed: () {
            Navigator.of(context).pop("Hello ${name}"); // 當按下按鈕會返回上一頁,並回傳內容
          },
        ),
      ),
    );
  }
}

- 接收回傳的內容

在MyHomePage的Navigator加入then的方法,在裡面新增一個showDialog,用來顯示接收到的回傳值。

https://upload.cc/i1/2019/09/17/1mwJfN.gif


總結

路由跳轉是很常見的功能,不過概念上就是push跟pop而已。
倒是稍早有查到一篇文章Flutter早知道 - Named Router可以传参了!,裡面提到pushNamed在某個開發版的branch已經可以傳遞參數,但我還沒實驗這部分,如果已經有人有測試過,再麻煩分享一下,或是等我晚點測試看看。


參考資料

https://api.flutter.dev/flutter/widgets/Navigator-class.html
https://blog.whezh.com/flutter-route-and-navigator/
https://medium.com/flutter-community/flutter-navigation-cheatsheet-a-guide-to-named-routing-dc642702b98c


上一篇
30天Flutter手滑系列 - 布局組件(Layout Widgets)(下)
下一篇
30天Flutter手滑系列 - 無狀態與有狀態Widgets (Stateless & Stateful widgets)
系列文
30天手滑用Google Flutter解鎖Hybrid App成就30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言