深入解析 Flutter Navigator:常見錯誤、解決方法與路由跳轉技巧、動畫

深入解析 Flutter Navigator:常見錯誤、解決方法與路由跳轉技巧、動畫

Overview of Content

在這篇文章中,我們將深入探討 Flutter 中的 Navigator,帶你了解常見的使用錯誤及其分析,並提供實用的解決方法,如自訂 Navigator 和層級分離

此外,我們還會解釋如何正確使用命名路由,以及如何獲取跳轉結果。對於注重用戶體驗的開發者,我們特別介紹了路由跳轉動畫的設計,包括自動跳轉動畫和自訂跳轉組合動畫… 無論你是 Flutter 的新手還是有經驗的開發者,本指南都能幫助你提升應用的導航體驗,避免常見的陷阱並優化整體的用戶流暢度。

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

以下使用的 Flutter 版本為 3.22.2

寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀

個人程式分享時比較注重「縮排」,所以可能不適合手機的排版閱讀,建議切換至「電腦版」、「平板版」視窗看


認識 Navigator

Fluttter 使用 Navigator 類來跳轉頁面

Navigator 錯誤的使用方式

以下的範例是一個錯誤的範例,接下來我們會分析這個錯誤,並將它導向正確

以下是程式碼目的是 Page1 頁面,跳轉到 Page2 頁面

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


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();
                  }
                )
            );

          },
        )
      ),
    );
  }

}

B. Page2 頁面


class Page2 extends StatelessWidget {

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

}

--出錯結果--

錯誤:Navigator operation requested with a context that does not include a Navigator.(使用不包含導航器的上下文請求導航器操作)

Navigator 錯誤分析

● 接下來分析 Navigator 錯誤,帶到 Navigator#of 方法,就可以發現我們剛剛在外部看得完全一樣的錯誤訊息,並且還可以發現以下事情

A. 在進入 Navigator#of 方法時,它會先獲取 NavigatorState 物件,而獲取的方法有三種…

● 透過 BuildContext#state 獲取

● 透過 BuildContext#findRootAncestorStateOfType 獲取

● 透過 BuildContext#findAncestorStateOfType 獲取(等等往下分析)

等等主要分析 findAncestorStateOfType 函數,操作就像是再呼叫 BuildContext 的父類別,並去尋找其中的 NavigatorState 物件


// 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>();
    }

    ... 省略
  }

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


// 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!;
  }

C. 進入 BuildContext#findAncestorStateOfType 方法,我們可以發現以下事情

● BuildContext#findAncestorStateOfType 方法的實作類是 Element

classDiagram BuildContext <|-- Element: 繼承 BuildContext: State state BuildContext: +(T extends State) findAncestorStateOfType() State <.. BuildContext: 依賴、關聯

● 並且 Element 內部有一個自身類型的成員 _parent,而 findAncestorStateOfType 方法則透過「鏈結」的方式不斷去尋找其父類別中的哪個類別屬於 T 屬性(也就是尋找 NavigatorState 物件)

● 這種設計模式就是 Chain 責任鏈模式,有興趣的人可以點擊連結了解這種設計模式

classDiagram BuildContext <|-- Element: 繼承 BuildContext: State state BuildContext: +(T extends State) findAncestorStateOfType() State <.. BuildContext: 依賴、關聯 Element <|-- Element Element: -Element _parent

// 可以發現該方法為 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;
  }

  ...  

}

Context & Elements 關係是什麼

A. Elements 這個類是實現了 BuildContext 類

可以發現 BuildContext 的抽象由 Element 實做… 也就是實際顯示的 View (Widget 最終會創建出 Element 元件)

B. 在我們定義的 StatefulWidget、StatelessWidget 中所拿到的 BuildContext 就是 Elements 的 BuildContext

C. StatefulWidget、StatelessWidget 中的 context 其實就是當前頁面

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

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

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

● 嘗試改為 StatelessWidget 修改為 StatefulWidget 有用嗎?

嘗試改為 StatefulWidget,會發現不管用,因為不管是 StatelessWidget / StatefulWidget 都是相同的 RootView


// 該程式輸出結果同樣錯誤,無法跳轉

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 Key、層級分離

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

A. 自己定義 Navigator:自己創建一個 GlobalKey<NavigatorState> 物件,並設定給 MaterialApp#navigatorKey 成員


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);   // 回到上個頁面
          },
        ),
      ),
    );
  }
}

B. 層級進行分離:透過在 MaterialApp 與 Page1 中間夾帶另一個 Widget 的方式進行跳轉,以下舉兩個使用層級分離的方案

● 透過 Builder 來包裝頁面:透過 Builder 中的 builder 成員給予的 BuildContext 來跳轉頁面


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"),
      ),
    );
  }
}

--實做結果--

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

自訂中間層:Builder 是個 Widget,而我們也可以自訂一個 Widget


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"),
      ),
    );
  }
}

--實做結果--

層級分離概念

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

在最上面我們失敗的實驗中可以看到 Navigator.of 的操作是往父類 Widget 搜尋 Router(它預設不會使用搜尋創建的 Router),而 MaterialApp 的父類 Widget 就是根佈局

根佈局(RenderView)不會有 Router 可用

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

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 函數開始

● 為什麼從 createState 函數開始?

因為這個 createState 函數創建出來的 State 會被賦予到 Element#_state 中,最終會影響到 BuildContext#state,也就會影響是否可以正確地找到 NavigatorState 物件

A. MaterialApp#createState 函數會創建 _MaterialAppState 物件


// material/app.dart

  @override
  State<MaterialApp> createState() => _MaterialAppState();

image

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


// material/app.dart

  @override
  Widget build(BuildContext context) {
    Widget result = _buildWidgetApp(context);

    ... 省略部分
  }
  
  Widget _buildWidgetApp(BuildContext context) {
    ... 省略部分
    
    return WidgetsApp(...);
  }

C. 而 WidgetsApp 也是 StatefulWidget,所以同樣從 createState 函數開始看… 可以看到它創建了 _WidgetsAppState 物件


// widgets/app.dart

  @override
  State<WidgetsApp> createState() => _WidgetsAppState();

幾著繼續看 _WidgetsAppState#build 方法,可以看到在沒有代理的情況下,它會創建一個 Navigator 物件作為 Widget


// 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 類      
          ... 省略
        ),
      );
    }

D. 最後,我們可以在 Navigator 類中的 createState 函數中,看到 NavigatorState 被創建出來!!


// navigator.dart

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

image


命名路由

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

命名路由使用範例

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

範例如下:


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, "/");
            },
          )
        ],
      ),
    );
  }

}

--實做結果--

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

Navigator 跳轉結果

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


取得 Navigator 跳轉結果:範例一

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

A. 設定基礎的命名路由


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(),
    );
  }

}

B. 設定跳轉頁面,並使用 asyncawait 關鍵字來等待該頁面返回的結果


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");
            },
          )
        ],
      ),
    );
  }

}

C. 頁面在返回時使用 Navigator.pop,並設定返回的數據,在返回之後 MainPage 就可以取得 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, "Page1 Finish");
            },
          )
        ],
      ),
    );
  }

}

--實做結果--

Navigator 返回數據:範例二

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


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."),
            ),
          )
      ),
    );
  }

}

路由跳轉動畫

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

自動路由跳轉動畫

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


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 再嵌入動畫,最內層的動畫再呼叫目標頁面; 可依照這個概念在嵌套更多的動畫,範例如下:以下使用「使用淡入動畫 + 平移動畫


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");
                  },
                );
              },
            ),
          ],
        ),
      ),
    );
  }

}

更多的 Flutter/Dart 語言相關文章

了解 Flutter 如何在跨平台開發中佔據重要地位,掌握快速上手的技巧與項目建置流程,開啟你的跨平台開發之旅!

探索跨平台與 Flutter 技術的未來:從認識到 Flutter 專案建置 | 3 種跨平台

Dart 語言基礎

探討 Dart 語言:宣告、數據類型、操作符 | 從基礎到應用指南

快速掌握 Dart 語言的核心概念,包括變數宣告、數據類型及操作符,為 Flutter 開發奠定扎實基礎。

Dart 函數與方法、異常處理、引用庫 | Java 比較

深入了解 Dart 的函數與異常處理特性,並與 Java 的處理方式進行比較,幫助你跨語言切換更加順暢。

深入解析 Dart 語言:命名慣例、類特性、建構函數與抽象特性

學習 Dart 類的設計邏輯及命名慣例,深入探索抽象類與 Mixin 的強大應用場景。

深入探索 Dart 的併發與異步處理:從 Isolate 到 Event Loop 的全面指南 | Future、Stream

徹底搞懂 Dart 的併發與異步處理,掌握 Isolate 與 Event Loop 的運行機制,助你提升應用效能!

深入 Flutter 框架

深入解析 Flutter Navigator:常見錯誤、解決方法與路由跳轉技巧、動畫

從常見問題到自定義解決方案,學會如何利用 Navigator 實現路由跳轉與流暢動畫效果。

深入理解 Flutter 中的數據共享:從普遍方案到 InheritedWidget | 3 種方案

探討數據共享的最佳實踐,了解 InheritedWidget 等三種主要方案,幫助你優化應用結構。

深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南

拆解 Flutter 的內部結構,全面了解 Widget、Element 和 RenderObject 之間的關係,提升你的 Flutter 開發技能!

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響