Flutter 佈局之 Padding、Align、Center

轉載:https://juejin.im/post/5b1cb0c46fb9a01e62598d56

本文主要介紹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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章