前言
在Flutter開發中,Container作為一個容器類的Widget,有點類似HTML的div,在專案中也是高頻使用;那在使用過程中,你是否有過下面的這些疑問:
- Container是什麼,它如何實現的?
- Container的大小約束規則是什麼?下面幾種情況你知道為什麼嗎?
Container介紹
首先我們來看一下Contaienr是什麼:
可以看到,通過上面可以知道,Container是繼承自StatelessWidget的widget,有豐富的屬性配置,通過這些屬性,我們可以設定前景,背景,顏色,內外邊距,裁剪方式,對齊方式等。而且由於Container是繼承自StatelessWidget,所以它並不能構建renderObject,不能直接參與繪製,所以看一下它的build()
函式的實現:
@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 (alignment != null)
current = Align(alignment: alignment, child: current);
final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
if (color != null)
current = ColoredBox(color: color, child: current);
if (clipBehavior != Clip.none) {
assert(decoration != null);
current = ClipPath(
clipper: _DecorationClipper(
textDirection: Directionality.of(context),
decoration: decoration
),
clipBehavior: clipBehavior,
child: current,
);
}
if (decoration != null)
current = DecoratedBox(decoration: decoration, child: current);
if (foregroundDecoration != null) {
current = DecoratedBox(
decoration: foregroundDecoration,
position: DecorationPosition.foreground,
child: current,
);
}
if (constraints != null)
current = ConstrainedBox(constraints: constraints, child: current);
if (margin != null)
current = Padding(padding: margin, child: current);
if (transform != null)
current = Transform(transform: transform, child: current);
return current;
}
複製程式碼
通過build()函式可以知道,Container只是對不同的renderObjectWidget的組合封裝。這簡化了我們佈局時的widget巢狀層數;其次,注意這裡各種widgets的組合的先後順序,flutter的“盒模型”沒有內外邊距的概念,邊距的實現是通過巢狀一個“盒子”實現的,我們能看到邊距的效果,就是因為這裡的順序,是先巢狀了邊距widget,這個widget的大小是child的大小加上我們設定的"padding"。關於大小約束在後面再具體聊。
大小和約束的流程
在學習大小約束之前,我們需要知道renderObject的佈局計算流程。我們定位到RenderObject類中,定位到函式:
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
void layout(Constraints constraints, { bool parentUsesSize = false }) {
if (!kReleaseMode && debugProfileLayoutsEnabled)
Timeline.startSync('$runtimeType', arguments: timelineArgumentsIndicatingLandmarkEvent);
...
RenderObject? relayoutBoundary;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
}
...
if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
...
if (!kReleaseMode && debugProfileLayoutsEnabled)
Timeline.finishSync();
return;
}
_constraints = constraints;
...
if (sizedByParent) {
...
try {
performResize();
...
} catch (e, stack) {
...
}
...
}
...
try {
performLayout();
...
} catch (e, stack) {
...
}
...
_needsLayout = false;
markNeedsPaint();
if (!kReleaseMode && debugProfileLayoutsEnabled)
Timeline.finishSync();
}
@protected
void performResize();
@protected
void performLayout();
void paint(PaintingContext context, Offset offset) { }
}
複製程式碼
找一個layout
的呼叫:
好了,我們來分析一下:layout
函式的入參是一個約束,可以從外部獲取約束,然後再呼叫performResize
和performLayout
,這裡官方要求子類不能重寫layout
,而是直接重寫performResize
或performLayout
;主要的作用是:把自己的約束傳給子類;從子類獲取大小;繼續繪製子類,最後通過類的paint
函式進行繪製。整個過程遞迴,就完成了各個renderObject的大小和約束的計算並繪製。
約束規則
偷個懶直接看Flutter官網的介紹: