下面的讨论只限于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,那么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负责设置的属性。