Flutter 組件 | 熟悉而陌生的 Container

0. Container 的簡介

若是你看過 Container 的源碼,會發現它是一個頗有意思的組件,它基本上沒幹啥正事,就是將已有的組件拼一拼而已。它是一個 StatelessWidget,其中 build 方法使用了以下八個組件,本文將從源碼的角度看一下,Container 究竟是如何運做的,爲其設置的各類屬性都被用在了哪裏。web

image-20210104221354698


1. 顏色屬性

LayoutBuilder 篇咱們知道,Scaffold 組件的 body 對應上層的區域約束爲 BoxConstraints(0.0<=w<=屏幕寬, 0.0<=h<=屏幕高)。從表現上來看,當只有 color 屬性時,Container 的尺寸會鋪滿最大約束區域。編程

image-20210104200522153

@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 的尺寸會鋪滿最大約束區域的緣由。微信

image-20210104203919102


2. child 屬性

以下,可見當設置 child 屬性後,Container 的佈局尺寸會與 child 一致。來看下源碼這是爲何。markdown

image-20210104205523272

經過上面的源碼也能夠看出, 當 child 屬性非空時,就不會包裹 LimitedBox + ConstrainedBox。從下面的調試結構看,只有 ColoredBox + Text 。 因此就沒有了區域的延展,從而和 child 尺寸一致。less

image-20210104205047135


3. 寬高屬性

添加寬高屬性以後,Container 的佈局區域會變爲指定區域。那源碼中是如何實現的呢?ide

image-20210104223623456

Container({
    //...
    double width,
    double height,
    //...
  }) : //...
       constraints =
        (width != null || height != null)
          ? constraints?.tighten(width: width, height: height)
            ?? BoxConstraints.tightFor(width: width, height: height)
複製代碼

當寬高被設置時,constraints 屬性會被設置爲對應寬高的緊約束,也就是把尺寸定死。源碼分析

image-20210104223551282


4.alignment 屬性

經過 alignment 能夠將子組件在容器區域內對齊擺放。那源碼中是如何實現的呢?佈局

image-20210104224302600

if (alignment != null)
  current = Align(alignment: alignment, child: current);
複製代碼

其實處理很是簡單,就是在 alignment 非空時,套上一個 Align 組件。測試

image-20210104224151550


5.padding 和 margin 屬性

經過佈局查看器能夠看出,外邊距是 margin,內邊距是 padding優化

image-20210104225059004

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;
}
複製代碼

從源碼中能夠看出 paddingmargin 屬性都是使用 Padding 屬性完成的,只不過 margin 在外側包裹而已。能夠看到實際的 padding 值是經過 _paddingIncludingDecoration 得到的,其中會包含裝飾的邊距,默認爲 0。

image-20210104224914989


6.decoration 和 foregroundDecoration 屬性

decoration 屬性和 foregroundDecoration 非空時,都會包裹一個 DecoratedBox 組件。 foregroundDecoration 是前景裝飾,因此較背景裝飾而言在上層。關於 DecoratedBox 組件的使用在以前介紹過,這裏就再也不詳細介紹了,可詳見以前的 DecoratedBox 組件文章。

image-20210104230442993

if (decoration != null)
  current = DecoratedBox(decoration: decoration, child: current);

if (foregroundDecoration != null) {
  current = DecoratedBox(
    decoration: foregroundDecoration,
    position: DecorationPosition.foreground,
    child: current,
  );
}
複製代碼

7. constraints 屬性

constraints 屬性非空,會包裹上 ConstrainedBox ,此時容器的區域會被約束,以下測試中,約束爲最小寬高 80、32,最大寬高 100,140。即說明當前容器的所佔區域不能在約束以外,這裏寬高爲 8,比最小區域寬高小,則會使用最小寬高。

image-20210105121035698

當設定大小比約束區域大時,會使用最大的約束區域,也就是說若是當前容器的佈局區域發生變化, constraints 會保證容器尺寸在一個範圍內變化。好比盛放文字時,文字的長短不一樣致使佈局尺寸不一樣,經過約束可讓文字在必定的尺寸範圍內變更。

image-20210105121322760

if (constraints != null)
  current = ConstrainedBox(constraints: constraints, child: current);
複製代碼

8. clipBehavior 裁剪行爲

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

enum Clip {
  none, // 無
  hardEdge, // 硬邊緣
  antiAlias, // 抗鋸齒
  antiAliasWithSaveLayer, // 抗鋸齒保存圖層
}
複製代碼

從源碼中能夠看出 clipBehavior 不爲 Clip.none 時,必須有 decoration 屬性。這裏將 current 包裹一層 ClipPathclipBehavior 就是在該組件中使用的。這裏的裁剪使用 _DecorationClipper ,經過 decoration 獲取裁剪路徑,也就是圓角裝飾時的裁剪行爲。

image-20210105122402575

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;
  }
}
複製代碼

9. transform 屬性

transform 接收一個 Matrix4 的變化矩陣對象,能夠據此完成一些移動、旋轉、縮放的變換效果。不過經過源碼能夠看出 Container 組件只是對 Transform 的一個簡單封裝,實際上 Transform 還能夠指定變化中心 origin對齊模式 alignment 等。這樣能夠看出來 Container 只是爲了組件的簡化使用,並不是全權將這些組件的功能進行集成。

image-20210105123424763

if (transform != null)
  current = Transform(transform: transform, child: current);
複製代碼

10. Container 組件存在的意義

對於這些 SingleChildRenderObjectWidget ,因爲各自的屬性比較少,有些功能很經常使用,當聯合使用時,就會一層層嵌套,致使使用的體驗不是很好。若是沒有 Container 組件,那麼要完成上面的效果,你就須要使用下面右側的實現方式,將這些小組件一個個嵌套,這樣用起來是很是麻煩和彆扭的。

當有了 Container ,雖然它沒有幹什麼很是偉大的事,卻實實在在地將這八個組件整合到了一塊兒。如右側圖片,使用起來就很是精簡。但本質上仍是那些組件的功勞,這就是一種封裝,將多個子系統內聚,對外界提供訪問的接口,表面上操做的是外表的接口,其實是子系統的運做。

image-20210105124801630

Container 是一個 StatelessWidget,它只須要完成 build 的任務,依賴其餘組件來完成任務,這是一件比較輕鬆的事。經過設置 Container 組件的屬性,再將這些屬性移交給內部的各個組件,能夠頗有效地表象的樹狀結構拉平,這樣的好處是提供代碼的易讀性,經過Container 的組件名,也有必定的語義性。更方便用戶的理解和使用。 咱們再反過來思考一下,源碼中能夠這樣,若是有相似的場景,不少短小的層級結構,咱們也能夠適當地封裝一個組件進行優化。

從源碼能夠看出對於 Align 、Transform 組件,Container 並無將它們所有屬性都集成進來。 這樣看來 Container 只是一個通才,什麼都能幹,但並不須要樣樣都精。若是暴露了過多的屬性,會增長用戶使用的複雜性。因此凡事適度,纔能有最好的效果。

最後說一下,經過源碼分析後,咱們應該能夠明白,有些很簡單的場景是不須要使用 Container 的,好比只是爲了加個 Padding 、只是顯示一下顏色、只是進行變換等,使用對應的組件便可。當須要同時使用幾個功能時,使用 Container 時也沒必要有什麼負擔,擔憂使用 Container 低效什麼的,其實就是在元素樹裏多了個元素而已,代碼可讀性的價值遠遠在其之上,本身一層層疊也多是多寫多錯。瞭解 Container 的源碼以後,在使用時便再也不陌生,一個黑盒被照亮後,在使用它的時候,你就會多一份自信。那麼本文就到此結束,謝謝觀看。


@張風捷特烈 2021.01.02 未允禁轉
個人公衆號:編程之王
聯繫我--郵箱:1981462002@qq.com -- 微信:zdl1994328
~ END ~

相關文章
相關標籤/搜索