用 Flutter 開發一個 Android App 吧 - Day3

Build Android app with Flutter - Day3

Posted by Bobson Lin on Thursday, September 12, 2019

Day 3. 進入點、登入頁面

本系列同步發表在 第11屆鐵人賽

今天開始進入本系列的主線 - 第一階段 UI 部份

進入點

lib/main.dart

import "package:flutter/material.dart";
import "package:gitme_reborn/pages/login.dart";

void main() => runApp(GitmeRebornApp());

class GitmeRebornApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Gitme Reborn",
      theme: ThemeData(
        primarySwatch: Colors.blueGrey,
      ),
      home: LoginPage(),
    );
  }
}

day3-1.png

基本上跟一般程式語言一樣進入點在 void main 這個函數,這裡頭直接調用 Flutter SDK 所提供的 runApp 函數,去啟動整個 Flutter App。

我直接將 Flutter 預設創建出來的 MyApp 稍微作些改寫,並刪除些不要的部份,剩下 20 行不到。

而這裡 runApp 函數我們帶入 GitmeRebornApp 這個 Widget 給它(StatelessWidget 表示他是沒有附加狀態的 Widget),後續 Flutter 都會幫你處理好。

小提醒:

  • 這裡使用 MaterialApp(import "package:flutter/material.dart";),它直接封裝了 Google 推出的 Material Design 的一些設定。
  • 如果想使用 Apple iOS 風格設計可使用 CupertinoApp 來建構,基本上直接使用 MaterialApp就有得玩了~

登入頁面

lib/pages/login.dart

import 'package:flutter/material.dart';

class LoginPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Login"),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: <Widget>[
            Container(
              padding: EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
              child: TextFormField(
                decoration: const InputDecoration(
                  prefixIcon: Icon(Icons.person),
                  labelText: "Name *",
                  hintText: "Your Github account username",
                ),
              ),
            ),
            Container(
              padding: EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
              child: TextFormField(
                decoration: const InputDecoration(
                  prefixIcon: Icon(Icons.lock),
                  suffixIcon: Icon(Icons.remove_red_eye),
                  labelText: "Password *",
                  hintText: "Your Github account password or ...",
                ),
              ),
            ),
            SizedBox(
              height: 52.0,
            ),
            SizedBox(
              width: MediaQuery.of(context).size.width - 48.0,
              height: 48.0,
              child: RaisedButton(
                child: Text("Login"),
                onPressed: () {},
              ),
            ),
          ],
        ),
      ),
    );
  }
}

day3-2.png

基本上在構建 UI 時,我都會想像上面分解圖(一開始寫 Flutter 時可以拿紙筆或其他工具畫出來,會更有感覺)。

而從圖上我們可以看到,UI 是以一層層向內疊放的方式去構建的,畫出圖可以更容易的對應 Flutter 程式碼。

lib/main.dart 裡可以看到程式碼中 GitmeRebornApphome 屬性裡面我填了 LoginPage,這使得剛跑起 App 時就會自動顯示出我寫的登入的畫面 (lib/pages/login.dart)。

小提醒:

  • 基本上我在撰寫新的 Widget 時都是以 StatelessWidget 起手。
  • 通常是在頁面使用到一些狀態改變(比如,使用者操作、讀取數據…等等事件發生時),才會用到 StatefulWidget

欸!? 暫停一下

  • blablabla 貼了一大堆程式碼,直接就進入業配(頁面)主題阿~~
  • 根本無法承受阿~~~
  • 一堆概念根本不知道怎麼來的阿…

哦哦哦~ 我知道… 畢竟一開始我也是這樣的… (各種碰壁跟撞牆…)

以下一些概念,是我透過官方文件或影片理解到的:

  • Flutter 世界裡一切的東西都是 Widget。
    所以在開發 Flutter App,很大部分是在構建 Widget Tree(Widget 樹),而大部份的 UI 功能會操作這些 Widget 也就夠了。

  • Flutter 很講重構建 UI 的方式應為陳述式(宣告式)(Declarative UI)的思考方式,這方式相對於以前 Android 或 Java Swing 的命令式(Imperative UI)。
    所以在開發 Flutter App,如果仔細觀察,其實可以很容易的將規劃的 UI,直接對應到程式碼。 Start thinking declaratively

    那什麼是陳述式 UI 呢?簡單來說,因為所有事件(操作)發生都會改變狀態,所以開發者應該直接思考的是

    如何將 輸入的狀態 轉換成 輸出的畫面

    也就是開發者直接思考如何寫出上圖的 f 函數。而 Flutter 就是提供構建 f 函數的工具。

  • [深入探索] Flutter 內部在構建及渲染畫面的步驟是

    Widget Tree => Element Tree => RenderObject Tree

    可以大致想像 Element Tree 是中間人,它必須知道 Widget Tree 做了什麼狀態改變,然後它需要怎麼調整(重建) Element Tree,另外它還要通知 RenderObject Tree 去渲染新畫面。

好了,到這裡希望大家能多多少少理解到 Flutter 開發 UI 的概念,後續在看程式碼上也比較容易理解~

參考