---
# System prepended metadata

title: Flutter 導航＆路由
tags: [Flutter]

---

---
title: 'Flutter 導航＆路由'
disqus: kyleAlien
---

Flutter 導航＆路由
===

## Overview of Content

**路由（Route）管理就是在 Flutter 移動開發中通常是指頁面的跳轉**，如同我們在開發 Android 時不同的 Module 跳轉就需要一個路由器，而 Flutter 的頁面跳轉也有自己的路由器風格


:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**深入解析 Flutter Navigator：常見錯誤、解決方法與路由跳轉技巧、動畫**](https://devtechascendancy.com/flutter-navigator-guide_routing-animation/)
:::

:::info
以下使用的 Flutter 版本為 `3.22.2`
:::

[TOC]

## 認識 Navigator

Fluttter 使用 Navigator 類來跳轉頁面

### Navigator 錯誤的使用方式

* **以下的範例是一個錯誤的範例**，接下來我們會分析這個錯誤，並將它導向正確
    
    > 以下是程式碼目的是 Page1 頁面，跳轉到 Page2 頁面

    1. Page1 頁面：在這個頁面中做個簡單的 Btn 來跳轉到 Page2 頁面

        ```dart=
        class Page1 extends StatelessWidget {

          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              home: Scaffold(
                appBar: AppBar(title: Text("主頁面")),
                body: RaisedButton(
                  child: Text("跳轉 Page2"),
                  onPressed: () {
                    debugPrint("點擊跳轉按鈕");

                    /// Navigator 也是 Widget
                    // 跳轉到 Page2 頁面
                    Navigator.of(context).push(
                        MaterialPageRoute(  // Route 是抽象類
                          builder: (BuildContext context) {
                            return Page2();
                          }
                        )
                    );

                  },
                )
              ),
            );
          }

        }
        ```

    2. Page2 頁面

        ```dart=
        class Page2 extends StatelessWidget {

          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              home: Scaffold(
                appBar: AppBar(title: Text("頁面")),
                body: Text("頁面 2"),
              ),
            );
          }

        }
        ```
        **--出錯結果--**
        
        :::warning
        錯誤：`Navigator operation requested with a context that does not include a Navigator.`（使用不包含導航器的上下文請求導航器操作）
        :::
        
        > ![](https://i.imgur.com/5b3wk1h.png)

### Navigator 錯誤分析

* 接下來分析 Navigator 錯誤，帶到 `Navigator#of` 方法，就可以發現我們剛剛在外部看得完全一樣的錯誤訊息，並且還可以發現以下事情
    
    1. 在進入 `Navigator#of` 方法時，它會先獲取 `NavigatorState` 物件，而獲取的方法有三種… 
        * 透過 BuildContext#`state` 獲取
        * 透過 BuildContext#`findRootAncestorStateOfType` 獲取
        * 透過 BuildContext#`findAncestorStateOfType` 獲取（等等往下分析）
        
            > 等等主要分析 `findAncestorStateOfType` 函數，操作就像是再呼叫 BuildContext 的父類別，並去尋找其中的  `NavigatorState` 物件
    
            ```dart=
            // navigator.dart 的源碼

              static NavigatorState of(
                BuildContext context, {
                bool rootNavigator = false,
              }) {
                // Handles the case where the input context is a navigator element.
                NavigatorState? navigator;
                if (context is StatefulElement && context.state is NavigatorState) {
                  navigator = context.state as NavigatorState;
                }
                if (rootNavigator) {
                  navigator = context.findRootAncestorStateOfType<NavigatorState>() ?? navigator;
                } else {
                  navigator = navigator ?? context.findAncestorStateOfType<NavigatorState>();
                }

                ... 省略
              }
            ```

    2. 透過 `assert()` 斷言來確保有取得 `NavigatorState` 物件

        ```dart=
        // navigator.dart 的源碼

          static NavigatorState of(
            BuildContext context, {
            bool rootNavigator = false,
          }) {
          
            ... 省略

            assert(() {
              if (navigator == null) {
                throw FlutterError(
                  'Navigator operation requested with a context that does not include a Navigator.\n'
                  'The context used to push or pop routes from the Navigator must be that of a '
                  'widget that is a descendant of a Navigator widget.',
                );
              }
              return true;
            }());
            return navigator!;
          }
        ```
    
    3. 進入 BuildContext#`findAncestorStateOfType` 方法，我們可以發現以下事情
        
        * BuildContext#`findAncestorStateOfType` 方法的實作類是 `Element` 類

            ```mermaid
            classDiagram
                BuildContext <|-- Element: 繼承
                    BuildContext: State state
                BuildContext: +(T extends State) findAncestorStateOfType()
                
                State <.. BuildContext: 依賴、關聯
            ```
            
        * 並且 Element 內部有一個自身類型的成員 `_parent`，而 `findAncestorStateOfType` 方法則透過「鏈結」的方式不斷去尋找其父類別中的哪個類別屬於 T 屬性（也就是尋找 `NavigatorState` 物件）

            :::info
            * 這種設計模式就是 [Chain 責任鏈模式](https://devtechascendancy.com/object-oriented_design_chain_framework/)，有興趣的人可以點擊連結了解這種設計模式

                ```mermaid
                classDiagram
                    BuildContext <|-- Element: 繼承
                    BuildContext: State state
                    BuildContext: +(T extends State) findAncestorStateOfType()

                    State <.. BuildContext: 依賴、關聯
                        
                    Element <|-- Element
                    Element: -Element _parent
                ```
            :::

            ```dart=
            // 可以發現該方法為 abstract 抽象方法 (framework.dart 中的 BuildContext)
              T findAncestorStateOfType<T extends State>();


            // --------------------------------------------------------------------
            // 以下是該方法的實做 (framework.dart 中的 Element)


            abstract class Element extends DiagnosticableTree implements BuildContext {

              Element? _parent;

              ...

              @override
              T findAncestorStateOfType<T extends State<StatefulWidget>>() {
                assert(_debugCheckStateIsActiveForAncestorLookup());

                // "1. " 獲取父節點
                Element ancestor = _parent;
                while (ancestor != null) {
                  if (ancestor is StatefulElement && ancestor.state is T)
                    break;
                  // 2. 不斷往上找
                  ancestor = ancestor._parent;
                }
                final StatefulElement statefulAncestor = ancestor as StatefulElement;
                return statefulAncestor?.state as T;
              }

              ...  

            }
            ```

            :::success
            * **Context & Elements 關係是什麼**？

                1. Elements 這個類是實現了 BuildContext 類
                
                    > 可以發現 BuildContext 的抽象由 Element 實做… 也就是實際顯示的 View (**Widget 最終會創建出 Element 元件**)
            
                    > ![](https://i.imgur.com/5wpei1b.png)
                
                2. 在我們定義的 StatefulWidget、StatelessWidget 中所拿到的 BuildContext 就是 Elements 的 BuildContext

                    > ![](https://i.imgur.com/sxUoBCp.png)

                3. StatefulWidget、StatelessWidget 中的 context 其實就是當前頁面
                    
                    > ![](https://i.imgur.com/pfkfrZT.png)

            :::

            :::info
            * 最終可以發現 Navigator 跳轉的關鍵在於 BuildContext 上下文的尋找到 `state` 成員為 `NavigatorState` 物件才可以正常跳轉
            :::

* 使用 Debug 模式來觀察 BuildContext#`_parent` 是哪個類，可以發現 Page1 它的父親是 RenderView (Root View)，它類似於 Android 布局中的 Decor View（畫面的根佈局）

    > ![](https://i.imgur.com/dM9ksUK.png)

    而 RenderView（Root View）的上一層就沒有其他的 Element，導致返回為 null，這也是為何無法正常跳轉的原因，因為在所有父類中根本找不到有哪個類是 `NavigatorState` 物件

    > ![](https://i.imgur.com/sLBr01s.png)
    
    :::warning
    * 嘗試改為 StatelessWidget 修改為 StatefulWidget 有用嗎？
        
        嘗試改為 StatefulWidget，會發現不管用，因為**不管是 StatelessWidget / StatefulWidget 都是相同的 RootView**
        ```dart=
        // 該程式輸出結果同樣錯誤，無法跳轉

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

        class FixFul extends StatefulWidget {

          @override
          FixFulState createState() => FixFulState();

        }

        class FixFulState extends State<FixFul> {

          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              home: Scaffold(
                  appBar: AppBar(title: Text("主頁面")),
                  body: RaisedButton(
                    child: Text("跳轉 Page2"),
                    onPressed: () {
                      debugPrint("點擊跳轉按鈕");
                      /// Navigator 也是 Widget
                      Navigator.of(context).push(
                          MaterialPageRoute(  // Route 是抽象類
                              builder: (BuildContext context) {
                                return Page2();
                              }
                          )
                      );
                    },
                  )
              ),
            );
          }

        }
        ```
    :::

### Navigator 跳轉失敗的解決方法：自訂 Navigator、層級分離

* 透過上面 Navigator 跳轉失敗的原因，我們就可以知道，是因為父類中沒有任何一個類有實作 `NavigatorState` 類，這才導致跳轉失敗，而解決這個問題的方法有以下幾中

    1. 自己定義 Navigator：自己創建一個 `GlobalKey<NavigatorState>` 物件，並設定給 MaterialApp#`navigatorKey` 成員
        
        ```dart=
        import 'package:flutter/material.dart';

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

        class Page1 extends StatelessWidget {

          /// 自定義導航 Key
          final GlobalKey<NavigatorState> navigatorKey = GlobalKey();

          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              /// 賦予 MaterialApp  Key
              navigatorKey: navigatorKey,

              home: Scaffold(
                appBar: AppBar(title: Text("主頁面")),
                body: RaisedButton(
                  child: Text("跳轉 Page2"),
                  onPressed: () {
                    debugPrint("navigatorKey type: ${navigatorKey.currentWidget.runtimeType.toString()}");

                    navigatorKey.currentState.push(MaterialPageRoute(
                        builder: (BuildContext context) {
                          return Page2();
                        }
                    ));
                  },
                )
              )
            );
          }

        }

        class Page2 extends StatelessWidget {

          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              home: Scaffold(
                appBar: AppBar(title: Text("頁面")),
                body: RaisedButton(
                  child: Text("返回頁面"),
                  onPressed: (){
                    Navigator.pop(context);   // 回到上個頁面
                  },
                ),
              ),
            );
          }
        }
        ```
    
    2. **層級進行分離**：透過在 MaterialApp 與 Page1 中間夾帶另一個 Widget 的方式進行跳轉，以下舉兩個使用層級分離的方案
    
        * 透過 `Builder` 來包裝頁面：透過 Builder 中的 `builder` 成員給予的 BuildContext 來跳轉頁面

            ```dart=
            import 'package:flutter/material.dart';

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

            class Page1 extends StatelessWidget {

              Widget builder(BuildContext context) {
                return Scaffold(
                    appBar: AppBar(title: Text("主頁面")),
                    body: RaisedButton(
                      child: Text("跳轉 Page2"),
                      onPressed: () {
                        debugPrint("點擊跳轉按鈕");

                        ///  此時修改傳入的 context 就是 Builder，Builder 的 Parent 就是 MaterialApp
                        Navigator.of(context).push(
                            MaterialPageRoute(
                                builder: (BuildContext context) {
                                  return Page2();
                                }
                            )
                        );
                      },
                    )
                );
              }

              @override
              Widget build(BuildContext context) {
                return MaterialApp(
                  // Builder 是一個基礎 Widget
                  home: Builder(
                    builder: builder,
                  )
                );
              }

            }

            class Page2 extends StatelessWidget {

              @override
              Widget build(BuildContext context) {
                return MaterialApp(
                  home: Scaffold(
                    appBar: AppBar(title: Text("頁面")),
                    body: Text("頁面 2"),
                  ),
                );
              }
            }
            ```
            **--實做結果--**
            > ![](https://i.imgur.com/rGgkNkt.png)

            :::info
            * 而 Builder 並非一定要放置在範例程式中的位置，**觀念是 Context 上下文的尋找，++只要放置在 MaterialApp 的下層即可++**
            :::

        * **自訂中間層**：Builder 是個 Widget，而我們也可以自訂一個 Widget
        
            ```dart=
            import 'package:flutter/material.dart';

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

            class CoverPage extends StatelessWidget {

              @override
              Widget build(BuildContext context) {
                return Scaffold(
                    appBar: AppBar(title: Text("主頁面")),
                    body: RaisedButton(
                      child: Text("跳轉 Page2"),
                      onPressed: () {
                        debugPrint("點擊跳轉按鈕");

                        /// 此時傳入的 context 就是 CoverPage's context，其上一級就是 Material App
                        Navigator.of(context).push(
                            MaterialPageRoute(
                                builder: (BuildContext context) {
                                  return Page2();
                                }
                            )
                        );
                      },
                    )
                );
              }

            }

            class Page1 extends StatelessWidget {

              @override
              Widget build(BuildContext context) {
                return MaterialApp(
                    home: CoverPage()
                );
              }

            }

            class Page2 extends StatelessWidget {

              @override
              Widget build(BuildContext context) {
                return MaterialApp(
                  home: Scaffold(
                    appBar: AppBar(title: Text("頁面")),
                    body: Text("頁面 2"),
                  ),
                );
              }
            }
            ```
            **--實做結果--**
            > ![](https://i.imgur.com/buWBxVD.png)

### 層級分離概念

* 接著我們也會會好奇為什麼只要套用在自己的 Widget 與 MaterialApp 中間夾帶一層中間層 Widget 就可以就可以正常跳轉？

    在最上面我們失敗的實驗中可以看到 Navigator.of 的操作是往父類 Widget 搜尋 Router（它預設不會使用搜尋創建的 Router），而 MaterialApp 的父類 Widget 就是根佈局
    
    > 根佈局（RenderView）不會有 Router 可用
    
    ```mermaid
    graph LR

    subgraph RenderView
    
    render_view

        subgraph MaterialApp
        Navigator.of
        Router
        context
        end 

    end 
    
    Navigator.of -.-> |往上層找| context
    
    context -.-> |找不到 Router| render_view
    ```
    
* 使用層級分離的技巧後，Navigator.of 的上下文就會在層級內部，這就導致 Navigator.of 可以找到 MaterialApp 的 Router


    ```mermaid
    graph LR

    subgraph RenderView

        subgraph MaterialApp
        Router
        
            subgraph 層級 Widget
            Navigator.of
            context
            end
        end 

    render_view

    end 
    
    Navigator.of -.-> |往上層找| context
    
    context -.-> |找到 Router| Router
    ```
    

### 深究 MaterialApp 創建的 NavigatorState
 
* MaterialApp 本身其實就在創建時附帶有路由器（`NavigatorState`）的功能，而這裡我們就來挖掘一下 MaterialApp 創建 NavigatorState 的時機點

    MaterialApp 是一個 StatefulWidget，所以我們從它創建 State 的 `createState` 函數開始
    
    :::success
    * 為什麼從 `createState` 函數開始？
    
        因為這個 `createState` 函數創建出來的 State 會被賦予到 Element#`_state` 中，最終會影響到 BuildContext#`state`，也就會影響是否可以正確地找到 `NavigatorState` 物件
    :::
    
    1. MaterialApp#`createState` 函數會創建 `_MaterialAppState` 物件
    
        ```dart=
        // material/app.dart
        
          @override
          State<MaterialApp> createState() => _MaterialAppState();
        ```
    
        > ![image](https://hackmd.io/_uploads/SJfJbOpsC.png)

    2. _MaterialAppState#`build` 函數會創建 `WidgetsApp` 物件

        ```dart=
        // material/app.dart
        
          @override
          Widget build(BuildContext context) {
            Widget result = _buildWidgetApp(context);

            ... 省略部分
          }
          
          Widget _buildWidgetApp(BuildContext context) {
            ... 省略部分
            
            return WidgetsApp(...);
          }
        ```
        
    3. 而 WidgetsApp 也是 `StatefulWidget`，所以同樣從 `createState` 函數開始看… 可以看到它創建了 `_WidgetsAppState` 物件

        ```dart=
        // widgets/app.dart

          @override
          State<WidgetsApp> createState() => _WidgetsAppState();
        ```
        
        幾著繼續看 _WidgetsAppState#`build` 方法，可以看到在沒有代理的情況下，它會創建一個 `Navigator` 物件作為 Widget
    
        ```dart=
        // widgets/app.dart

          @override
          Widget build(BuildContext context) {
            Widget? routing;
            if (_usesRouterWithDelegates) {
              ... 省略

            } else if (_usesNavigator) {
              assert(_navigator != null);

              routing = FocusScope(
                debugLabel: 'Navigator Scope',
                autofocus: true,
                child: Navigator(              // 創建 Navigator 類      
                  ... 省略
                ),
              );
            }
        ```
    
    4. 最後，我們可以在 Navigator 類中的 `createState` 函數中，看到 NavigatorState 被創建出來！！

        ```dart=
        // navigator.dart
        
          @override
          NavigatorState createState() => NavigatorState();
        ```
        
        > ![image](https://hackmd.io/_uploads/rJxmIOpi0.png)

## 命名路由

另外一種不透過層級分離的技巧就可以跳轉頁面的方式，就是透過設定「命名路由」來跳轉

### 命名路由使用範例

* 路由針對每個頁面取名，然後**通過頁面名子傳遞給路由器就可以直接開啟**，跳轉頁面使用 Navigator#pushName 方法

    範例如下：

    ```dart=
    import 'package:flutter/material.dart';

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

    class MyApp extends StatelessWidget {

      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: "Flutter_Router_Demo",
          /// routes 原型：final Map<String, WidgetBuilder>
          /// WidgetBuilder 原型：typedef WidgetBuilder = Widget Function(BuildContext context);
          routes: {
            "/": (BuildContext context) => MainPage(),
            "Page1": (BuildContext context) => Page1(),
          },
        );
      }

    }


    class MainPage extends StatelessWidget {

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Build")),
          body: Column(
            children: <Widget>[
              Text("主頁面"),
              RaisedButton(
                child: Text("跳轉"),
                onPressed: () {
                  debugPrint("跳轉頁面");
                  Navigator.pushNamed(context, "Page1");
                },
              )
            ],
          ),
        );
      }

    }

    class Page1 extends StatelessWidget {

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Build")),
          body: Column(
            children: <Widget>[
              Text("頁面二"),
              RaisedButton(
                child: Text("返回"),
                onPressed: () {
                  debugPrint("返回頁面");
                  Navigator.pop(context);
                  //Navigator.pushNamed(context, "/");
                },
              )
            ],
          ),
        );
      }

    }
    ```
    **--實做結果--**
    > 跳轉成功
    > 
    > ![](https://i.imgur.com/H4Wu04o.png)


    :::warning
    * 命名路由中 `/`是一種特殊的名子，它代表了「**主頁面**」，有了 `/` 就不能使用 Material 的 home 屬性，否則就會報錯

        > ![](https://i.imgur.com/q5sRCKE.png)

    :::

## Navigator 跳轉結果

在 Android `StartActivityForResult` 可以查看跳轉的結果，而 Flutter 則更加的方便，在轉跳的 `Navigator#push` or `Navigator#pushName` 方法返回的是一個 Future 物件，配合使用 `async`、`await` 就可以堵塞並獲取結果

> ![](https://i.imgur.com/C3rSlrs.png)

### 取得 Navigator 跳轉結果：範例一

* 取得 Navigator 跳轉結果範例如下

    1. 設定基礎的命名路由

        ```dart=
        import 'package:flutter/material.dart';

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

        class MyApp extends StatelessWidget {

          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              title: "Flutter_Router_Demo",
              /// routes 原型：final Map<String, WidgetBuilder>
              /// WidgetBuilder 原型：typedef WidgetBuilder = Widget Function(BuildContext context);
              routes: {
                "/": (BuildContext context) => MainPage(),
                "Page1": (BuildContext context) => Page1(),
              },
              home: MainPage(),
            );
          }

        }
        ```

    2. 設定跳轉頁面，並使用 `async`、`await` 關鍵字來等待該頁面返回的結果

        ```dart=
        class MainPage extends StatelessWidget {

          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(title: Text("Build")),
              body: Column(
                children: <Widget>[
                  Text("主頁面"),
                  RaisedButton(
                    child: Text("跳轉"),

                    /// 若沒有使用 async await 則無法接收轉跳參數
                    onPressed: () async {
                      debugPrint("跳轉頁面");
                      var r = await Navigator.pushNamed(context, "Page1");

                      debugPrint("Finish: $r");
                    },
                  )
                ],
              ),
            );
          }

        }
        ```

    3. 頁面在返回時使用 `Navigator.pop`，並設定返回的數據，在返回之後 `MainPage` 就可以取得 `Page1` 返回的數據

        ```dart=
        class Page1 extends StatelessWidget {

          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(title: Text("Build")),
              body: Column(
                children: <Widget>[
                  Text("頁面二"),
                  RaisedButton(
                    child: Text("返回"),
                    onPressed: () {
                      debugPrint("返回頁面");

                      /// 返回參數
                      Navigator.pop(context, "Page1 Finish");
                    },
                  )
                ],
              ),
            );
          }

        }
        ```
        **--實做結果--**
        > ![](https://i.imgur.com/dnC5i6S.png)

### Navigator 返回數據：範例二

* Navigator 返回數據的第二個範例如下

    ```dart=
    import 'package:flutter/material.dart';

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

    class MyFirstNavigator extends StatelessWidget {

      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: "Navigator",
          home: Scaffold(
            appBar: AppBar(
              title: const Text("First Page")
            ),
            body: Builder(builder: (context) {    // 使用 Builder 的 Context
              return RaisedButton(
                onPressed: () {
                  _waitNavigatorData(context);
                },
                child: Text("Jump to second page."),
              );
            })
          ),
        );
      }

      void _waitNavigatorData(BuildContext context) async {
        var push = await Navigator.push(
            context,
            MaterialPageRoute(
                builder: (context) => MySecondNavigator()
            )
        );

        Scaffold.of(context).showSnackBar(
            new SnackBar(content: new Text("$push"))
        );
      }
    }

    class MySecondNavigator extends StatelessWidget {

      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: "Navigator",
          home: Scaffold(
              appBar: AppBar(
                  title: const Text("First Page")
              ),
              body: Center(
                child: RaisedButton(
                  onPressed: () {
                    // 回傳數值 泛型 T
                    Navigator.pop(context, "Hello Navigator");
                  },
                  child: Text("Return Msg."),
                ),
              )
          ),
        );
      }

    }
    ```
    > ![](https://i.imgur.com/G4iIrWJ.png)


## 路由跳轉動畫

上面我們在 Router 中跳轉頁面是使用 Material 風格的動畫 (也就是 MaterialPageRoute 類)


### 自動路由跳轉動畫

* 如果要自訂動畫 **需要透過 `PageRouteBuilder` Widget 設定**，範例如下：以下動畫效果為「翻頁」第二頁由下至上反滾下來

    ```dart=
    import 'package:flutter/material.dart';

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

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Flutter Demo',
          home: MainRoute(),
        );
      }
    }

    class MainRoute extends StatelessWidget {

      Route routeWithAnimation() {
        return PageRouteBuilder(
          /// 設定動畫時間
          transitionDuration: Duration(milliseconds: 1500),
          /// 原型如下：
          /// typedef RoutePageBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);

          pageBuilder: (context, animation, secondaryAnimation) {
            /// SlideTransition 平移
            return SlideTransition(
              /// Tween: 補間動畫
              position: Tween<Offset>(
                /// 上至下的平移
                begin: const Offset(0.0, -1.0),    // 開始點
                end: const Offset(0.0, 0.0),      // 結束點
              ).animate(animation),

              /// 目標跳轉界面
              child: Page1(),
            );
          }
        );
      }

      @override
      Widget build(BuildContext context) {

        return Scaffold(
          appBar: AppBar(title: Text("Main Page")),
          body: Center (
            child: Column(
              children: <Widget>[
                Text("主頁面"),
                RaisedButton(
                  child: Text("跳轉"),
                  onPressed: () async {
                    debugPrint("頁面跳轉");
                    var r = await Navigator.of(context).push(
                        // 呼叫 route 動畫
                        routeWithAnimation()
                    );
                    debugPrint("頁面跳轉結束：$r");
                  },
                )
              ],
            ),
          ),
        );
      }

    }

    class Page1 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Second Page")),
          body: Center (
            child: Column(
              children: <Widget>[
                Text("子頁面"),
                Builder(
                  builder: (context) {
                    return RaisedButton(
                      child: Text("返回"),
                      onPressed: () {
                        Navigator.pop(context, "Hey~ I'm Second Page");
                      },
                    );
                  },
                ),
              ],
            ),
          ),
        );
      }

    }
    ```

### 自訂跳轉組合動畫

* 在動畫中的 child 再嵌入動畫，最內層的動畫再呼叫目標頁面; 可依照這個概念在嵌套更多的動畫，範例如下：以下使用「**使用淡入動畫 + 平移動畫**」

    ```dart=
    import 'package:flutter/material.dart';

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

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Flutter Demo',
          home: MainRoute(),
        );
      }
    }

    class MainRoute extends StatelessWidget {

      Route myAnimation() {
        return PageRouteBuilder(
          /// 設定動畫時間
          transitionDuration: Duration(milliseconds: 1500),
          /// 原型如下：
          /// typedef RoutePageBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);
            pageBuilder: (context, animation, secondaryAnimation) {
              /// SlideTransition 平移
              return SlideTransition(
                /// Tween: 補間動畫
                position: Tween<Offset>(
                  /// 左至右的平移
                  begin: const Offset(1.0, 0.0),    // 開始點
                  end: const Offset(0.0, 0.0),      // 結束點
                ).animate(animation),

                /// 目標跳轉界面
                child: new FadeTransition(
                  opacity: animation,
                  child: Page1(),
                ),
              );
            }
        );
      }

      @override
      Widget build(BuildContext context) {

        return Scaffold(
          appBar: AppBar(title: Text("Main Page")),
          body: Center (
            child: Column(
              children: <Widget>[
                Text("主頁面"),
                RaisedButton(
                  child: Text("跳轉"),
                  onPressed: () async {
                    debugPrint("頁面跳轉");
                    var r = await Navigator.of(context).push(
                        myAnimation()
                    );
                    debugPrint("頁面跳轉結束：$r");
                  },
                )
              ],
            ),
          ),
        );
      }

    }

    class Page1 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Second Page")),
          body: Center (
            child: Column(
              children: <Widget>[
                Text("子頁面"),
                Builder(
                  builder: (context) {
                    return RaisedButton(
                      child: Text("返回"),
                      onPressed: () {
                        Navigator.pop(context, "Hey~ I'm Second Page");
                      },
                    );
                  },
                ),
              ],
            ),
          ),
        );
      }

    }
    ```






## Appendix & FAQ

:::info
:::

###### tags: `Flutter`