我们大致分析一下
首先是调用到 MultiChildRenderObjectElement 的 mount 方法中,先将传入的 parent 插入到树中,
接着在 RenderObjectElement 中 创建了一个 renderObject 并添加到渲染树中插槽的指定位置
最后回到 MultiChildRenderObjectElement 中遍历所有的 child,并且调用 inflateWidget 创建 child并插入到指定的插槽中。
StatefulElement
相比于 StatelessWidget ,StatefulWidget 中多了一个 State,在 StatefulWidget 中会通过 createState 来创建一个 State,如下所示
abstract class StatefulWidget extends Widget { const StatefulWidget({ Key? key }) : super(key: key); @override StatefulElement createElement() => StatefulElement(this); @protected @factory State createState(); // ignore: no_logic_in_create_state, this is the original sin }
通过上面代码可以看出 StatefulWidget 对应的 Element 正是 StatefulElement。
class StatefulElement extends ComponentElement { /// Creates an element that uses the given widget as its configuration. StatefulElement(StatefulWidget widget) : _state = widget.createState(), super(widget) { assert(state._element == null); state._element = this; state._widget = widget; } State<StatefulWidget> get state => _state!; State<StatefulWidget>? _state; @override Widget build() => state.build(this); }
在 StatefulElement 中通过调用 widget.createState 获取到了 state 对象
StatefulElement 在创建完成后,framework 就会调用 StatelessElement 中的 mount 方法了。
和 StatelessElement 不同的是:
Statelesselement 中是通过调用 widget.build(this) 方法
StatefulElement 中是通过调用 state.build(this) 方法
总结
通过上面的分析,我们知道了 Element 的生命周期以及它的调用过程。并且如果你仔细观察上面举例的三个 Element 就会发现,上面的这三种 Element 可以分为两类,分别是组合类和绘制类。
组合类一般都继承自 StatelessElement 或者 StatefulElement,它们都属于组合类,并不参与绘制,内部嵌套了多层 Widget,查看它们的 mount 方法,就会发现其中并没有创建 renderObject 并添加到渲染树。例如 StatelessWidget ,StatefulWidget,Text,Container ,Image 等。
绘制类的特征顾名思义就是参加绘制了,在mount 方法中,会有创建 renderObject 并 attachRenderObject 渲染树中。如 Column ,SizedBox,Transform 等。
其实还有一种类型,这里借用大佬的一张图来看一下:
代理类对应的 element 是 ProxyElement。
RenderObject
在上面我们提到过每一个 Element 都对应一个 RenderObject,我们可以通过 Element.renderObject 来获取。并且 RenderObject 的主要职责是 Layout 绘制,所有的 RenderObject 会组成一颗渲染树 Render Tree。
通过上面的分析,我们知道树的核心就在 mount 方法中,我们直接查看 RenderObjectElement.mount() 方法
@override void mount(Element? parent, Object? newSlot) { super.mount(parent, newSlot); _renderObject = widget.createRenderObject(this); attachRenderObject(newSlot); _dirty = false; }
在执行完 super.mount 之后 (将 parent 插入到 element 树中),执行了 attachRenderObject 方法。
@override void attachRenderObject(Object? newSlot) { assert(_ancestorRenderObjectElement == null); _slot = newSlot; //查询当前最近的 RenderObject 对象 _ancestorRenderObjectElement = _findAncestorRenderObjectElement(); // 将当前节点的 renderObject 对象插入到上面找的的 RenderObject下面 _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot); //......... } RenderObjectElement? _findAncestorRenderObjectElement() { Element? ancestor = _parent; while (ancestor != null && ancestor is! RenderObjectElement) ancestor = ancestor._parent; return ancestor as RenderObjectElement?; }
上面代码也不难理解,就是一个死循环,退出的添加就是 ancestor 父节点为空和父节点是 RenderObjectElement 。说明这个方法会找到离当前节点最近的一个 RenderObjectElement 对象,然后调用 insertRenderObjectChild 方法,这个方法是一个抽象方法,我们查看两个类里面的重写逻辑
SingleChildRenderObjectElement
void insertRenderObjectChild(RenderObject child, Object? slot) { final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>; renderObject.child = child; }
在上面代码中,找到了当前 RenderObjectElement 的 renderObject ,并且将我们传入的 child 给了 renderObject 的 child 。所以这样就将传入的 child 挂在了 RenderObject 树上。
MultiChildRenderObjectElement
@override void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) { final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject; assert(renderObject.debugValidateChild(child)); renderObject.insert(child, after: slot.value?.renderObject); assert(renderObject == this.renderObject); }
上面代码中,找到 renderObject 之后赋值给了 ContainerRenderObjectMixin> 这个类,我们来看一下这个类
/// Generic mixin for render objects with a list of children. /// 具有子项列表的渲染对象的通用混合 /// Provides a child model for a render object subclass that has a doubly-linked /// list of children. /// 为具有双向链接的子项列表的渲染对象子类提供子模型 mixin ContainerRenderObjectMixin<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>> on RenderObject { }
泛型 mixin 用于渲染一组子对象的对象。看到这里我们可以知道 MultiChildRenderObjectElement 是可以有子列表的。
通过上面的注释可以看出一个重点,双向链表。所以 MultiChildRenderObjectElement 的子节点通过双向链表连接。上面 insert 最终会调用到 _insertIntoChildList 方法中,如下:
ChildType? _firstChild; ChildType? _lastChild; void _insertIntoChildList(ChildType child, { ChildType? after }) { final ParentDataType childParentData = child.parentData! as ParentDataType; _childCount += 1; assert(_childCount > 0); if (after == null) { // after 为 null,则插入到 _firstChild 中 childParentData.nextSibling = _firstChild; if (_firstChild != null) { final ParentDataType _firstChildParentData = _firstChild!.parentData! as ParentDataType; _firstChildParentData.previousSibling = child; } _firstChild = child; _lastChild ??= child; } else { final ParentDataType afterParentData = after.parentData! as ParentDataType; if (afterParentData.nextSibling == null) { // insert at the end (_lastChild); we'll end up with two or more children // 将 child 插入到末尾 assert(after == _lastChild); childParentData.previousSibling = after; afterParentData.nextSibling = child; _lastChild = child; } else { // insert in the middle; we'll end up with three or more children // 插入到中间 childParentData.nextSibling = afterParentData.nextSibling; childParentData.previousSibling = after; // set up links from siblings to child final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling!.parentData! as ParentDataType; final ParentDataType childNextSiblingParentData = childParentData.nextSibling!.parentData! as ParentDataType; childPreviousSiblingParentData.nextSibling = child; childNextSiblingParentData.previousSibling = child; assert(afterParentData.nextSibling == child); } } }
根据上面的注释我们可以将其分为三部分,1,在 after 为 null 的时候将 child 插入到第一个节点,2,将 child 插入到 末端,3,将 child 插入到中间。
我们以一个例子为例:
Column( children: [ SizedBox(.....), Text(data), Text(data), Text(data), ], )
第一个 Stack 向上找到 Column( RenderObjectElement ) 之后, 调用这个方法,目前 after 为 null,则 _firstchild 就是 SizedBox( SizedBox 对应的 renderObject 是 RenderConstrainedBox)
第二个是 Text,我们知道 Text 是组合类型的,所以他不会被挂载到 树中,通过查询源码可以看出最终的 text 用的是 RichText。RichText 向上查找到 Column( RenderObjectElement ) 之后 ,调用这个方法,传入了两个参数,第一个 child 是 RichText 对应的 RenderParagraph ,第二个 after 是 SizedBox 对应的 RenderConstrainedBox 。依照上面逻辑,执行下面的代码
final ParentDataType afterParentData = after.parentData! as ParentDataType; if (afterParentData.nextSibling == null) { // insert at the end (_lastChild); we'll end up with two or more children assert(after == _lastChild); childParentData.previousSibling = after; afterParentData.nextSibling = child; _lastChild = child; }
将 child 的 childParentData.previousSibling 指向第一个节点,将第一个接的的 afterParentData.nextSibling 指向 child,最后让 _lastchild 执行 child。
后面也是如此,当流程结束后就可以得到一个 RenderTree。
总结
本文主要介绍了三棵树的构建过程以及 elemnt 的生命周期,这些虽然我们在开发过程中用的比较少,但是却是通向 flutter 内部世界的大门。
其实在写这篇文章的时候也是一知半解,通过不断的查看源码,翻看博客等才慢慢的有些了解。并且在最后产出了这篇文章,可能文章中会有一些错误,如果你看到的话麻烦在下面提出来,感谢!!