书接上文
控件树中的每个控件通过实现RenderObjectWidget.createRenderObject(BuildContext context)
→ RenderObject
方法来创建对应的不同类型的RenderObject
对象,组成渲染对象树。因为Flutter极大地简化了布局的逻辑,所以整个布局过程中只需要深度遍历一次:
渲染对象树中的每个对象都会在布局过程中接受父对象的Constraints参数,决定自己的大小,然后父对象就可以按照自己的逻辑决定各个子对象的位置,完成布局过程。
子对象不存储自己在容器中的位置,所以在它的位置发生改变时并不需要重新布局或者绘制。子对象的位置信息存储在它自己的parentData字段中,但是该字段由它的父对象负责维护,自身并不关心该字段的内容。同时也因为这种简单的布局逻辑,Flutter可以在某些节点设置布局边界(Relayout boundary),即当边界内的任何对象发生重新布局时,不会影响边界外的对象,反之亦然.
布局完成后,渲染对象树中的每个节点都有了明确的尺寸和位置,Flutter会把所有对象绘制到不同的图层上:
因为绘制节点时也是深度遍历,可以看到第二个节点在绘制它的背景和前景不得不绘制在不同的图层上,因为第四个节点切换了图层(因为“4”节点是一个需要独占一个图层的内容,比如视频),而第六个节点也一起绘制到了红色图层。这样会导致第二个节点的前景(也就是“5”)部分需要重绘时,和它在逻辑上毫不相干但是处于同一图层的第六个节点也必须重绘。为了避免这种情况,Flutter提供了另外一个“重绘边界”的概念:在进入和走出重绘边界时,Flutter会强制切换新的图层,这样就可以避免边界内外的互相影响。典型的应用场景就是ScrollView,当滚动内容重绘时,一般情况下其他内容是不需要重绘的。虽然重绘边界可以在任何节点手动设置,但是一般不需要我们来实现,Flutter提供的控件默认会在需要设置的地方自动设置。
控件库(Widgets)
Flutter的控件库提供了非常丰富的控件,包括最基本的文本、图片、容器、输入框和动画等等。在Flutter中“一切皆是控件”,通过组合、嵌套不同类型的控件,就可以构建出任意功能、任意复杂度的界面。它包含的最主要的几个类有:
// WidgetsFlutterBinding 是Flutter的控件框架和Flutter引擎的胶水层, 基于Flutter控件系统开发的程序都需要使用
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding,
PaintingBinding, RendererBinding, WidgetsBinding { ... }
// Widget就是所有控件的基类,它本身所有的属性都是只读的。
abstract class Widget extends DiagnosticableTree { ... }
// StatelessWidget和StatefulWidget并不会直接影响RenderObject的创建,它们只负责创建对应的RenderObjectWidget
// StatelessElement和StatefulElement也是类似的功能。
abstract class StatelessWidget extends Widget { ... }
abstract class StatefulWidget extends Widget { ... }
// RenderObjectWidget所有的实现类则负责提供配置信息并创建具体的RenderObjectElement。
abstract class RenderObjectWidget extends Widget { ... }
// Element是Flutter用来分离控件树和真正的渲染对象的中间层,控件用来描述对应的element属性,控件重建后可能会复用同一个element。
abstract class Element extends DiagnosticableTree implements BuildContext { ... }
class StatelessElement extends ComponentElement { ... }
class StatefulElement extends ComponentElement { ... }
// RenderObjectElement持有真正负责布局、绘制和碰撞测试(hit test)的RenderObject对象。
abstract class RenderObjectElement extends Element { ... }
它们之间的关系如下图:
- 如果控件的属性发生了变化(因为控件的属性是只读的,所以变化也就意味着重新创建了新的控件树),但是其树上每个节点的类型没有变化时,element树和render树可以完全重用原来的对象(因为element和render object的属性都是可变的)
- 如果控件树中的某个节点的类型发生了变化,则 element树和 render树中对应的节点也需要重新创建