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(),
);
}
}
基本上跟一般程式語言一樣進入點在 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: () {},
),
),
],
),
),
);
}
}
基本上在構建 UI 時,我都會想像上面分解圖(一開始寫 Flutter 時可以拿紙筆或其他工具畫出來,會更有感覺)。
而從圖上我們可以看到,UI 是以一層層向內疊放的方式去構建的,畫出圖可以更容易的對應 Flutter 程式碼。
在 lib/main.dart
裡可以看到程式碼中 GitmeRebornApp
的 home
屬性裡面我填了 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,直接對應到程式碼。那什麼是陳述式 UI 呢?簡單來說,因為所有事件(操作)發生都會改變狀態,所以開發者應該直接思考的是
如何將 輸入的狀態 轉換成 輸出的畫面
也就是開發者直接思考如何寫出上圖的 f 函數。而 Flutter 就是提供構建 f 函數的工具。
[深入探索] Flutter 內部在構建及渲染畫面的步驟是
Widget Tree => Element Tree => RenderObject Tree
可以大致想像 Element Tree 是中間人,它必須知道 Widget Tree 做了什麼狀態改變,然後它需要怎麼調整(重建) Element Tree,另外它還要通知 RenderObject Tree 去渲染新畫面。
好了,到這裡希望大家能多多少少理解到 Flutter 開發 UI 的概念,後續在看程式碼上也比較容易理解~
參考
- Flutter.dev - Start thinking declaratively
- Flutter YouTube - Flutter Widgets 101 Ep. 1
- Flutter实战 - 14.2 Element与BuildContext