下面的讨论只限于framework.dart包内的核心类型。

Flutter的布局过程

理解Flutter的布局,必须要先看明白布局过程中不同的树之间的关系。

Widget的直接子类有四个,但也可以分为两类:一类是用于帮助构建Widget间关系的Widget,包括StatelessWidget、StatefulWidget、ProxyWidget,另一类是用于生成RenderObject的:RenderObjectWidget。

Element的直接子类只有两个,分别是ComponentElement和RenderObjectElement;

RenderObjectElement和RenderObjectWidget配合使用,而其他Widget则是配合ComponentElement使用。

在Flutter中,虽然Widget树和Element树的节点总是一一对应的,但到了RenderObject树,节点会显著的少于Element树或Widget树,这是因为在构建RenderObject树时,只有RenderObjectWidget,或者说只有RenderObjectElement才会真正的对应一个RenderObject节点,这个生成RenderObject的方法被定义在RenderObjectWidget上,当从RenderObjectElement生成节点时,实际上是代理到了Widget方法上的。
至于ComponentElement,它定义了build方法,用于根据Widget配置来构建一个新的Widget,并继续基于新的Widget对应的Element建立Element树。

RenderObject即负责布局(layout),也负责绘制(paint),但并不是所有RenderObject都会同时实现这两个功能,这一点和Android布局类似。flutter中负责布局的是两类RenderObject:RenderBox和RenderSilver,其中大部分普通的Widget最后都是通过RenderBox的子类完成的布局和绘制,RenderSilver是专用于带内容区域滚动逻辑的Widget的,据flutter开发者说,Silver非常、非常复杂。

一般布局逻辑

BoxConstraints

https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html
Flutter中的基类RenderObject类中定义了constraints属性,这个属性是由父级RenderObject在测量这个RenderObject时传入的,这个属性的类型是Constraints,也是一个基类。
不过,实际的flutter代码中,大部分的RenderObject都是RendedrBox,RenderBox重写了constraints getter,要求constraints必须是一个BoxConstraints类型,不然在尝试获取constraints时就会因为转型失败而异常。我们在使用Flutter时,需要关注BoxConstraints的用法和语义,以更高效的完成布局

约束语义

BoxConstraints定义了min/max width/height,当minWidth=maxWidth时,flutter就说约束在宽度方向上是tight的,当minWidth=0时,flutter就说约束在width方向上是loose的。特别的,当minWidth=maxWidth=0时,约束即是tight的又是loose的,文档没有明确规定这种case如何处理,因此通常别这么干。
前面的讨论里,当约束为loose时,并没有区分max是否有限,这里明确:当max有限时,就说约束是bounded的,对应的当max为无限时,就说约束是unbounded的,expand常用来代表unbounded语义。

一般来说,Flutter widget都必须满足上述语义。虽然Widget不会直接暴露BoxConstraints,但它们都会提供可设置的属性,来间接的让使用者配置最终使用的BoxConstraints。

例子

https://api.flutter.dev/flutter/widgets/Column-class.html
Column文档中的Layout algorithm比较充分的体现了上述约束是如何生效的。

ParentData

在使用Flex Widget时,虽然Flex的children类型是List,但文档却要求我们必须使用Flexible类型的Widget,这是因为Flexible能够和Flex通过FlexParentData协作,共同完成Flex布局协议。这个ParentData是在Flex的RenderObject(RenderFlex)上挂载新的RenderObject时由父RenderObject设置的(是的,parentData是RenderObject类上定义的属性)。在FlexParentData中,定义了宽高约束、fit、flex属性,其中宽高约束是在布局发生时,由RenderFlex为每个child RenderObject设置好的,而fit和flex则是Flexible Widget的属性,当child RenderObject被创建好之后通过一个applyParentData回调,由Widget设置到ParentData中的。这些属性是在布局时Parent与Child都需要使用的属性,并且设置也是由Parent和Child协作设置的。
如果我们不使用Flexible,那么Flex的children就无法指定fit和flex属性,Flex布局,以及children,都只能按照一种默认的方案来完成布局,Flex布局的能力就废了一大半。

Proxy

前面讨论的ParentData是通过ParentDataWidget、ParentDataElement、以及具体的RenderObject支持的,其中ParentDataWidget和ParentDataElement都是Proxy,即ProxyWidget和ProxyElement。
所谓Proxy,就是说它们仅仅是另一个对象的代理,它们会拦截、修饰另一个对象,但最终还是会利用另一个对象来完成工作。在flutter中,ProxyWidget和ProxyElement非常简单,ProxyWidget持有child widget,ProxyElement将所有工作转发给ProxyWidget持有的那个widget。
ParentDataWidget和ParentDataElement在上述简单的转发关系之上增加了一些逻辑,ParentDataWidget上定义了applyParentData方法,允许根据Widget属性设置ParentData,ParentDataElement则重写了notifyClient方法,当Element创建了RenderObject或者需要更新RenderObject时都会调用该方法,在该方法里让Widget为ParentData设置子Widget负责设置的属性。