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
類
● 並且 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 可用
● 使用層級分離的技巧後,Navigator.of 的上下文就會在層級內部,這就導致 Navigator.of 可以找到 MaterialApp 的 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();
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();
命名路由
另外一種不透過層級分離的技巧就可以跳轉頁面的方式,就是透過設定「命名路由」來跳轉
命名路由使用範例
● 路由針對每個頁面取名,然後通過頁面名子傳遞給路由器就可以直接開啟,跳轉頁面使用 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 物件,配合使用 async
、await
就可以堵塞並獲取結果
取得 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. 設定跳轉頁面,並使用 async
、await
關鍵字來等待該頁面返回的結果
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 開發技能!