Flutter學習:什麼是Container以及佈局約束的實現

落魄程式設計師線上炒飯發表於2021-06-01

前言

在Flutter開發中,Container作為一個容器類的Widget,有點類似HTML的div,在專案中也是高頻使用;那在使用過程中,你是否有過下面的這些疑問:

  1. Container是什麼,它如何實現的?
  2. Container的大小約束規則是什麼?下面幾種情況你知道為什麼嗎?

截圖2021-05-31 下午8.01.09.png

Container介紹

首先我們來看一下Contaienr是什麼: 截圖2021-05-27 下午5.04.06.png 可以看到,通過上面可以知道,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"。關於大小約束在後面再具體聊。

截圖2021-05-31 下午8.26.20.png

大小和約束的流程

在學習大小約束之前,我們需要知道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的呼叫:

截圖2021-06-01 下午3.45.55.png

截圖2021-06-01 下午3.51.39.png 好了,我們來分析一下:layout函式的入參是一個約束,可以從外部獲取約束,然後再呼叫performResizeperformLayout,這裡官方要求子類不能重寫layout,而是直接重寫performResizeperformLayout;主要的作用是:把自己的約束傳給子類;從子類獲取大小;繼續繪製子類,最後通過類的paint函式進行繪製。整個過程遞迴,就完成了各個renderObject的大小和約束的計算並繪製。

約束規則

偷個懶直接看Flutter官網的介紹

截圖2021-06-01 下午6.21.00.png

相關文章