[4] 快速學會 Flutter 基礎入門

·

4 min read

0. 影片教學

本篇文章是一個步驟指導,建議可以搭配影片一起學習:

1. 安裝 Flutter 開發環境

Flutter

首先到 docs.flutter.dev/get-started/install/macos 下載 flutter

下載完成後,選擇要解壓縮的目錄之後進行解壓縮,因為它不需要進行安裝,所以位置要選一個不會被變動到的位置。

設定路徑:

$ vim .bash_profile

將 flutter 路徑填入:

$ export PATH=$PATH:/Users/jakechang/Downloads/flutter/bin

重新讀取設定檔案:

$ source ~/.bash_profile

檢查缺少的軟體:

$ flutter doctor

會依序檢查電腦是否有安裝這些軟體:

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.0.3, on macOS 12.4 21F79 darwin-x64, locale zh-Hant-TW)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 13.4.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.2)
[✓] IntelliJ IDEA Community Edition (version 2022.1.3)
[✓] VS Code (version 1.68.0)
[✓] Connected device (3 available)
[✓] HTTP Host Availability

要全部打勾才可以順利執行 flutter,這邊是我已經全部安裝好的狀態,一般初學者大致上會缺少以下軟體 Android / Xcode / IntelliJ,這邊就根據這些軟體的安裝來解說。

Android Studio

Xcode

  • 直接在 App Store 下載安裝 Xcode
  • 安裝 CocoaPods,cocoapods.org
  • 最後一樣開啟 Xcode 新增一個專案,然後開啟一個 iOS 模擬器

IntelliJ IDEA

IntelliJ IDEA 是一個可以開發 flutter 的 IDE 工具,到這邊下載 jetbrains.com/idea/download/#section=mac

在 IntelliJ IDEA 上安裝 Flutter 套件:Preferences -> Plugins,搜尋 Flutter 進行安裝

都完成之後,再重新執行一次 flutter doctor ,確認每個項目都已經打勾

2. Hello Flutter

主程式為 main.dart,修改 main 這個入口函式:

void main() {
  runApp(const Center(
    child: Text("Hello Flutter", textDirection: TextDirection.ltr),
  ));
}

重新執行模擬器,螢幕上會出現 Hello Flutter

可以使用 stless 的關鍵字建立 class:

void main() {
  runApp(const DemoApp());
}

class DemoApp extends StatelessWidget {
  const DemoApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text("Hello Text", textDirection: TextDirection.ltr,),
    );
  }
}

3. Container

Center 容器只能調整位置,不能增加其它屬性,例如背景顏色。若要放入其它屬性,就必須使用 Container 容器:

@override
Widget build(BuildContext context) {
  return Container(
    color: Colors.blue,
    alignment: Alignment.center,
    child: const Text("Hello Text", textDirection: TextDirection.ltr,),
  );
}

其它 Container 屬性可參考:api.flutter.dev/flutter/widgets/Container-c..

4. Column

如果要放兩個以上的 Text 元件,就必須要使用 Column

@override
Widget build(BuildContext context) {
  return Container(
    color: Colors.blue,
    alignment: Alignment.center,
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: const <Widget>[
        Text("Hello Text1", textDirection: TextDirection.ltr,),
        Text("Hello Text2", textDirection: TextDirection.ltr,),
      ]
    ),
  );
}

5. MaterialApp

一個具有 header 與 body 的排版方式:

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text("Header"),
      ),
      body: const Center(
        child: Text("Body"),
      ),
    ),
  );
}

6. Button

一個最簡單的 Button 使用方式:

@override
Widget build(BuildContext context) {
  return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("Header"),
        ),
        body: Center(
          child: RaisedButton(
          onPressed: btnClickEvent,
          child: const Text("Button"),
        )
      ),
    )
  );
}

void btnClickEvent() {
  print("hello button");
}

同時放上 Button 與 Text

@override
Widget build(BuildContext context) {
  return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("Header"),
        ),
        body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                RaisedButton(
                  onPressed: btnClickEvent,
                  child: const Text("Button"),
                ),
                const Text("Text", textDirection: TextDirection.ltr,),
              ],
            )
        ),
      )
  );
}

6. Button 與 Text 的互動

接下來示範按鈕按下去, Text 會一直累加數字。因為這邊要去即時更改 UI 元件,需要將 Widget 狀態設定為 Stateful,另外這邊也將按鈕的事件直接寫在 onPressed 裡面。

void main() {
  runApp(const TestApp());
}

class TestApp extends StatefulWidget {

  const TestApp({Key? key}) : super(key: key);

  @override
  State<TestApp> createState() => _TestAppState();
}

class _TestAppState extends State<TestApp> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text("Header"),
          ),
          body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  RaisedButton(
                    onPressed: (){
                      setState((){
                        count++;
                        print("$count");
                      });
                    },
                    child: const Text("Button"),
                  ),
                  Text(count.toString(), textDirection: TextDirection.ltr,),
                ],
              )
          ),
        )
    );
  }
}

一個簡單的範例

最後來製作一個簡單的登入頁面,輸入完帳號密碼之後,可以跳轉到第二頁,並且把輸入的資料帶入呈現。

首先先來製作登入頁面:

void main() {
  runApp(const DemoApp2());
}

class DemoApp2 extends StatefulWidget {
  const DemoApp2({Key? key}) : super(key: key);

  @override
  State<DemoApp2> createState() => _DemoApp2State();
}

class _DemoApp2State extends State<DemoApp2> {
  TextEditingController emailController = TextEditingController();
  TextEditingController passwordController = TextEditingController();

  int count = 0;

  @override
  Widget build(BuildContext context) {

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("Header"),
        ),
        body: Container(
            margin: const EdgeInsets.all(20),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                TextField(
                  controller: emailController,
                  decoration: const InputDecoration(hintText: "Email"),),
                TextField(
                  controller: passwordController,
                  decoration: const InputDecoration(hintText: "Password"),),
                Center(
                  child: RaisedButton(
                  onPressed: onPressed,
                  child: const Text("Send"),
                  ),
                )
              ],
           )
        ),
      ),
    );
  }

  void onPressed() {
    print(emailController.text + passwordController.text);
  }

}

將 button 按鈕事件改寫成:

RaisedButton(
  onPressed: (){
    print(emailController.text + passwordController.text);
  },
child: const Text("Send"),
)

加入 Page2 頁面,一樣也是使用 Stateful:

class Page2 extends StatefulWidget {
  const Page2({Key? key}) : super(key: key);

  @override
  State<Page2> createState() => _Page2State();
}

class _Page2State extends State<Page2> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("Pages"),
        ),
        body: Center(
          child: Text("page2"),
        ),
      );
  }
}

按鈕加入跳轉到第二頁:

RaisedButton(
  onPressed: () {
    Navigator.push(context, MaterialPageRoute(
      builder: (context) => const Page2()));
    },
  child: const Text("Send"),
),

修正主程式後,就可以完成跳轉功能:

runApp(const MaterialApp(
  home: DemoApp2(),
));

最後要將 email 與 password 兩個參數帶入給第二頁,所以在 Page2 先新增兩個變數:

class Page2 extends StatefulWidget {
  final String email;
  final String password;

  const Page2({Key? key, required this.email, required this.password}) : super(key: key);

  @override
  State<Page2> createState() => _Page2State();
}
class _Page2State extends State<Page2> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("Pages"),
        ),
        body: Center(
          child: Text("Email: $widget.email, Password: $widget.password"),
        ),
      );
  }
}

修改第一頁按鈕事件:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => Page2(email: emailController.text, password: passwordController.text,)
  )
);