Flutter学习:什么是Container以及布局约束的实现

1,353 阅读2分钟

前言

在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