本文主要介紹Flutter中非常常見(jiàn)的Container,列舉了一些實(shí)際例子介紹如何使用。
1. 簡(jiǎn)介
A convenience widget that combines common painting, positioning, and sizing widgets.
Container在Flutter中太常見(jiàn)了。官方給出的簡(jiǎn)介,是一個(gè)結(jié)合了繪制(painting)、定位(positioning)以及尺寸(sizing)widget的widget。
可以得出幾個(gè)信息,它是一個(gè)組合的widget,內(nèi)部有繪制widget、定位widget、尺寸widget。后續(xù)看到的不少widget,都是通過(guò)一些更基礎(chǔ)的widget組合而成的。
1.1 組成
Container的組成如下:
- 最里層的是child元素;
- child元素首先會(huì)被padding包著;
- 然后添加額外的constraints限制;
- 最后添加margin。
Container的繪制的過(guò)程如下:
- 首先會(huì)繪制transform效果;
- 接著繪制decoration;
- 然后繪制child;
- 最后繪制foregroundDecoration。
Container自身尺寸的調(diào)節(jié)分兩種情況:
- Container在沒(méi)有子節(jié)點(diǎn)(children)的時(shí)候,會(huì)試圖去變得足夠大。除非constraints是unbounded限制,在這種情況下,Container會(huì)試圖去變得足夠小。
- 帶子節(jié)點(diǎn)的Container,會(huì)根據(jù)子節(jié)點(diǎn)尺寸調(diào)節(jié)自身尺寸,但是Container構(gòu)造器中如果包含了width、height以及constraints,則會(huì)按照構(gòu)造器中的參數(shù)來(lái)進(jìn)行尺寸的調(diào)節(jié)。
1.2 布局行為
由于Container組合了一系列的widget,這些widget都有自己的布局行為,因此Container的布局行為有時(shí)候是比較復(fù)雜的。
一般情況下,Container會(huì)遵循如下順序去嘗試布局:
- 對(duì)齊(alignment);
- 調(diào)節(jié)自身尺寸適合子節(jié)點(diǎn);
- 采用width、height以及constraints布局;
- 擴(kuò)展自身去適應(yīng)父節(jié)點(diǎn);
- 調(diào)節(jié)自身到足夠小。
進(jìn)一步說(shuō):
- 如果沒(méi)有子節(jié)點(diǎn)、沒(méi)有設(shè)置width、height以及constraints,并且父節(jié)點(diǎn)沒(méi)有設(shè)置unbounded的限制,Container會(huì)將自身調(diào)整到足夠小。
- 如果沒(méi)有子節(jié)點(diǎn)、對(duì)齊方式(alignment),但是提供了width、height或者constraints,那么Container會(huì)根據(jù)自身以及父節(jié)點(diǎn)的限制,將自身調(diào)節(jié)到足夠小。
- 如果沒(méi)有子節(jié)點(diǎn)、width、height、constraints以及alignment,但是父節(jié)點(diǎn)提供了bounded限制,那么Container會(huì)按照父節(jié)點(diǎn)的限制,將自身調(diào)整到足夠大。
- 如果有alignment,父節(jié)點(diǎn)提供了unbounded限制,那么Container將會(huì)調(diào)節(jié)自身尺寸來(lái)包住child;
- 如果有alignment,并且父節(jié)點(diǎn)提供了bounded限制,那么Container會(huì)將自身調(diào)整的足夠大(在父節(jié)點(diǎn)的范圍內(nèi)),然后將child根據(jù)alignment調(diào)整位置;
- 含有child,但是沒(méi)有width、height、constraints以及alignment,Container會(huì)將父節(jié)點(diǎn)的constraints傳遞給child,并且根據(jù)child調(diào)整自身。
另外,margin以及padding屬性也會(huì)影響到布局。
1.3 繼承關(guān)系
Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > Container
從繼承關(guān)系可以看出,Container是一個(gè)StatelessWidget。Container并不是一個(gè)最基礎(chǔ)的widget,它是由一系列的基礎(chǔ)widget組合而成。
2. 源碼解析
構(gòu)造函數(shù)如下:
Container({
Key key,
this.alignment,
this.padding,
Color color,
Decoration decoration,
this.foregroundDecoration,
double width,
double height,
BoxConstraints constraints,
this.margin,
this.transform,
this.child,
})
平時(shí)使用最多的,也就是padding、color、width、height、margin屬性。
2.1 屬性解析
key:Container唯一標(biāo)識(shí)符,用于查找更新。
alignment:控制child的對(duì)齊方式,如果container或者container父節(jié)點(diǎn)尺寸大于child的尺寸,這個(gè)屬性設(shè)置會(huì)起作用,有很多種對(duì)齊方式。
padding:decoration內(nèi)部的空白區(qū)域,如果有child的話,child位于padding內(nèi)部。padding與margin的不同之處在于,padding是包含在content內(nèi),而margin則是外部邊界,設(shè)置點(diǎn)擊事件的話,padding區(qū)域會(huì)響應(yīng),而margin區(qū)域不會(huì)響應(yīng)。
color:用來(lái)設(shè)置container背景色,如果foregroundDecoration設(shè)置的話,可能會(huì)遮蓋color效果。
decoration:繪制在child后面的裝飾,設(shè)置了decoration的話,就不能設(shè)置color屬性,否則會(huì)報(bào)錯(cuò),此時(shí)應(yīng)該在decoration中進(jìn)行顏色的設(shè)置。
foregroundDecoration:繪制在child前面的裝飾。
width:container的寬度,設(shè)置為double.infinity可以強(qiáng)制在寬度上撐滿,不設(shè)置,則根據(jù)child和父節(jié)點(diǎn)兩者一起布局。
height:container的高度,設(shè)置為double.infinity可以強(qiáng)制在高度上撐滿。
constraints:添加到child上額外的約束條件。
margin:圍繞在decoration和child之外的空白區(qū)域,不屬于內(nèi)容區(qū)域。
transform:設(shè)置container的變換矩陣,類型為Matrix4。
child:container中的內(nèi)容widget。
2.2 一個(gè)例子
new Container(
constraints: new BoxConstraints.expand(
height:Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0,
),
decoration: new BoxDecoration(
border: new Border.all(width: 2.0, color: Colors.red),
color: Colors.grey,
borderRadius: new BorderRadius.all(new Radius.circular(20.0)),
image: new DecorationImage(
image: new NetworkImage('http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'),
centerSlice: new Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0),
),
),
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: new Text('Hello World',
style: Theme.of(context).textTheme.display1.copyWith(color: Colors.black)),
transform: new Matrix4.rotationZ(0.3),
)
這是官方文檔給出例子的一個(gè)變種,包含屬性比較全,可以看下其用法。實(shí)際運(yùn)行效果如下:

其中decoration可以設(shè)置邊框、背景色、背景圖片、圓角等屬性,非常實(shí)用。對(duì)于transform這個(gè)屬性,一般有過(guò)其他平臺(tái)開(kāi)發(fā)經(jīng)驗(yàn)的,都大致了解,這種變換,一般不是變換的實(shí)際位置,而是變換的繪制效果,也就是說(shuō)它的點(diǎn)擊以及尺寸、間距等都是按照未變換前的。
2.3 源碼
decoration = decoration ?? (color != null ? new BoxDecoration(color: color) : null),
可以看出,對(duì)于顏色的設(shè)置,最后都是轉(zhuǎn)換為decoration來(lái)進(jìn)行繪制的。如果同時(shí)包含decoration和color兩種屬性,則會(huì)報(bào)錯(cuò)。
@override
Widget build(BuildContext context) {
Widget current = child;
if (child == null && (constraints == null || !constraints.isTight)) {
current = new LimitedBox(
maxWidth: 0.0,
maxHeight: 0.0,
child: new ConstrainedBox(constraints: const BoxConstraints.expand())
);
}
if (alignment != null)
current = new Align(alignment: alignment, child: current);
final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = new Padding(padding: effectivePadding, child: current);
if (decoration != null)
current = new DecoratedBox(decoration: decoration, child: current);
if (foregroundDecoration != null) {
current = new DecoratedBox(
decoration: foregroundDecoration,
position: DecorationPosition.foreground,
child: current
);
}
if (constraints != null)
current = new ConstrainedBox(constraints: constraints, child: current);
if (margin != null)
current = new Padding(padding: margin, child: current);
if (transform != null)
current = new Transform(transform: transform, child: current);
return current;
}
Container的build函數(shù)不長(zhǎng),繪制也是一個(gè)線性的判斷的過(guò)程,一層一層的包裹著widget,去實(shí)現(xiàn)不同的樣式。
最里層的是child,如果為空或者其他約束條件,則最里層包含的為一個(gè)LimitedBox,然后依次是Align、Padding、DecoratedBox、前景DecoratedBox、ConstrainedBox、Padding(實(shí)現(xiàn)margin效果)、Transform。
Container的源碼本身并不復(fù)雜,復(fù)雜的是它的各種布局表現(xiàn)。我們謹(jǐn)記住一點(diǎn),如果內(nèi)部不設(shè)置約束,則按照父節(jié)點(diǎn)盡可能的擴(kuò)大,如果內(nèi)部有約束,則按照內(nèi)部來(lái)。
2.4 使用場(chǎng)景
Container算是目前項(xiàng)目中,最經(jīng)常用到的一個(gè)widget。在實(shí)際使用過(guò)程中,筆者在以下情況會(huì)使用到Container,當(dāng)然并不是絕對(duì)的,也可以通過(guò)其他widget來(lái)實(shí)現(xiàn)。
- 需要設(shè)置間隔(這種情況下,如果只是單純的間隔,也可以通過(guò)Padding來(lái)實(shí)現(xiàn));
- 需要設(shè)置背景色;
- 需要設(shè)置圓角或者邊框的時(shí)候(ClipRRect也可以實(shí)現(xiàn)圓角效果);
- 需要對(duì)齊(Align也可以實(shí)現(xiàn));
- 需要設(shè)置背景圖片的時(shí)候(也可以使用Stack實(shí)現(xiàn))。
3. 例子
接下來(lái)我們?cè)囍プ鲆粋€(gè)圓角按鈕,它包含以下特性:
- 支持設(shè)置按鈕的三種狀態(tài)(正常態(tài)、點(diǎn)擊態(tài)、禁用態(tài))的色值;
- 支持設(shè)置按鈕標(biāo)題;
- 支持設(shè)置寬高;
- 支持點(diǎn)擊回調(diào);
根據(jù)上面介紹,利用decoration這個(gè)屬性,基本上就可以完成效果了,至于點(diǎn)擊效果以及點(diǎn)擊回調(diào),則使用一個(gè)GestureDetector就可以完成了。實(shí)際的例子非常簡(jiǎn)單,在這里就不貼代碼了。實(shí)際運(yùn)行效果如下所示:

3.1 注意事項(xiàng)
這個(gè)小控件,寫起來(lái)很簡(jiǎn)單,本身沒(méi)有什么難度,只是純粹的介紹了Container的使用方法,但是有一個(gè)地方需要注意的。在控件的deactivate狀態(tài),我們需要將控件的屬性初始到最開(kāi)始的狀態(tài),例如在本例中,有如下代碼:
@override
void deactivate() {
super.deactivate();
currentColor = widget.backgroundColor;
}
這么做是為什么了?是因?yàn)樵邳c(diǎn)擊按鈕進(jìn)行頁(yè)面跳轉(zhuǎn)的時(shí)候,按鈕處在點(diǎn)擊態(tài),當(dāng)我們返回的時(shí)候,頁(yè)面還是處在點(diǎn)擊態(tài),這顯然就不正確了,因此需要我們手動(dòng)的在deactivate狀態(tài)下,將控件恢復(fù)到初始狀態(tài)。但是呢,這個(gè)設(shè)置顏色,并不是說(shuō)在deactivate的時(shí)候,就立馬去刷新控件,而是在下次再進(jìn)入這個(gè)頁(yè)面的時(shí)候,再次運(yùn)行build的時(shí)候,會(huì)按照這個(gè)初始值進(jìn)行繪制,也就是恢復(fù)到了最開(kāi)始的狀態(tài)。
3.2 代碼
代碼Github地址,這是一個(gè)系列的項(xiàng)目,如果不出意外,會(huì)將Flutter中常見(jiàn)的二十多種布局widget都介紹一下。
4. 后話
筆者建了一個(gè)flutter學(xué)習(xí)相關(guān)的項(xiàng)目,github地址,里面包含了筆者寫的關(guān)于flutter學(xué)習(xí)相關(guān)的一些文章,會(huì)定期更新,也會(huì)上傳一些學(xué)習(xí)demo,歡迎大家關(guān)注。