天天看點

32添加元件_Flutter 元件 | Container 容器

0. Container 的簡介

如果你看過

Container

的源碼,會發現它是一個很有意思的元件,它基本上沒幹啥正事,就是将已有的元件拼一拼而已。它是一個

StatelessWidget

,其中

build

方法使用了如下八個元件,本文将從源碼的角度看一下,Container 到底是如何運作的,為其設定的各種屬性都被用在了哪裡。

32添加元件_Flutter 元件 | Container 容器

1. 顔色屬性

LayoutBuilder

篇我們知道,Scaffold 元件的 body 對應上層的區域限制為

BoxConstraints(0.0<=w<=螢幕寬, 0.0<=h<=螢幕高)

。從表現上來看,當隻有 color 屬性時,Container 的尺寸會鋪滿最大限制區域。

32添加元件_Flutter 元件 | Container 容器

[email protected] 2Widget build(BuildContext context) { 3  Widget current = child; 4  if (child == null && (constraints == null || !constraints.isTight)) { 5    current = LimitedBox( 6      maxWidth: 0.0, 7      maxHeight: 0.0, 8      child: ConstrainedBox(constraints: const BoxConstraints.expand()), 9    ); 10  } 11  ... 12  if (color != null) 13    current = ColoredBox(color: color, child: current); 14  ... 15  return current; 16}

從代碼中可以看到,當 child 為 null ,并且 constraints 為 null,

current

會被套上 LimitedBox + ConstrainedBox,其中 ConstrainedBox 的限制是延展的。當顔色非 null,會在

current

上傳套上

ColoredBox

,而

ColoredBox

元件的作用就是在尺寸區域中填充顔色。這就是 Container 的尺寸會鋪滿最大限制區域的原因。

32添加元件_Flutter 元件 | Container 容器

2. child 屬性

如下,可見當設定

child

屬性後,

Container

的布局尺寸會與

child

一緻。來看下源碼這是為什麼。

32添加元件_Flutter 元件 | Container 容器

通過上面的源碼也可以看出, 當 child 屬性非空時,就不會包裹

LimitedBox

+

ConstrainedBox

。從下面的調試結構看,隻有

ColoredBox

+

Text

。是以就沒有了區域的延展,進而和

child

尺寸一緻。

32添加元件_Flutter 元件 | Container 容器

3. 寬高屬性

添加寬高屬性之後,

Container

的布局區域會變為指定區域。那源碼中是如何實作的呢?

32添加元件_Flutter 元件 | Container 容器

1  Container({ 2    //... 3    double width, 4    double height, 5    //... 6  }) : //... 7       constraints = 8        (width != null || height != null) 9          ? constraints?.tighten(width: width, height: height) 10            ?? BoxConstraints.tightFor(width: width, height: height)

當寬高被設定時,

constraints

屬性會被設定為對應寬高的緊限制,也就是把尺寸定死。

32添加元件_Flutter 元件 | Container 容器

4.alignment 屬性

通過 alignment 可以将子元件在容器區域内對齊擺放。那源碼中是如何實作的呢?

32添加元件_Flutter 元件 | Container 容器

1if (alignment != null) 2  current = Align(alignment: alignment, child: current);

其實處理非常簡單,就是在

alignment

非空時,套上一個

Align

元件。

32添加元件_Flutter 元件 | Container 容器

5.padding 和 margin 屬性

通過布局檢視器可以看出,外邊距是

margin

,内邊距是

padding

32添加元件_Flutter 元件 | Container 容器

1EdgeInsetsGeometry get _paddingIncludingDecoration { 2  if (decoration == null || decoration.padding == null) 3    return padding; 4  final EdgeInsetsGeometry decorationPadding = decoration.padding; 5  if (padding == null) 6    return decorationPadding; 7  return padding.add(decorationPadding); 8} [email protected] 10Widget build(BuildContext context) { 11  Widget current = child; 12   //... 13  final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration; 14  if (effectivePadding != null) 15    current = Padding(padding: effectivePadding, child: current); 16  //... 17  if (margin != null) 18  current = Padding(padding: margin, child: current); 19  return current; 20}

從源碼中可以看出

padding

margin

屬性都是使用

Padding

屬性完成的,隻不過

margin

在外側包裹而已。可以看到實際的

padding

值是通過

_paddingIncludingDecoration

獲得的,其中會包含裝飾的邊距,預設為 0。

32添加元件_Flutter 元件 | Container 容器

6.decoration 和 foregroundDecoration 屬性

decoration 屬性和 foregroundDecoration 非空時,都會包裹一個

DecoratedBox

元件。foregroundDecoration 是前景裝飾,是以較背景裝飾而言在上層。關于

DecoratedBox

元件的使用在之前介紹過,這裡就不再詳細介紹了,可詳見之前的

DecoratedBox

元件文章。

32添加元件_Flutter 元件 | Container 容器

1if (decoration != null) 2  current = DecoratedBox(decoration: decoration, child: current); 3 4if (foregroundDecoration != null) { 5  current = DecoratedBox( 6    decoration: foregroundDecoration, 7    position: DecorationPosition.foreground, 8    child: current, 9  ); 10}

7. constraints 屬性

constraints

屬性非空,會包裹上

ConstrainedBox

,此時容器的區域會被限制,如下測試中,限制為最小寬高 80、32,最大寬高 100,140。即說明目前容器的所占區域不能在限制之外,這裡寬高為 8,比最小區域寬高小,則會使用最小寬高。

32添加元件_Flutter 元件 | Container 容器

當設定大小比限制區域大時,會使用最大的限制區域,也就是說如果目前容器的布局區域發生變化,

constraints

會保證容器尺寸在一個範圍内變化。比如盛放文字時,文字的長短不同導緻布局尺寸不同,通過限制可以讓文字在一定的尺寸範圍内變動。

32添加元件_Flutter 元件 | Container 容器

1if (constraints != null) 2  current = ConstrainedBox(constraints: constraints, child: current);

8. clipBehavior 裁剪行為

Clip

是一個枚舉類,包含四種形式,如下:

1enum Clip { 2  none, // 無 3  hardEdge, // 硬邊緣 4  antiAlias, // 抗鋸齒 5  antiAliasWithSaveLayer, // 抗鋸齒儲存圖層 6}

從源碼中可以看出

clipBehavior

不為

Clip.none

時,必須有

decoration

屬性。這裡将

current

包裹一層

ClipPath

clipBehavior

就是在該元件中使用的。這裡的裁剪使用

_DecorationClipper

,通過

decoration

擷取裁剪路徑,也就是圓角裝飾時的裁剪行為。

32添加元件_Flutter 元件 | Container 容器

1if (clipBehavior != Clip.none) { 2  assert(decoration != null); 3  current = ClipPath( 4    clipper: _DecorationClipper( 5      textDirection: Directionality.of(context), 6      decoration: decoration 7    ), 8    clipBehavior: clipBehavior, 9    child: current, 10  ); 11} 12 13/// A clipper that uses [Decoration.getClipPath] to clip. 14class _DecorationClipper extends CustomClipper<Path> { 15  _DecorationClipper({ 16    TextDirection textDirection, 17    @required this.decoration 18  }) : assert(decoration != null), 19       textDirection = textDirection ?? TextDirection.ltr; 20 21  final TextDirection textDirection; 22  final Decoration decoration; 23 24  @override 25  Path getClip(Size size) { 26    return decoration.getClipPath(Offset.zero & size, textDirection); 27  } 28 29  @override 30  bool shouldReclip(_DecorationClipper oldClipper) { 31    return oldClipper.decoration != decoration 32        || oldClipper.textDirection != textDirection; 33  } 34}

9. transform 屬性

transform 接收一個

Matrix4

的變化矩陣對象,可以據此完成一些移動、旋轉、縮放的變換效果。不過通過源碼可以看出

Container

元件隻是對

Transform

的一個簡單封裝,實際上

Transform

還可以指定

變化中心 origin

對齊模式  alignment

等。這樣可以看出來

Container

隻是為了元件的簡化使用,并非全權将這些元件的功能進行內建。

32添加元件_Flutter 元件 | Container 容器

1if (transform != null) 2  current = Transform(transform: transform, child: current);

10. Container 元件存在的意義

對于這些

SingleChildRenderObjectWidget

,由于各自的屬性比較少,有些功能很常用,當聯合使用時,就會一層層嵌套,導緻使用的體驗不是很好。如果沒有

Container

元件,那麼要完成上面的效果,你就需要使用下面右側的實作方式,将這些小元件一個個嵌套,這樣用起來是非常麻煩和别扭的。

當有了

Container

,雖然它沒有幹什麼非常偉大的事,卻實實在在地将這八個元件整合到了一起。如右側圖檔,使用起來就非常精簡。但本質上還是那些元件的功勞,這就是一種封裝,将多個子系統内聚,對外界提供通路的接口,表面上操作的是外表的接口,實際上是子系統的運作。

32添加元件_Flutter 元件 | Container 容器

Container 是一個 StatelessWidget,它隻需要完成

build

的任務,依賴其他元件來完成任務,這是一件比較輕松的事。通過設定

Container

元件的屬性,再将這些屬性移交給内部的各個元件,可以很有效地

表象的樹狀結構拉平

,這樣的好處是提供代碼的

易讀性

,通過

Container

的元件名,也有一定的

語義性

。更友善使用者的了解和使用。我們再反過來思考一下,源碼中可以這樣,如果有類似的場景,很多短小的層級結構,我們也可以适當地封裝一個元件進行優化。

從源碼可以看出對于 Align 、Transform 元件,Container 并沒有将它們全部屬性都內建進來。這樣看來

Container

隻是一個通才,什麼都能幹,但并不需要樣樣都精。如果暴露了過多的屬性,會增加使用者使用的複雜性。是以凡事适度,才能有最好的效果。

最後說一下,通過源碼分析後,我們應該可以明白,有些很簡單的場景是不需要使用

Container

的,比如隻是為了加個

Padding

、隻是顯示一下顔色、隻是進行變換等,使用對應的元件即可。當需要同時使用幾個功能時,使用 Container 時也不必有什麼負擔,擔心使用 Container 低效什麼的,其實就是在元素樹裡多了個元素而已,代碼可讀性的價值遠遠在其之上,自己一層層疊也可能是

多寫多錯

。了解  

Container

的源碼之後,在使用時便不再陌生,一個黑盒被照亮後,在使用它的時候,你就會多一份自信。這篇就到這,謝謝觀看。