若是你看過 Container
的源碼,會發現它是一個頗有意思的組件,它基本上沒幹啥正事,就是將已有的組件拼一拼而已。它是一個 StatelessWidget
,其中 build
方法使用了以下八個組件,本文將從源碼的角度看一下,Container 究竟是如何運做的,爲其設置的各類屬性都被用在了哪裏。web
在 LayoutBuilder
篇咱們知道,Scaffold 組件的 body 對應上層的區域約束爲 BoxConstraints(0.0<=w<=屏幕寬, 0.0<=h<=屏幕高)
。從表現上來看,當只有 color 屬性時,Container 的尺寸會鋪滿最大約束區域。編程
@override
Widget build(BuildContext context) {
Widget current = child;
if (child == null && (constraints == null || !constraints.isTight)) {
current = LimitedBox(
maxWidth: 0.0,
maxHeight: 0.0,
child: ConstrainedBox(constraints: const BoxConstraints.expand()),
);
}
...
if (color != null)
current = ColoredBox(color: color, child: current);
...
return current;
}
複製代碼
從代碼中能夠看到,當 child 爲 null ,而且 constraints 爲 null,current
會被套上 LimitedBox + ConstrainedBox,其中 ConstrainedBox 的約束是延展的。 當顏色非 null,會在 current
上傳套上 ColoredBox
,而 ColoredBox
組件的做用就是在尺寸區域中填充顏色。這就是 Container 的尺寸會鋪滿最大約束區域的緣由。微信
以下,可見當設置 child
屬性後,Container
的佈局尺寸會與 child
一致。來看下源碼這是爲何。markdown
經過上面的源碼也能夠看出, 當 child 屬性非空時,就不會包裹 LimitedBox
+ ConstrainedBox
。從下面的調試結構看,只有 ColoredBox
+ Text
。 因此就沒有了區域的延展,從而和 child
尺寸一致。less
添加寬高屬性以後,Container
的佈局區域會變爲指定區域。那源碼中是如何實現的呢?ide
Container({
//...
double width,
double height,
//...
}) : //...
constraints =
(width != null || height != null)
? constraints?.tighten(width: width, height: height)
?? BoxConstraints.tightFor(width: width, height: height)
複製代碼
當寬高被設置時,constraints
屬性會被設置爲對應寬高的緊約束,也就是把尺寸定死。源碼分析
經過 alignment 能夠將子組件在容器區域內對齊擺放。那源碼中是如何實現的呢?佈局
if (alignment != null)
current = Align(alignment: alignment, child: current);
複製代碼
其實處理很是簡單,就是在 alignment
非空時,套上一個 Align
組件。測試
經過佈局查看器能夠看出,外邊距是 margin
,內邊距是 padding
。優化
EdgeInsetsGeometry get _paddingIncludingDecoration {
if (decoration == null || decoration.padding == null)
return padding;
final EdgeInsetsGeometry decorationPadding = decoration.padding;
if (padding == null)
return decorationPadding;
return padding.add(decorationPadding);
}
@override
Widget build(BuildContext context) {
Widget current = child;
//...
final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
//...
if (margin != null)
current = Padding(padding: margin, child: current);
return current;
}
複製代碼
從源碼中能夠看出 padding
和 margin
屬性都是使用 Padding
屬性完成的,只不過 margin
在外側包裹而已。能夠看到實際的 padding
值是經過 _paddingIncludingDecoration
得到的,其中會包含裝飾的邊距,默認爲 0。
decoration 屬性和 foregroundDecoration 非空時,都會包裹一個 DecoratedBox
組件。 foregroundDecoration 是前景裝飾,因此較背景裝飾而言在上層。關於 DecoratedBox
組件的使用在以前介紹過,這裏就再也不詳細介紹了,可詳見以前的 DecoratedBox
組件文章。
if (decoration != null)
current = DecoratedBox(decoration: decoration, child: current);
if (foregroundDecoration != null) {
current = DecoratedBox(
decoration: foregroundDecoration,
position: DecorationPosition.foreground,
child: current,
);
}
複製代碼
constraints
屬性非空,會包裹上 ConstrainedBox
,此時容器的區域會被約束,以下測試中,約束爲最小寬高 80、32,最大寬高 100,140。即說明當前容器的所佔區域不能在約束以外,這裏寬高爲 8,比最小區域寬高小,則會使用最小寬高。
當設定大小比約束區域大時,會使用最大的約束區域,也就是說若是當前容器的佈局區域發生變化, constraints
會保證容器尺寸在一個範圍內變化。好比盛放文字時,文字的長短不一樣致使佈局尺寸不一樣,經過約束可讓文字在必定的尺寸範圍內變更。
if (constraints != null)
current = ConstrainedBox(constraints: constraints, child: current);
複製代碼
Clip
是一個枚舉類,包含四種形式,以下:
enum Clip {
none, // 無
hardEdge, // 硬邊緣
antiAlias, // 抗鋸齒
antiAliasWithSaveLayer, // 抗鋸齒保存圖層
}
複製代碼
從源碼中能夠看出 clipBehavior
不爲 Clip.none
時,必須有 decoration
屬性。這裏將 current
包裹一層 ClipPath
,clipBehavior
就是在該組件中使用的。這裏的裁剪使用 _DecorationClipper
,經過 decoration
獲取裁剪路徑,也就是圓角裝飾時的裁剪行爲。
if (clipBehavior != Clip.none) {
assert(decoration != null);
current = ClipPath(
clipper: _DecorationClipper(
textDirection: Directionality.of(context),
decoration: decoration
),
clipBehavior: clipBehavior,
child: current,
);
}
/// A clipper that uses [Decoration.getClipPath] to clip.
class _DecorationClipper extends CustomClipper<Path> {
_DecorationClipper({
TextDirection textDirection,
@required this.decoration
}) : assert(decoration != null),
textDirection = textDirection ?? TextDirection.ltr;
final TextDirection textDirection;
final Decoration decoration;
@override
Path getClip(Size size) {
return decoration.getClipPath(Offset.zero & size, textDirection);
}
@override
bool shouldReclip(_DecorationClipper oldClipper) {
return oldClipper.decoration != decoration
|| oldClipper.textDirection != textDirection;
}
}
複製代碼
transform 接收一個 Matrix4
的變化矩陣對象,能夠據此完成一些移動、旋轉、縮放的變換效果。不過經過源碼能夠看出 Container
組件只是對 Transform
的一個簡單封裝,實際上 Transform
還能夠指定變化中心 origin
、對齊模式 alignment
等。這樣能夠看出來 Container
只是爲了組件的簡化使用,並不是全權將這些組件的功能進行集成。
if (transform != null)
current = Transform(transform: transform, child: current);
複製代碼
對於這些 SingleChildRenderObjectWidget
,因爲各自的屬性比較少,有些功能很經常使用,當聯合使用時,就會一層層嵌套,致使使用的體驗不是很好。若是沒有 Container
組件,那麼要完成上面的效果,你就須要使用下面右側的實現方式,將這些小組件一個個嵌套,這樣用起來是很是麻煩和彆扭的。
當有了 Container
,雖然它沒有幹什麼很是偉大的事,卻實實在在地將這八個組件整合到了一塊兒。如右側圖片,使用起來就很是精簡。但本質上仍是那些組件的功勞,這就是一種封裝,將多個子系統內聚,對外界提供訪問的接口,表面上操做的是外表的接口,其實是子系統的運做。
Container 是一個 StatelessWidget,它只須要完成 build
的任務,依賴其餘組件來完成任務,這是一件比較輕鬆的事。經過設置 Container
組件的屬性,再將這些屬性移交給內部的各個組件,能夠頗有效地表象的樹狀結構拉平
,這樣的好處是提供代碼的易讀性
,經過Container
的組件名,也有必定的語義性
。更方便用戶的理解和使用。 咱們再反過來思考一下,源碼中能夠這樣,若是有相似的場景,不少短小的層級結構,咱們也能夠適當地封裝一個組件進行優化。
從源碼能夠看出對於 Align 、Transform 組件,Container 並無將它們所有屬性都集成進來。 這樣看來 Container
只是一個通才,什麼都能幹,但並不須要樣樣都精。若是暴露了過多的屬性,會增長用戶使用的複雜性。因此凡事適度,纔能有最好的效果。
最後說一下,經過源碼分析後,咱們應該能夠明白,有些很簡單的場景是不須要使用 Container
的,好比只是爲了加個 Padding
、只是顯示一下顏色、只是進行變換等,使用對應的組件便可。當須要同時使用幾個功能時,使用 Container 時也沒必要有什麼負擔,擔憂使用 Container 低效什麼的,其實就是在元素樹裏多了個元素而已,代碼可讀性的價值遠遠在其之上,本身一層層疊也多是多寫多錯
。瞭解 Container
的源碼以後,在使用時便再也不陌生,一個黑盒被照亮後,在使用它的時候,你就會多一份自信。那麼本文就到此結束,謝謝觀看。
@張風捷特烈 2021.01.02 未允禁轉
個人公衆號:編程之王
聯繫我--郵箱:1981462002@qq.com -- 微信:zdl1994328
~ END ~