大家好!在 Day 3 我們快速掌握了 Dart 語言的核心精華,為接下來的 Flutter 之旅鋪平了道路。有了 Dart 這把利劍,我們今天終於要正式踏入 Flutter 的世界,親手創建我們的專案——「省錢拍拍 (SnapSaver)」。
今天的目標是:創建專案,並透過逐行程式碼,徹底解構 Flutter 預設 App 的每一塊積木。不只要知道概念,更要看懂程式碼是如何實現這些概念的。
準備好了嗎?讓我們正式啟動專案!
請打開 VS Code 的終端機 (Terminal),切換到你想要存放專案的資料夾路徑。然後,輸入以下指令:
flutter create snapsaver
完成後,會看到終端機提示:
All done!
In order to run your application, type:
$ cd snapsaver
$ flutter run
Your application code is in snapsaver/lib/main.dart.
接著,請切換到我們剛創建的 snapsaver
資料夾。
除了指令,你也可以透過 VS Code 的介面來建立專案:
F1
叫出命令面板,輸入 flutter
並點選 New Project
。Application
,選擇你想要存放專案的資料夾。snapsaver
),按下 Enter 即可創建成功。打開專案後,左側會有一堆資料夾和檔案。別緊張,我們初期只需要關注幾個最核心的部分即可。
lib/
: 我們未來 95% 的時間都會待的地方,所有 Dart 程式碼都存放在此。
main.dart
: App 的入口檔案,程式從這裡開始執行。pubspec.yaml
: 專案的「身分證」與「說明書」。管理專案名稱、版本、依賴套件與資源。ios/
和 android/
: 存放原生平台的專案檔,初期可先忽略。現在,我們打開 lib/main.dart
,聚焦在 MyHomePage
這個 Widget,看看畫面是如何由程式碼一行行搭建的。
在深入程式碼前,我們先看 MyApp
的類別宣告:
class MyApp extends StatelessWidget
這行程式碼的意思是:MyApp
繼承了 StatelessWidget
,因此 MyApp
本身就是 Flutter Widget 的一種。
在 main.dart
的頂部,MyApp
Widget 返回了 MaterialApp
。它就像是 App 的「總設定檔」,定義了 App 的根路由(home)、主題(theme)等。
class MyApp extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return MaterialApp( // 返回 MaterialApp
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// 指定 MyHomePage 為我們的首頁
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
MyHomePage
的 build
方法是整個畫面的核心。在這裡,它返回了一個 Scaffold
Widget。Scaffold
負責搭建頁面的基本結構,如頂部導航欄、頁面主體等。
直接來看程式碼:
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
// Scaffold Widget 提供了標準的頁面佈局結構
return Scaffold(
// 1. appBar: 頁面頂部的導航欄
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
// 2. body: 頁面的主體內容
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text(
'$_counter', // 顯示計數器變數
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
// 3. floatingActionButton: 右下角的懸浮按鈕
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter, // 按下時觸發 _incrementCounter 函式
tooltip: 'Increment',
child: const Icon(Icons.add), // 按鈕中的 '+' 圖示
),
);
}
}
這段程式碼中的 Scaffold
三大屬性:
AppBar
Widget,它會自動被放置在頁面頂部。AppBar
內部有一個 title
屬性,我們再放入一個 Text
Widget 來顯示標題文字。結構:Scaffold -> appBar -> AppBar -> title -> Text
Center
: 將它的子元件(child)置於中心。Column
: 將它的子元件們(children)垂直排列。Text
x 2: 兩個 Text Widget,一個顯示說明文字,另一個顯示 _counter
變數的數值。結構:Scaffold -> body -> Center -> Column -> [Text, Text]
FloatingActionButton
Widget。
onPressed
: 指定了按鈕被按下時要執行的函式,這裡連結到了 _incrementCounter
。child
: 在按鈕內部要顯示的內容,這裡是一個 Icon
Widget。在 Flutter 的世界裡,萬物皆為 Widget (元件)
而 Widget 分為兩大類:
StatelessWidget
(無狀態元件)
main.dart
範例中,MyApp 這個類別就是一個 StatelessWidget
。StatefulWidget
(有狀態元件)
MyHomePage
就是一個 StatefulWidget
。StatefulWidget
的特別之處在於,它會將「狀態 (State)」交由一個獨立的 State
物件來管理。當我們呼叫 setState()
這個方法時,Flutter 就會知道:「嘿!資料變了,快幫我把畫面重新畫一次!」
當使用者點擊 FloatingActionButton
時,會觸發 onPressed
指定的 _incrementCounter
函式。
void _incrementCounter() {
setState(() {
_counter++;
});
}
setState()
會做兩件事:
_counter
的數值。當 Flutter 收到通知後,就會重新執行 _MyHomePageState
的 build
方法,用新的 _counter
數值來重繪 Text
Widget,於是我們就會在螢幕上看到了數字的更新。
今天,我們從無到有創建了「省錢拍拍」專案,掌握了 Flutter App 的核心結構:MaterialApp
負責全局,Scaffold
搭建骨架,各種基礎 Widget 填充內容,並理解 StatelessWidget
和 StatefulWidget
的核心差異。