在 Flutter 中,只要是與使用者介面相關的資訊,幾乎都跟 Widget 有關,開發者主要就是使用 Widget 描述畫面,文字、圖片、Icon 等使用者介面元件,都使用 Widget 來描述,看不到的東西也是 Widget,例如佈局相關的描述,也是使用 Widget。
如〈Hello, World!〉中談到的,Flutter 應用程式的進入點,就是接受 Widget 的 runApp,而當中接收的 Text,就是個 Widget 的子類後裔,嚴格來說,是 StatelessWidget 的子類:
Widget > StatelessWidget > Text
StatelessWidget 主要用於描述狀態不變的使用者介面元件,狀態不變的意思是,不會有方法定義可以改變 StatelessWidget 實例的狀態,你繼承 StatelessWidget 定義自己的 Widget 時,也不該有方法可以改變狀態。
這時你就會有疑問了,在某些操作後文字內容不是會改變嗎?這是因為建立了新的 Text 實例來描述文字,然後畫面重新組建,因此看到了新的文字內容,這通常跟 Widget 的子類 StatefulWidget 有關,之後會看到。
Widget 在設計時,往往考慮的是元件的獨立、可組態、可重用,儘量不與其他元件相依,也就是說,只管自己的職責,例如 Text,想想看你在操作文書處理軟體時,對於文字,考慮的是文字本身、文字對齊、顏色、粗體等資訊,這些可以在 Text 上設定。
例如,將文字改為黃色,可以透過 style:
import 'package:flutter/material.dart';
void main() => runApp(
Text(
'Hello, World!',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: TextStyle(color: Colors.yellow),
)
);
最後呈現出來的畫面如下:
目前的文字怎麼都一直顯示在螢幕頂端?Text 可以設置它顯示在螢幕中間嗎?這不是 Text 本身關心的事,因此 Text 不包含這些描述。
有關於元件的佈局,例如置中對齊、行排列、列排列等,是由 Widget 另一子類 RenderObjectWidget 來描述,例如方才談到的顯示在螢幕中間,可以使用 Center:
import 'package:flutter/material.dart';
void main() => runApp(
Center(
child: Text(
'Hello, World!',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: TextStyle(color: Colors.yellow),
),
)
);
顯示結果如下:
Center 實際上就是 RenderObjectWidget 的子裔:
Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Align > Center
Center 的父類是 Align,看名稱就知道跟對齊有關,實際上,Center 只是預設為置中對齊的 Align,因為 Center 的原始碼是:
class Center extends Align {
const Center({ Key key, double widthFactor, double heightFactor, Widget child })
: super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}
簡單來說,只是呼叫父類建構式罷了,其中並沒有指定 alignment 命名參數,而 Align 的預設值是 Alignment.center,也就是置中:
class Align extends SingleChildRenderObjectWidget {
const Align({
Key key,
this.alignment = Alignment.center, // 預設就是置中
this.widthFactor,
this.heightFactor,
Widget child,
}) : assert(alignment != null),
assert(widthFactor == null || widthFactor >= 0.0),
assert(heightFactor == null || heightFactor >= 0.0),
super(key: key, child: child);
...
}
SingleChildRenderObjectWidget 一看也就知道,它只會包含一個 Widget 元件,使用 child 命名參數來指定;除了 Center、Align 之外,之後你可能還會很常使用的還有 Row、Column,它們也是 RenderObjectWidget 的子裔,以 Column 為例:
Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Flex > Column
MultiChildRenderObjectWidget 表示,Row 可以包含多個 Widget 元件,使用 使用 children 命名參數來指定,Flex 表示它會使用一維陣列的方式來顯示元件。
例如,若整個畫面作為一行(Column),在其中顯示兩個文字:
import 'package:flutter/material.dart';
void main() => runApp(
Column(
children: <Widget> [
Text(
'Hello, World!',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: TextStyle(color: Colors.yellow),
),
Text(
'哈囉!世界!',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: TextStyle(color: Colors.blue),
),
],
mainAxisAlignment: MainAxisAlignment.center
)
);
範例程式中看到的 <Widget> [....] 會建立 Dart 的 List,<Widget> 是 Dart 的泛型語法,表示 List 中都是 Widget,Android 會自動產生這個泛型語法,不少文件也會寫出來,不過其實只要編譯器能推斷型態,不寫 <Widget> 也是可以的,例如:
import 'package:flutter/material.dart';
void main() => runApp(
Column(
children: [
Text(
'Hello, World!',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: TextStyle(color: Colors.yellow),
),
Text(
'哈囉!世界!',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: TextStyle(color: Colors.blue),
),
],
mainAxisAlignment: MainAxisAlignment.center
)
);
mainAxisAlignment 設定為 MainAxisAlignment.center 表示置中對齊元件,結果就會如下:
那麼可不可以改一下背景顏色?有關於元件的繪製、邊距(margin)、墊充(padding)、大小等,是由 Container 負責,它是 StatelessWidget 的子類:
Widget > StatelessWidget > Container
來隨意地設定一下元件的顏色、邊距與寬度:
import 'package:flutter/material.dart';
void main() => runApp(
Column(
children: [
Container(
child: Text(
'Hello, World!',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: TextStyle(color: Colors.yellow),
),
color: Colors.blue,
margin: const EdgeInsets.all(20.0),
width: 200.0
),
Container(
child: Text(
'哈囉!世界!',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: TextStyle(color: Colors.blue),
),
color: Colors.yellow,
margin: const EdgeInsets.all(20.0),
width: 400.0
),
],
mainAxisAlignment: MainAxisAlignment.center
)
);
結果畫面如下:
以上範例中都有些設定細節,目前來說並不用太關心它們,現階段最重要的是,認識 Widget 等相關子類的繼承關係,以及各自負責的職責大致為何,這樣面對日後一堆畫面範例設定,才不會搞不清楚,哪個可以設定給哪個,而目前的 Widget 樹是長這樣:
雖然 Widget 樹還很簡單,然而程式碼開始傷眼了,你有辦法看一眼就大致能想像出畫面概貌嗎?當元件描述日趨複雜後,就該考慮重構,將元件抽取出來獨立設計了。

