在 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 樹還很簡單,然而程式碼開始傷眼了,你有辦法看一眼就大致能想像出畫面概貌嗎?當元件描述日趨複雜後,就該考慮重構,將元件抽取出來獨立設計了。