Flutter中的RenderObjectElement与RenderObjectWidget

avatar
奇舞团移动端团队 @奇舞团

级别: ★☆☆☆☆
标签:「Flutter」「RenderObjectElement 」「RenderObjectWidget」
作者: 沐灵洛
审校: QiShare团队


RenderObjectWidgetRenderObjectElement提供配置信息。 RenderObjectElement包装了RenderObjectRenderObject为应用程序提供真正的渲染。

RenderObjectWidget是什么?

RenderObjectWidgetRenderObjectElement的配置信息。 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作为其配置的ElementRenderObjectElement对象关联了一个处于渲染树中的RenderObject组件;RenderObject 主要处理一些固定的操作,如:布局、绘制和 Hit testing。 与ComponentElement一样RenderObjectElement也是抽象类,不同的是ComponentElement不会直接创建RenderObject,而是间接通过创建其他Element创建RenderObject

RenderObjectElement主要有三个系统的子类,分别处理renderObject作为child时的不同情况。

  • LeafRenderObjectElement:叶子渲染对象对应的元素,处理没有childrenrenderObject
  • SingleChildRenderObjectElement:处理只有单个childrenderObject
  • MultiChildRenderObjectElement: 处理有多个children的渲染对象

有时RenderObjectchild模型更复杂一些,比如多维数组的形式,则可能需要基于RenderObjectElement实现一个新的子类。

RenderObjectElement对象花费大量时间充当widgetrenderObject之间的中介。为使此更易于处理,大多数RenderObjectElement子类都覆盖了这些getter ,以便它们返回元素期望的特定类型,例如:

class FooElement extends RenderObjectElement {                                       
                                                                                    
 @override                                                                          
 Foo get widget => super.widget; 
                                                   
@override                                                                          
RenderFoo get renderObject => super.renderObject;                                  
                                                                          
}                                                                                    

系统常用组件与RenderObjectElement:

常用组件Widget(父级)Element
Flex/Wrap/Flow/StackMultiChildRenderObjectWidgetMultiChildRenderObjectElement
RawImage(Imaget)/ErrorWidgetLeafRenderObjectWidgetLeafRenderObjectElement
Offstage/SizedBox/Align/PaddingSingleChildRenderObjectWidgetSingleChildRenderObjectElement

RenderObjectElement中的插槽(Slots)

如果一个「element」有多个子element, 每个子element都对应一个子renderObject ,该子renderObject应该作为此「element」renderObjectchild被添加到树中。

然而,此element的最直接(immediate)的children,在生成对应的renderObjects时, 可能最终用于生成实际renderObjectschildren已经不是最初的最直接的children(简言之,子element在生成最终对应的renderObject时,可能已经发生了改变)。

比如: StatelessElement (StatelessWidgetelement) 仅与其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,与新的Widgetslot共同这为RenderObjectElement对象提供新的Element对象的列表。

在可能的情况下,update方法会尝试将elements关联的旧widgets与新的widgets匹配。例如,旧widgets之一和新widgets之一有相同的keyruntimeType,它们应该是匹配的。并且旧的element应该使用新的widget进行更新(并且slot也会更新到新widget的新位置上)。children调用updateChild应该按照它们的逻辑顺序。

  • build阶段动态确定children
    childwidget可能来自回调,而非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时,有可能在更新此元素之前由另一个元素主动删除该孩子。(特别是,以有GlobalKeywidget为根对应的element移动到在build阶段处理的更早的元素时)发生这种情况时,该elementforgetChild方法将使用引用受影响的子元素。RenderObjectElement子类的forgetChild方法必须从child列表中删除该child元素,以便在下次updatechild时,不考虑已删除的child。出于性能原因,如果有很多元素,则可以通过将它们存储在Set中而不是主动更改child列表的本地记录和所有插槽的标识来更快地跟踪哪些元素被遗忘了具体的可以参考MultiChildRenderObjectElement的实现。

保持渲染树

一旦后代生成渲染对象,它将调用insertChildRenderObject。如果后代的slot更改了identity,将调用moveChildRenderObject。如果后代消失了,它将调用removeChildRenderObject。 这三个方法应相应地更新渲染树,并分别将给定的子渲染对象与该element自己的渲染对象相连接(attaching),移动和分离(detaching )。

了解更多iOS及相关新技术,请关注我们的公众号:

image

可添加如下微信,并备注加入QiShare技术交流群,邀请你加入《QiShare技术交流群》。 微信

关注我们的途径有:
QiShare(简书)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公众号)

Flutter中的StatelessWidget及其生命周期
Flutter中的Widget
Flutter中的Element(下篇)
Flutter中的Element(上篇)
iOS 解决 [NSURL fileURLWithPath:] 对 # 的编码问题
Xcode 调整导航目录字体大小b
Swift 5.1 (21) - 泛型
Swift 5.1 (20) - 协议
Swift 5.1 (19) - 扩展
Swift 5.1 (18) - 嵌套类型
浅谈编译过程
奇舞团安卓团队——aTaller
奇舞周刊