開始寫第一個 Flutter 2 程式

紅寶鐵軌客
Join to follow...
Follow/Unfollow Writer: 紅寶鐵軌客
By following, you’ll receive notifications when this author publishes new articles.
Don't wait! Sign up to follow this writer.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.
寫程式中、折磨中、享受中 ......
855   0  
·
2021/06/02
·
19 mins read


如果你照著我之前寫的安裝程式,你的 Android Studio 跟 Flutter 2 SDK 應該都已經裝好了,所以我們現在就可以開始寫 Flutter 了。

寫程式前有一個很重要的建議,就是剛開始學習時,當然可以照著書寫,但是要真學會,看完後就一定要自己找一個目標,自己重新重頭開始寫一次,沒有自己重頭開發的程式,你永遠不算學會。

好啦,來開始寫 Flutter 的第一步,就是先把 flutter22 中,我們之前建立的測試 project 刪掉,我們不會再用到它了。

起步走!

我們現在來寫一個錄音程式,就叫 happy recorder 好了。

打開 Android Studio,創立一個新 happy recorder 專案:

  1. file > new > new flutter project 
  2. 我們是要開發 app,所以選 flutter app,也確定一下 SDK 的目錄對不對。
  3. next 後,將 project name 改成 happy_recorder(專案名稱,不能有空白),輸入 Organization,把 iOS,Android,Web 打勾,按完成。

鐺鐺,Main.dart 就打開來等著你修改了!

注意:一定要輸入 Organization

除非你確定這個 App 絕對不會上傳到 App Store,不然就請務必一定要輸入 Organization,它的標準寫法是貴公司的網域倒著寫,例如 google.com 就是 com.google,這個以後非常難改,要改可能會花一兩天的時間(血淚紀錄)。

 

先來看看會不會動:

  1. 先選擇左邊的一個目標模擬平台,
  2. 再按那個綠色 Play 鍵,

第一次要等很長一下,你應該就會看到那個熟悉的 App 出現了!(又是點<+>加一...... )

在 Mac 上我是建議使用 iOS 模擬器,你可能需要先把它打開,Android Studio 才看得到,你也才可以選為目標模擬平台,它比 Andorid 模擬器快多了;如果你一定要用 Andorid 模擬器,但是又沒有看到,你在綠色 Play 鍵旁右右邊,不在上圖裡,可以找 AVD manager,在那裡可以打開 Andorid 模擬器,Andorid 模擬器很慢,要快一點可以選舊的(很舊的)版本。

有 Unable locate adb 警告?

開啟 Android 的模擬器時如果你跟我一樣出現了上面的「警告」,怎麼辦?類似這種問題在開發的過程中,會不斷地出現,所以一定要學會應對,也要習慣怎麼自己找答案,通常 google 就能找到很不錯的答案,但是要能把問題「縮小」,例如像這樣的問題,可以查“flutter Unable locate adb”,如果少了前面的 Flutter,你就把問題被大了。 可能解答1可能解答2

只是,有一個更重要的問題要問自己,值得花這個時間嗎?我花了五個小時,嘗試解決這個開發平台的問題,還是沒解決,值得嗎?我們是要學寫程式呢?還是要「玩」平台?這種問題很可能在下一次改版後就不存在了。


很好,接下來,找到這行程式,這行其實就是標題,把它改成如下:

home: MyHomePage(title: 'Happy Recorder v0.0'),

按一下綠色 Play 鍵邊的「閃電」,哇,馬上就可以看到標題改了,這就是 flutter 的 hot reload,對程式開發來說,很重要。

需求式學習法 - 先學要必須要懂的

我一直認為學「好」程式語言,甚至是任何事,都是來自真正的需求,如果不是真的需要,就算花了大把的時間精力,到後來還是很快就忘了,這樣的學習效率太低。

這本書就是以需求式學習法寫的,這本書不會讓你精通 Flutter,但是希望能讓你最快會用 Flutter。


很好,我們有一個會動的程式了,那我們就先來看看這個程式的第一段在寫什麼!

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}
  • 第 1 行:這行宣告了:這個程式使用了一個已經開發好的套件,叫做:package:flutter/material.dart,我們使用 import 這個指令把套件導入。套件是所有開發平台中很重要的資源軍火庫,強大又完整的套件庫可以大幅的提高開發效能,簡單來說,就是不用再重新發明輪子了,但是,如何找到功能強大又維護好的套件就是挑戰了,萬一你用了一個不再維護的套件,作業環境一改(如:OS 更新),可能就不能用了。Flutter 有兩種套件:
    • 一種是 packages:pub.dev 裡有一大堆,一般都是用 Dart 開發,就用在我們的 App 程式中,是跨平台的。
    • 另一種叫 plugins:這是平台(iOS, Android,...)限制的,也就是說每一種平台都有不同的 plugin,一般是用平台專屬的語言開發,例如照相功能,在每一個平台plugin 都不一樣。 
  • 第 3 行:Flutter 是用 dart 開發,dart 一定要有一個 main(),有人叫它入口函數,當 App 執行後,系統就會呼叫這個 main() 來開始執行,為什麼會叫 main?應該就是從古老的 C 語言留下來的習慣。在 dart 中,使用任何的變數都要先告訴 dart 它是什麼,也就是必須要宣告,函數也是變數,所以我們必須要告訴 dart,這個 main() 是個 void,void = 空靈,就是「無」啦,這也是有效的宣告,有趣吧,寫 void 也就是要 dart 不用管我是什麼啦,不重要,這也是從古老的 C 語言留下來的寫法。
  • 第 4 行:讓我們花一點時間來了解 runApp(MyApp()); 
    • runApp() 是一個 flutter 的 widget,widget = 小玩意,在 Flutter 中 widget 是指使用者介面 UI 上的各種小玩意,所以包含像是文字、按鈕、直橫條等等,在 Flutter 中都是 widget。
    • runApp() 這個 widget 會做一件很偉大的事,就是會把它的「內容」顯示在 App 的畫面,不使用它,你的 App 就會有一個空空的畫面,如果第二次呼叫 runApp() 就會把之前的畫面內容移除、再顯示新的「目前內容」,所以,講白話文就是說:runApp() 會把()內的「內容」顯示在 App 上。
    • runApp() 的內容是 MyApp() 產生的,而 MyApp() 就在下面,裡面有什麼?當然就是一開始的畫面,分開來寫只是要保持 main() 的乾淨。
    • 更完整的寫法應該是 return runApp(MyApp()),沒寫 return 就是 return null,不過反正 main 是 void 無效的、不用 dart 管的,所以 return 就可以省了。
    • 第 3~5 行可以更簡單的寫成 void main() => runApp(MyApp()); 這個 => 是 dark 的一種簡寫,話說,也不過就是將 {} 改成 =>,字數一樣,有差嗎。
    • 用分號結束這個指令。在 dart 中,有大括弧 {} 就不用分號;,沒有大括弧就要分號,用來告訴 dart 這個程式區塊結束了。

接下來看第二段:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Happy Recorder v0.0'),
    );
  }
}
  • 第 1 行:這可有很多細節要說了:
    • MyApp()  是個 class,class = object,所以 dart 是一個 object-oriented 物件導向語言,我很討厭「物件導向」這翻譯, 不過我的喜好不重要,我對 object 只記得一件事,就是可以繼承,白話文就是可以搞無性繁殖+基因修改啦。dart 有兩種無性繁殖的繼承方法:
      • extends:只能一次繼承一個 class。
      • implements:這個讚,能一次繼承很多個 class(es),
    • extends StatelessWidget 是繼承 Stateless「無狀態」widget,所以這個 MyApp() 就成了一個無性繁殖的「無狀態」widget。
    • Flutter 有「無狀態」widget,及 Stateful「有狀態」兩種 UI widget:
      • Stateless「無狀態」widget:是用在內容不會改變的地方,像是公司 logo。
      • Stateful「有狀態」widget:當 UI 內容會改變時使用,像是時間。
  • 第 2 行:@override,是一個 Dart 語言中的特殊註解寫法,在官方文件中稱它為 Metadata,不同於一般的 comments 說明,它的內容是可以被程式讀取的,甚至,還可以自行定義它的行為,主要用於「查詢」程式內容,一般開發 app 應該用不到,所以我們現在知道它是什麼就好。在 dart 中有兩個預設的 Metadata,一個是 @override,另一個是 @deprecated,這兩個用法都很簡單:
    • @override = 在它下面的程式是將無性繁殖繼承過來的東西、做基因修改;
    • @deprecated = 在它下面的程式已經被淘汰了。
    • 實際上使用:就照這樣寫這樣用啦,因為大家都這樣寫,先不用管到底有啥用,雖然,你沒寫也沒關係⋯⋯ 不信?你可以把它刪掉試試。
  • 第 3 行:前一行的 @override 就是宣告要修改 build 這個 StatelessWidge 內建的 method 方法,各位可以快速的點開看一下 StatelessWidget 文件,看看其中列了多少 methods,build 這個 method 就是將它()內的內容「畫出」。
  • 第 4 行:原來 MyApp 的內容是使用 Material design,也就是 Android 上使用的 UX 風格,畫這種 UI 的程式在 Flutter 中已經寫好了,就是 MaterialApp(), 從連結的文件中,我們也就知道:
  • 第 5 行:Title 是一個 Property 屬性,它也是讓裝置(iOS, Android, ...)辨識 App 的名稱。
  • 第 6~8 行:Theme 是一個 Material design UI Property,它要用 ThemeData 來設定這個 Widget 的顏色,這裡只設定了主要顏色為 Colors.blue,可調整的地方很多,可以點選文件看一下。
  • 第 9 行:home 也是在 MaterialApp() 中的 Properity,他有兩個功能:
    • 是一個 Widget,在這裏它呼叫 MyHomePage() 來畫出這個 widget。
    • 也預設的導航(路由)起始點,很像網站中的首頁,只不過 Flutter 是使用堆疊的觀念,所以前進後退就會是 push 跟 pop。更多參考:中文1
Stateless「無狀態」/ Stateful「有狀態」widget 簡介

看完了以上的介紹,下面這兩個影片可以讓你進一步了解 stateless/stateful widget,還有更重要的,了解 Widget tree vs Element Tree,原來螢幕上看到的是 elements,Widget 是個藍圖。

How to Create Stateless Widgets - Flutter Widgets 101 Ep. 1 Google Developers — YouTube

How Stateful Widgets Are Used Best - Flutter Widgets 101 Ep. 2 Google Developers — YouTube


再來看第三段:

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}
  • 第 1 行:我們前段才說 Flutter 有「無狀態」widget,及 Stateful「有狀態」兩種 UI widget,上面才剛用了 Stateless「無狀態」widget,現在我們就看到 Stateful「有狀態」widget 了。Stateful「有狀態」的 widget 是使用在當 UI 的內容會被改變,下一段,我們就會看到,怎麼改變它的內容。
  • 第 2 行:這行可就難懂多了,讓我們先來看懂這行在寫什麼:
    • 這行是 dart 的 class constructor(建構函數), 什麼是 constructor?A:就是當這個 class 的 object 被建立時,constructor 會自動執行,簡單來說,就是當這個 MyHomePage 被執行時,這段程式碼會自動先執行。在 dart 中創立一個 constructor 很簡單,就是使用跟 class 一樣的名稱就好。
    • 什麼是 ({Key? key, required this.title}) ?
      • () 內就是 constructor 的參數 。
      • 在 Flutter 中,用大括弧 {} 包起來的參數,就是有「命名 named」的參數,既然是有命名的參數,所以也就有可能沒有傳進來,所以也就成為了 可有可無的參數,optional 參數。
      • Key 是一個 Flutter 內建的 class,用來標號識別所有的 Flutter 的 Widgets、Elements,......
      • Key? 中的?是指這個參數是 nullable,這是 Flutter 2.0 後很強調的一項安全機制 null safety,如果沒有這個?問號,這個參數就不能是 null,程式會報錯,如果這個參數有可能是 null,就一定要加一個?。
      • required 說這個參數一定要有,不是可有可無的參數。
      • this.title 是命名為 title 的變數,沒有宣告他的型態是因為它的初始值與型態是傳進來的 title 參數值及型態,這只是一個 dart 的「爽」寫法,可以省打幾個字。 
      • 白話文解釋,這個函數有兩個變數:
        • 第一個可有可無的參數叫做 key,他的型態是 Flutter 內建的 class Key。
        • 第二個參數叫做 title,這個參數一定要有,型態是 this.title 的型態,預設值也是 this.title 的值。
    • 等等,任何一個函數的寫法都是:function_name(參數) { },為什麼這個函數寫成這樣:function_name(參數) : ...?那個 (參數):... 是什麼?大括弧 {} 怎麼不見了呢?
      • (參數):... 又是 Flutter 的特殊寫法,放在(參數):後的是個 initializer(起始器),當這函數被呼叫時,會先執行這段,用來做預設起始動作。
      • 所以這個 constructor 一執行,就會去做 super(key: key) 的動作了。
      • 大括弧 {} 被省略了,這種寫法在 Dart 中叫做 Redirecting constructors,翻譯成中文就是重新指向建構函數,白話文就是這函數自己什麼也不做,就只是把參數帶路給 initializer,也就是 super(key: key) 。
    • super(key: key) 就是去執行 MyHomePage class 的「父母」constructor,也就是 StatefulWidget 的 constructor。
    • 白話文解釋這行程式:當 MyHomePage 被呼叫時,會確定一定要有 title 變數,還會用這個 Widget 的識別來重設他的「父母」class constructor。 
  • 第 4 行:宣告 title 變數型態是 string 字串,而且不會被改變。一樣,我們來深入看看:
    • final 是宣告變數的一種方法,就是告訴電腦,這個變數的值只能設定一次,設定後,就不會再被改變了,如果被改了,就要告訴我。其他宣告變數的方法有:
      • var:這個變數可以隨便亂改,電腦你就不用管了。
      • Const:這是寫死在程式內的變數,只要被改,就是錯誤,先知道這樣就好,我們接下來有機會還會再說明。
      • late:延遲設定變數的值。
    • String:dart 的一種變數型態,有:int, double, String, bool, List, , Set, Map, Runes, Symbol。變數可能是 null 時就要加個問號?在後面。用 Null 來宣告 null......  
    • String title:配合前面 constructor 的 required this.title 就是說這個 widget 有一個參數,型態是 String,而且一定要有。
    • 變數名稱:title 就是一個變數名稱,在 Flutter/Dart 中,並沒有硬性規定的命名規則,但是,我一直認為合群是很重要的,一般來說,建議的變數命名法是 lowerCamelCase,例如:firstName,sencordName。更多 Flutter/Dart 命名建議請參考這裡
  • 第 6 行:@override 學過了,讚!你已經知道一些 Flutter/Dart 了。
  • 第 7 行:這一行講的是 Futter 的另一座大山 state,他是呼叫下一段會介紹的 _MyHomePageState class ,所以我們先來做一個簡單的介紹:
    • 底線 _ 開頭的函數或是變數就變成 private 了,private 的程式碼就只能在這個 library 中被呼叫,在 Dart 中,每一個 app 就是一個 library,每一個 import 進來的也是一個 library。(聽無?沒關係,知道這樣寫的函數只有在這段程式碼 package 內可以用就好,我們會在後面的程式碼管理中,更進一步的解釋什麼是 package。)
    • StatefulWidget 就是「有狀態 state」的 widget,所以自然要建立一個狀態 state,createState() 就是 StatefulWidget 中的一個 method 方法,用來建立這個 widget 的狀態。(又聽無?好啦,知道這個有狀態 Widget 的狀態 state 是會由下面的 _MyHomePageState 管理就好。)
    • 我們先知道這樣就好,下一段我們就要來介紹 state 了。


什麼是 keys?

Key 這個觀念在 Flutter 中很重要,也超難懂,是控制 widget 很重要的觀念,我強烈建議好好的多看幾遍下面的影片,應該是目前最好也最清楚的介紹了,就算看不懂,也可以先有個觀念,知道 Widget 與 elemenet 的關係。

When to Use Keys - Flutter Widgets 101 Ep. 4 Google Developers — YouTube 

再來看最後這 State 段:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
  • 第 1 行:好慘,才一開頭,這 State<MyHomePage> 是什麼鬼,又看不懂了,沒關係,我們來看懂它:
    • <..> 在 dart 中被稱為 generic,我認為中文翻譯為「通用」比較接近原意,他主要是為了減少重複的程式碼,官方文件上有一段很不錯的介紹,有空就看看,沒空?那就看這裡就行了。
      • 例如:var names = <String>['Seth', 'Kathy', 'Lars'] 這行中,就是指 name 這個 array 內容都是 string,這樣就可以很輕易的建立一個 array 還規定它的內容一定要是 string,換成 <Int>[],就變成 int 內容的 array,所以有 generic 的「通用」寫法是不是很方便。
    • State<MyHomePage> 指的就是:這個是 MyHomePage 的 State, 哇哈,真的很簡潔方便!
    • class _MyHomePageState extends 我們已經學過了:建立一個隱私的 _MyHomePageState class 繼承 MyHomePage 的 State。
  • 第 2 行:這個 class 內有一個 private int 變數,叫做 _counter,預設為 0。
  • 第 4~8 行:建立了一個叫做 _incrementCounter() 的方法,當它被執行時,它只做一件事:告訴系統這個 widget 的 state 狀態被改變了,也就是呼叫 setState(callback) 這個 State class 的 method 方法,有時我們常會用變髒了 dirty 來形容它。
    • 注意:我在介紹 setState(callback) 的 () 括弧中,我寫的是 callback,這是指當這個 setState 被呼叫的同時,它還會同時執行一個「回馬槍」函數,也就是這個 callback,callback 一定是一個函數,但是我們怎麼看到這段程式碼中是一個:() {_counter++; }?
    • 原來 () {_counter++; } 確實是一個函數,只是因為它的工作實在太簡單了,不過就是將 _counter 加一,所以寫程式的人連給它取的名字都懶,沒名的函數就變成了一個「無名」函數,英文叫 Anonymous functions,在 Dart 中,「無名」函數的寫法就是 (參數s) {程式..}。
    • 白話文解釋:當 _incrementCounter() 這個方法被呼叫時,會把這個 widget 的狀態弄髒,還會將 _counter 加一,是的,就這麼簡單。
  • 第 10 行:@override 看過了,所以以下的碼就是會改寫原來的 methods。
  • 第 11~35 行:這麼多行其實只做一件事,就是建立 build 出 MyHomePage 的畫面,真正執行的是 return 了一個 Scaffold() 物件,這個 scaffold() class 的物件可厲害了,只要照著它的文件設定一下,就可以建立一個使用 Material design 的完整畫面,scaffold 翻譯成中文應該是「樣板」,但是很多人都用「鷹架」,也行, 反正就是預設了一個樣本,大家要用時改改內容好了。我們等一下來好好的玩一玩這個 scaffold(),現在先來看個這段碼到底在做什麼:
    • 第 13~15 行:就是設定 App 的標頭,Appbar 也是一個 Flutter Widget。 widget.title 有點小複雜,先不要管太多,反正記得,widget 是個神奇的東西,它會把這個 state 的爸爸,也就是 MyHomePage 的參數帶進來,所以 widget.title 就是 MyHomePage 的 title 參數。
    • 第 16~29 行:就是設定 App 的內文,也就是 body,這個內人有兩個小孩,都是中間對齊,兩個小孩都是文字 widget,第二個比較特別一點,它是顯示 _counter 的值,在 dart 中要顯示變數的值就是這樣用:${var},大家可以到 DartPad 中玩玩。
    • 第 30~34 行:這建立了一個浮動的按鈕,你點一下它,就會呼叫 _incrementCounter() 這個方法,也就是將 _counter + 1 及把這個 widget 的狀態弄髒,讓 Flutter 把它重畫一次。


來玩一玩 scaffold()

好啦,你已經看完了整個 Flutter 自動產生的程式了,我相信,基本上頭都昏了,還一行程式都沒寫到,別急,那我們就先來改變一下 Scaffold() 的設定,讓我們往 happy recorder 邁進。

這是改過的程式,我只改了 _MyHomePageState 中的程式碼:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _openDrawer() {
    _scaffoldKey.currentState!.openDrawer();
  }

  void _closeDrawer() {
    Navigator.of(context).pop();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      drawer: Drawer(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text('This is the Drawer'),
              ElevatedButton(
                onPressed: _closeDrawer,
                child: const Text('Close Drawer'),
              ),
            ],
          ),
        ),
      ),
      bottomNavigationBar: BottomAppBar(
        shape: const CircularNotchedRectangle(),
        child: Container(height: 30.0),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

這些改變讓畫面變成了下面這樣:

讓我們來看一下改了什麼:

  • 加了一個 drawer 抽屜(就是左上角的三條線 menu),先知道怎麼用就好,因為我也是抄來的(抄是學習的重要法寶),我是從 drawer 的文件抄來的,剛學程式時,我最喜歡抄來抄去了,當然,以後還是要了解它。 
    • 第 3 行: 我們增加了一個 GlobalKey<ScaffoldState>,也就是 Scaffold 的 State 的 GlobalKey(以後再來講 Global key,先這樣)。
    • 第 11~17 行:加了 Drawer 抽屜的開跟關。
    • 第 40~53 行:把 Drawer 抽屜畫出來。
  • 加了一個 bottomNavigationBar (最下面),然後把 floatingActionButton 放在中間,這個設定很簡單,也是抄來的,來源
    • 第 54~57 行: 我們增加了一個 bottomNavigationBar。
    • 第 63 行:設定 floatingActionButton 的位置在中間。

好啦,我就先改到這裡,大家可以繼續看文件改下去,很好玩的,雖然我們的程式離能夠錄音還很遠,不過,這就是我們的主畫面了。

現在,強烈的建議到下列的網頁看看,可以瞬間增加很多功力:

  • Flutter 的 UX 的 gallery 去看看,啊,原來 Flutter 有那麼多的 widget,做 UI 方便多了。
  • Flutter 的 cookbook,這裡有很多程式的開發參考。
  • Codelabs 的前段:Good for beginners 可以看看,很棒的。 

最後,在結束這篇已經超長的文章前,我還想建議大家看一下這段影片:

當你的 Widget 有很多層時:
請用 InheritedWidget

Inherited Widgets Explained - Flutter Widgets 101 Ep. 3 Google Developers — YouTube

這個影片對初學者來說,太難了,另外也講的很不清楚,我們在目前只要知道,透過這個 Inherited widget 就能讓資料在不同的 Widget 中分享,目前知道這點就夠了。


恭喜你,你已經學會「一些」 Flutter 了。







WriterShelf™ is a unique multiple pen name blogging and forum platform. Protect relationships and your privacy. Take your writing in new directions. ** Join WriterShelf**
WriterShelf™ is an open writing platform. The views, information and opinions in this article are those of the author.


Article info

This article is part of:
分類於:
標籤:
日期:
創作於:2021/06/02,最後更新於:2022/02/20。
合計:5634字


Share this article:
About the Author

很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
就,這會一點點,那會一點點⋯⋯




Join the discussion now!
Don't wait! Sign up to join the discussion.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.