RenderObjectWidget
为RenderObjectElement
提供配置信息。
RenderObjectElement
包装了RenderObject
,RenderObject
为应用程序提供真正的渲染。
RenderObjectWidget是什么?
RenderObjectWidget
是RenderObjectElement
的配置信息。
RenderObjectWidget
是个抽象类。
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key key }) : super(key: key);
/// RenderObjectWidgets always inflate to a [RenderObjectElement] subclass.
@override
RenderObjectElement createElement();
/// 使用`RenderObjectWidget`信息,
///创建一个`RenderObjectWidget`表示的`RenderObject`实例。
///创建时机:
///`[RenderObjectElement.mount]`方法中使用`RenderObjectElement`创建。
///挂载时,调用关联的此`widget`创建其对应的`RenderObject`
@protected
RenderObject createRenderObject(BuildContext context);
/// 复制此[RenderObjectWidget]描述的配置到给定的[RenderObject],
///此`RenderObject`类型将与此`RenderObjectWidget`的
///[createRenderObject]返回的`RenderObject`类型相同。
/// 调用时机:[RenderObjectElement.update]
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
///此`widget`前一个关联的`RenderObject`已经从树中移除。
///此处的`RenderObject`是其的一个副本。
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
RenderObjectElement是什么?
使用RenderObjectWidget
作为其配置的Element
。 RenderObjectElement
对象关联了一个处于渲染树中的RenderObject
组件;RenderObject
主要处理一些固定的操作,如:布局、绘制和 Hit testing
。 与ComponentElement
一样RenderObjectElement
也是抽象类,不同的是ComponentElement
不会直接创建RenderObject
,而是间接通过创建其他Element
创建RenderObject
。
RenderObjectElement
主要有三个系统的子类,分别处理renderObject
作为child
时的不同情况。
-
LeafRenderObjectElement
:叶子渲染对象对应的元素,处理没有children
的renderObject
。 -
SingleChildRenderObjectElement
:处理只有单个child
的renderObject
。 -
MultiChildRenderObjectElement
: 处理有多个children
的渲染对象
有时RenderObject
的child
模型更复杂一些,比如多维数组的形式,则可能需要基于RenderObjectElement
实现一个新的子类。
RenderObjectElement
对象花费大量时间充当widget
与renderObject
之间的中介。为使此更易于处理,大多数RenderObjectElement
子类都覆盖了这些getter
,以便它们返回元素期望的特定类型,例如:
class FooElement extends RenderObjectElement {
@override
Foo get widget => super.widget;
@override
RenderFoo get renderObject => super.renderObject;
}
系统常用组件与RenderObjectElement:
常用组件 | Widget(父级) | Element |
---|---|---|
Flex/Wrap/Flow/Stack | MultiChildRenderObjectWidget | MultiChildRenderObjectElement |
RawImage(Imaget)/ErrorWidget | LeafRenderObjectWidget | LeafRenderObjectElement |
Offstage/SizedBox/Align/Padding | SingleChildRenderObjectWidget | SingleChildRenderObjectElement |
RenderObjectElement
中的插槽(Slots)
如果一个「element」
有多个子element
, 每个子element
都对应一个子renderObject
,该子renderObject
应该作为此「element」
的renderObject
的child
被添加到树中。
然而,此element
的最直接(immediate
)的children
,在生成对应的renderObjects
时, 可能最终用于生成实际renderObjects
的children
已经不是最初的最直接的children
(简言之,子element
在生成最终对应的renderObject
时,可能已经发生了改变)。
比如: StatelessElement
(StatelessWidget
的element
) 仅与其child
(由StatelessWidget.build
返回的element
)对应的任何RenderObject
对应。
因此,为每个child
分配了一个_slot_
令牌(token)。这是一个对RenderObjectElement
节点私有的标识符。当最终生成RenderObject
的后代时,并准备好将其附加到该节点的渲染对象时,它将该_slot_
令牌(token)传递回该节点,并允许该节点快速地标记出应当将子渲染对象相对于其他对象放置在父渲染对象的何处。
更新children
element
生命的早期,framework
调用mount
方法,这个方法会为每个child
调用updateChild
,传入此child
,传入新的widget
,以及此child
的新的slot
,从而获得子element
的列表。
Element updateChild(Element child, Widget newWidget, dynamic newSlot)
随后,framework
将调用update
方法,此方法最终会通过rebuild
方法使得RenderObjectElement
为每个child
调用再次调用updateChild
,并传入在mount
或上一次运行update
时(以最近发生的为准)获得的Element
,与新的Widget
和slot
共同这为RenderObjectElement
对象提供新的Element
对象的列表。
在可能的情况下,update
方法会尝试将elements
关联的旧widgets
与新的widgets
匹配。例如,旧widgets
之一和新widgets
之一有相同的key
和runtimeType
,它们应该是匹配的。并且旧的element
应该使用新的widget
进行更新(并且slot
也会更新到新widget
的新位置上)。children
调用updateChild
应该按照它们的逻辑顺序。
-
在
build
阶段动态确定children
child
的widget
可能来自回调,而非element
关联的widget
。 -
在
layout
阶段动态确定children
如果要在布局时生成widget
,则需在update
方法不起作用时生成它们:element
的渲染对象的布局此时尚未开始。相反,update
方法可以将渲染对象标记为需要的布局(RenderObject.markNeedsLayout
),然后渲染对象的RenderObject.performLayout
方法可以回调到element
让它生成的widget
相应地调用updateChild
。
为了使渲染对象在布局期间调用element
,它必须使用RenderObject.invokeLayoutCallback
。要使element
在其update
方法之外调用updateChild
,它必须使用BuildOwner.buildScope
。与在布局期间进行构建时相比,framework
在正常操作中提供的检查要多得多。因此,使用layout-time build
语义创建widget
时应格外小心。 -
building
时处理错误
如果element
调用builder
函数为其子代获取widget
,则可能会发生该builder
内引发了异常。应该使用FlutterError.reportError
捕获并报告此类异常。如果需要child
,但是builder
以这种方式失败了,则可以使用ErrorWidget
的实例代替。
Detaching children
使用GlobalKey
时,有可能在更新此元素之前由另一个元素主动删除该孩子。(特别是,以有GlobalKey
的widget
为根对应的element
移动到在build
阶段处理的更早的元素时)发生这种情况时,该element
的forgetChild
方法将使用引用受影响的子元素。RenderObjectElement
子类的forgetChild
方法必须从child
列表中删除该child
元素,以便在下次update
其child
时,不考虑已删除的child
。出于性能原因,如果有很多元素,则可以通过将它们存储在Set
中而不是主动更改child
列表的本地记录和所有插槽的标识来更快地跟踪哪些元素被遗忘了具体的可以参考MultiChildRenderObjectElement
的实现。
保持渲染树
一旦后代生成渲染对象,它将调用insertChildRenderObject
。如果后代的slot
更改了identity
,将调用moveChildRenderObject
。如果后代消失了,它将调用removeChildRenderObject
。
这三个方法应相应地更新渲染树,并分别将给定的子渲染对象与该element
自己的渲染对象相连接(attaching
),移动和分离(detaching
)。
网友评论