Flutter 佈局(二)- Padding、Align、Center詳解

吹個大氣球發表於2018-06-10

本文主要介紹Flutter佈局中的Padding、Align以及Center控制元件,詳細介紹了其布舉行為以及使用場景,並對原始碼進行了分析。

1. Padding

A widget that insets its child by the given padding.

1.1 簡介

Padding在Flutter中用的也挺多的,作為一個基礎的控制元件,功能非常單一,給子節點設定padding屬性。寫過其他端的都瞭解這個屬性,就是設定內邊距屬性,內邊距的空白區域,也是widget的一部分。

Flutter中並沒有單獨的Margin控制元件,在Container中有margin屬性,看原始碼關於margin的實現。

if (margin != null)
  current = new Padding(padding: margin, child: current);
複製程式碼

不難看出,Flutter中淡化了margin以及padding的區別,margin實質上也是由Padding實現的。

1.2 佈局行為

Padding的佈局分為兩種情況:

  • 當child為空的時候,會產生一個寬為left+right,高為top+bottom的區域;
  • 當child不為空的時候,Padding會將佈局約束傳遞給child,根據設定的padding屬性,縮小child的佈局尺寸。然後Padding將自己調整到child設定了padding屬性的尺寸,在child周圍建立空白區域。

1.3 繼承關係

Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Padding
複製程式碼

從繼承關係可以看出,Padding控制元件是一個基礎控制元件,不像Container這種組合控制元件。Container中的margin以及padding屬性都是利用Padding控制元件去實現的。

1.3.1 關於SingleChildRenderObjectWidget

SingleChildRenderObjectWidget是RenderObjectWidgets的一個子類,用於限制只能有一個子節點。它只提供child的儲存,而不提供實際的更新邏輯。

1.3.2 關於RenderObjectWidgets

RenderObjectWidgets為RenderObjectElement提供配置,而RenderObjectElement包含著(wrap)RenderObject,RenderObject則是在應用中提供實際的繪製(rendering)的元素。

1.4 示例程式碼

例項程式碼直接上官方的例子,非常的簡單:

new Padding(
  padding: new EdgeInsets.all(8.0),
  child: const Card(child: const Text('Hello World!')),
)
複製程式碼

例子中對Card設定了一個寬度為8的內邊距。

1.5 原始碼解析

建構函式如下:

const Padding({
    Key key,
    @required this.padding,
    Widget child,
  })
複製程式碼

包含一個padding屬性,相當的簡單。

1.5.1 屬性解析

padding:padding的型別為EdgeInsetsGeometry,EdgeInsetsGeometry是EdgeInsets以及EdgeInsetsDirectional的基類。在實際使用中不涉及到國際化,例如適配阿拉伯地區等,一般都是使用EdgeInsets。EdgeInsetsDirectional光看命名就知道跟方向相關,因此它的四個邊距不限定上下左右,而是根據方向來定的。

1.5.2 原始碼

@override
  RenderPadding createRenderObject(BuildContext context) {
    return new RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
   );
}
複製程式碼

Padding的建立函式,實際上是由RenderPadding來進行的。

關於RenderPadding的實際佈局表現,當child為null的時候:

if (child == null) {
  size = constraints.constrain(new Size(
    _resolvedPadding.left + _resolvedPadding.right,
    _resolvedPadding.top + _resolvedPadding.bottom
  ));
  return;
}
複製程式碼

返回一個寬為_resolvedPadding.left+_resolvedPadding.right,高為_resolvedPadding.top+_resolvedPadding.bottom的區域。

當child不為null的時候,經歷了三個過程,即調整child尺寸、調整child位置以及調整Padding尺寸,最終達到實際的佈局效果。

// 調整child尺寸
final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
child.layout(innerConstraints, parentUsesSize: true);

// 調整child位置
final BoxParentData childParentData = child.parentData;
childParentData.offset = new Offset(_resolvedPadding.left, _resolvedPadding.top);

// 調整Padding尺寸
size = constraints.constrain(new Size(
  _resolvedPadding.left + child.size.width + _resolvedPadding.right,
  _resolvedPadding.top + child.size.height + _resolvedPadding.bottom
));
複製程式碼

到此處,上面介紹的padding佈局行為就解釋的通了。

1.6 使用場景

Padding本身還是挺簡單的,基本上需要間距的地方,它都能夠使用。如果在單一的間距場景,使用Padding比Container的成本要小一些,畢竟Container裡面包含了多個widget。Padding能夠實現的,Container都能夠實現,只不過,Container更加的複雜。

2. Align

A widget that aligns its child within itself and optionally sizes itself based on the child's size.

2.1 簡介

在其他端的開發,Align一般都是當做一個控制元件的屬性,並沒有拿出來當做一個單獨的控制元件。Align本身實現的功能並不複雜,設定child的對齊方式,例如居中、居左居右等,並根據child尺寸調節自身尺寸。

2.2 佈局行為

Align的佈局行為分為兩種情況:

  • 當widthFactor和heightFactor為null的時候,當其有限制條件的時候,Align會根據限制條件儘量的擴充套件自己的尺寸,當沒有限制條件的時候,會調整到child的尺寸;
  • 當widthFactor或者heightFactor不為null的時候,Aligin會根據factor屬性,擴充套件自己的尺寸,例如設定widthFactor為2.0的時候,那麼,Align的寬度將會是child的兩倍。

Align為什麼會有這樣的佈局行為呢?原因很簡單,設定對齊方式的話,如果外層元素尺寸不確定的話,內部的對齊就無法確定。因此,會有寬高因子、根據外層限制擴大到最大尺寸、外層不確定時調整到child尺寸這些行為。

2.3 繼承關係

Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Align
複製程式碼

可以看出,Align跟Padding一樣,也是一個非常基礎的元件,Container中的align屬性,也是使用Align去實現的。

2.4 示例程式碼

new Align(
  alignment: Alignment.center,
  widthFactor: 2.0,
  heightFactor: 2.0,
  child: new Text("Align"),
)
複製程式碼

例子依舊很簡單,設定一個寬高為child兩倍區域的Align,其child處在正中間。

2.5 原始碼解析

const Align({
    Key key,
    this.alignment: Alignment.center,
    this.widthFactor,
    this.heightFactor,
    Widget child
  })
複製程式碼

Align的建構函式基本上就是寬高因子、對齊方式屬性。日常使用中,寬高因子屬性基本上用的不多。如果是複雜的佈局,Container內部的align屬性也可以實現相同的效果。

2.5.1 屬性解析

alignment:對齊方式,一般會使用系統預設提供的9種方式,但是並不是說只有這9種,例如如下的定義。系統提供的9種方式只是預先定義好的。

/// The top left corner.
static const Alignment topLeft = const Alignment(-1.0, -1.0);
複製程式碼

Alignment實際上是包含了兩個屬性的,其中第一個引數,-1.0是左邊對齊,1.0是右邊對齊,第二個引數,-1.0是頂部對齊,1.0是底部對齊。根據這個規則,我們也可以自定義我們需要的對齊方式,例如

/// 居右高於底部1/4處.
static const Alignment rightHalfBottom = alignment: const Alignment(1.0, 0.5),
複製程式碼

widthFactor:寬度因子,如果設定的話,Align的寬度就是child的寬度乘以這個值,不能為負數。

heightFactor:高度因子,如果設定的話,Align的高度就是child的高度乘以這個值,不能為負數。

2.5.2 原始碼

@override
  RenderPositionedBox createRenderObject(BuildContext context) {
    return new RenderPositionedBox(
      alignment: alignment,
      widthFactor: widthFactor,
      heightFactor: heightFactor,
      textDirection: Directionality.of(context),
    );
  }
複製程式碼

Align的實際構造呼叫的是RenderPositionedBox

RenderPositionedBox的佈局表現如下:

// 根據_widthFactor、_heightFactor以及限制因素來確定寬高
final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;

if (child != null) {
  //  如果child不為null,則根據規則設定Align的寬高,如果需要縮放,則根據_widthFactor是否為null來進行縮放,如果不需要,則儘量擴充套件。
  child.layout(constraints.loosen(), parentUsesSize: true);
  size = constraints.constrain(new Size(shrinkWrapWidth ? child.size.width * (_widthFactor ?? 1.0) : double.infinity,
                                        shrinkWrapHeight ? child.size.height * (_heightFactor ?? 1.0) : double.infinity));
  alignChild();
} else {
  // 如果child為null,如果需要縮放,則變為0,否則就儘量擴充套件
  size = constraints.constrain(new Size(shrinkWrapWidth ? 0.0 : double.infinity,
                                        shrinkWrapHeight ? 0.0 : double.infinity));
}
複製程式碼

2.6 使用場景

一般在對齊場景下使用,例如需要右對齊或者底部對齊之類的。它能夠實現的功能,Container都能實現。

3. Center

Center繼承自Align,只不過是將alignment設定為Alignment.center,其他屬性例如widthFactor、heightFactor,佈局行為,都與Align完全一樣,在這裡就不再單獨做介紹了。Center原始碼如下,沒有設定alignment屬性,是因為Align預設的對齊方式就是居中。

class Center extends Align {
  /// Creates a widget that centers its child.
  const Center({ Key key, double widthFactor, double heightFactor, Widget child })
    : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}
複製程式碼

4. 後話

筆者建了一個flutter學習相關的專案,github地址,裡面包含了筆者寫的關於flutter學習相關的一些文章,會定期更新,也會上傳一些學習demo,歡迎大家關注。

5. 參考

  1. Padding class
  2. EdgeInsetsGeometry class
  3. EdgeInsets class
  4. EdgeInsetsDirectional class
  5. RenderPadding class
  6. Align class
  7. Center class

相關文章