本文主要介紹Flutter布局中的Padding、Align以及Center控件,詳細介紹了其布局行為以及使用場景,并對源碼進行了分析。
1. Padding
A widget that insets its child by the given padding.
1.1 簡介
Padding在Flutter中用的也挺多的,作為一個基礎(chǔ)的控件,功能非常單一,給子節(jié)點設(shè)置padding屬性。寫過其他端的都了解這個屬性,就是設(shè)置內(nèi)邊距屬性,內(nèi)邊距的空白區(qū)域,也是widget的一部分。
Flutter中并沒有單獨的Margin控件,在Container中有margin屬性,看源碼關(guān)于margin的實現(xiàn)。
if (margin != null)
current = new Padding(padding: margin, child: current);
不難看出,F(xiàn)lutter中淡化了margin以及padding的區(qū)別,margin實質(zhì)上也是由Padding實現(xiàn)的。
1.2 布局行為
Padding的布局分為兩種情況:
- 當child為空的時候,會產(chǎn)生一個寬為left+right,高為top+bottom的區(qū)域;
- 當child不為空的時候,Padding會將布局約束傳遞給child,根據(jù)設(shè)置的padding屬性,縮小child的布局尺寸。然后Padding將自己調(diào)整到child設(shè)置了padding屬性的尺寸,在child周圍創(chuàng)建空白區(qū)域。
1.3 繼承關(guān)系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Padding
從繼承關(guān)系可以看出,Padding控件是一個基礎(chǔ)控件,不像Container這種組合控件。Container中的margin以及padding屬性都是利用Padding控件去實現(xiàn)的。
1.3.1 關(guān)于SingleChildRenderObjectWidget
SingleChildRenderObjectWidget是RenderObjectWidgets的一個子類,用于限制只能有一個子節(jié)點。它只提供child的存儲,而不提供實際的更新邏輯。
1.3.2 關(guān)于RenderObjectWidgets
RenderObjectWidgets為RenderObjectElement提供配置,而RenderObjectElement包含著(wrap)RenderObject,RenderObject則是在應(yīng)用中提供實際的繪制(rendering)的元素。
1.4 示例代碼
實例代碼直接上官方的例子,非常的簡單:
new Padding(
padding: new EdgeInsets.all(8.0),
child: const Card(child: const Text('Hello World!')),
)
例子中對Card設(shè)置了一個寬度為8的內(nèi)邊距。
1.5 源碼解析
構(gòu)造函數(shù)如下:
const Padding({
Key key,
@required this.padding,
Widget child,
})
包含一個padding屬性,相當?shù)暮唵巍?/p>
1.5.1 屬性解析
padding:padding的類型為EdgeInsetsGeometry
,EdgeInsetsGeometry是EdgeInsets以及EdgeInsetsDirectional的基類。在實際使用中不涉及到國際化,例如適配阿拉伯地區(qū)等,一般都是使用EdgeInsets。EdgeInsetsDirectional光看命名就知道跟方向相關(guān),因此它的四個邊距不限定上下左右,而是根據(jù)方向來定的。
1.5.2 源碼
@override
RenderPadding createRenderObject(BuildContext context) {
return new RenderPadding(
padding: padding,
textDirection: Directionality.of(context),
);
}
Padding的創(chuàng)建函數(shù),實際上是由RenderPadding
來進行的。
關(guān)于RenderPadding的實際布局表現(xiàn),當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的區(qū)域。
當child不為null的時候,經(jīng)歷了三個過程,即調(diào)整child尺寸、調(diào)整child位置以及調(diào)整Padding尺寸,最終達到實際的布局效果。
// 調(diào)整child尺寸
final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
child.layout(innerConstraints, parentUsesSize: true);
// 調(diào)整child位置
final BoxParentData childParentData = child.parentData;
childParentData.offset = new Offset(_resolvedPadding.left, _resolvedPadding.top);
// 調(diào)整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能夠?qū)崿F(xiàn)的,Container都能夠?qū)崿F(xiàn),只不過,Container更加的復(fù)雜。
2. Align
A widget that aligns its child within itself and optionally sizes itself based on the child's size.
2.1 簡介
在其他端的開發(fā),Align一般都是當做一個控件的屬性,并沒有拿出來當做一個單獨的控件。Align本身實現(xiàn)的功能并不復(fù)雜,設(shè)置child的對齊方式,例如居中、居左居右等,并根據(jù)child尺寸調(diào)節(jié)自身尺寸。
2.2 布局行為
Align的布局行為分為兩種情況:
- 當widthFactor和heightFactor為null的時候,當其有限制條件的時候,Align會根據(jù)限制條件盡量的擴展自己的尺寸,當沒有限制條件的時候,會調(diào)整到child的尺寸;
- 當widthFactor或者heightFactor不為null的時候,Aligin會根據(jù)factor屬性,擴展自己的尺寸,例如設(shè)置widthFactor為2.0的時候,那么,Align的寬度將會是child的兩倍。
Align為什么會有這樣的布局行為呢?原因很簡單,設(shè)置對齊方式的話,如果外層元素尺寸不確定的話,內(nèi)部的對齊就無法確定。因此,會有寬高因子、根據(jù)外層限制擴大到最大尺寸、外層不確定時調(diào)整到child尺寸這些行為。
2.3 繼承關(guān)系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Align
可以看出,Align跟Padding一樣,也是一個非常基礎(chǔ)的組件,Container中的align屬性,也是使用Align去實現(xiàn)的。
2.4 示例代碼
new Align(
alignment: Alignment.center,
widthFactor: 2.0,
heightFactor: 2.0,
child: new Text("Align"),
)
例子依舊很簡單,設(shè)置一個寬高為child兩倍區(qū)域的Align,其child處在正中間。
2.5 源碼解析
const Align({
Key key,
this.alignment: Alignment.center,
this.widthFactor,
this.heightFactor,
Widget child
})
Align的構(gòu)造函數(shù)基本上就是寬高因子、對齊方式屬性。日常使用中,寬高因子屬性基本上用的不多。如果是復(fù)雜的布局,Container內(nèi)部的align屬性也可以實現(xiàn)相同的效果。
2.5.1 屬性解析
alignment:對齊方式,一般會使用系統(tǒng)默認提供的9種方式,但是并不是說只有這9種,例如如下的定義。系統(tǒng)提供的9種方式只是預(yù)先定義好的。
/// The top left corner.
static const Alignment topLeft = const Alignment(-1.0, -1.0);
Alignment實際上是包含了兩個屬性的,其中第一個參數(shù),-1.0是左邊對齊,1.0是右邊對齊,第二個參數(shù),-1.0是頂部對齊,1.0是底部對齊。根據(jù)這個規(guī)則,我們也可以自定義我們需要的對齊方式,例如
/// 居右高于底部1/4處.
static const Alignment rightHalfBottom = alignment: const Alignment(1.0, 0.5),
widthFactor:寬度因子,如果設(shè)置的話,Align的寬度就是child的寬度乘以這個值,不能為負數(shù)。
heightFactor:高度因子,如果設(shè)置的話,Align的高度就是child的高度乘以這個值,不能為負數(shù)。
2.5.2 源碼
@override
RenderPositionedBox createRenderObject(BuildContext context) {
return new RenderPositionedBox(
alignment: alignment,
widthFactor: widthFactor,
heightFactor: heightFactor,
textDirection: Directionality.of(context),
);
}
Align的實際構(gòu)造調(diào)用的是RenderPositionedBox
。
RenderPositionedBox的布局表現(xiàn)如下:
// 根據(jù)_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,則根據(jù)規(guī)則設(shè)置Align的寬高,如果需要縮放,則根據(jù)_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,如果需要縮放,則變?yōu)?,否則就盡量擴展
size = constraints.constrain(new Size(shrinkWrapWidth ? 0.0 : double.infinity,
shrinkWrapHeight ? 0.0 : double.infinity));
}
2.6 使用場景
一般在對齊場景下使用,例如需要右對齊或者底部對齊之類的。它能夠?qū)崿F(xiàn)的功能,Container都能實現(xiàn)。
3. Center
Center繼承自Align,只不過是將alignment設(shè)置為Alignment.center,其他屬性例如widthFactor、heightFactor,布局行為,都與Align完全一樣,在這里就不再單獨做介紹了。Center源碼如下,沒有設(shè)置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學(xué)習(xí)相關(guān)的項目,github地址,里面包含了筆者寫的關(guān)于flutter學(xué)習(xí)相關(guān)的一些文章,會定期更新,也會上傳一些學(xué)習(xí)demo,歡迎大家關(guān)注。