Render Tree
What are the render object building blocks?
RenderObjectprovides the basic infrastructure for modeling hierarchical visual elements in the UI and includes a generalized protocol for performing layout, painting, and compositing. This protocol is unopinionated, deferring to subclasses to determine the inputs and outputs of layout, how hit testing is performed (though all render objects areHitTestTargets), and even how the render object hierarchy is modeled.Constraints represent the immutable inputs to layout. They must indicate whether they offer no choice (
Constraints.isTight), are the canonical representation (Constraints.isNormalized), and provide “==” andhashCodeimplementations.ParentDatais opaque data stored in a childRenderObjectby its parent. The only requirement is thatParentDatainstances implement aParentData.detachmethod to react to their render object being removed from the tree.
How is the render object lifecycle managed?
The
RendererBindingestablishes aPipelineOwneras part ofRendererBinding.initInstances(i.e., the binding’s “constructor”). ThePipelineOwneris analogous to theBuildOwner, tracking which render objects need compositing bits, layout, painting, and semantics updates. Objects mark themselves “dirty” by adding themselves to lists, e.g.,PipelineOwner._nodesNeedingLayout,PipeplineOwner._nodesNeedingPaint, etc. All dirty objects are later cleaned by the corresponding “flush” method:PipelineOwner.flushLayout,PipelineOwner.flushPaint, etc. These methods form the majority of the rendering pipeline and are invoked every frame viaRendererBinding.drawFrame.Whenever an object marks itself dirty, it will generally also invoke
PipelineOwner.requestVisualUpdateto schedule a frame and update the UI. This calls thePipelineOwner.onNeedVisualUpdatehandler which schedules a frame viaRendererBinding.ensureVisualUpdateandSchedulerBinding.scheduleFrame. The rendering pipeline (as facilitated byRendererBinding.drawFrameorWidgetsBinding.drawFrame) will invoke thePipelineOwner’s various flush methods to drive each stage.When the render object is attached to its
PipelineOwner, it will mark the render object as dirty in all necessary dimensions (e.g.,RenderObject.markNeedsLayout,RenderObject.markNeedsPaint). Generally, all are invoked since the internal flags that are consulted are initialized to true (e.g.,RenderObject._needsLayout,RenderObject._needsPaint).
How is the render tree structured?
The render tree consists of
AbstractNodeinstances (RenderObjectis a subclass). When a child is added or removed,RenderObject.adoptChildandRenderObject.dropChildmust be called, respectively. When altering the depth of a child,RenderObject.redepthChildmust be called to keepRenderObject.depthin sync.Every node also needs to use
RenderObject.attachandRenderObject.detachto set and clear the pipeline owner (RenderObject.owner), respectively. EveryRenderObjecthas a parent (RenderObject.parent) and an attached state (RenderObject.attached).Parent data is a
ParentDatasubclass optionally associated with a render object. By convention, the child is not to access this data, though a protocol is free to alter this rule (but shouldn’t). When adding a child, the parent data is initialized by callingRenderObject.setupParentData. The parent data may then be mutated in, e.g.,RenderObject.performLayout.All
RenderObjectsimplement the visitor pattern viaRenderObject.RenderObject.visitChildrenwhich invokes aRenderObjectVisitoronce per child.
What is necessary when altering the child model?
When adding or removing a child, call
RenderObject.adoptChildandRenderObject.dropChildrespectively.The parent’s
RenderObject.attachandRenderObject.detachmethods must call their counterpart on each child.The parent’s
RenderObject.redepthChildrenandRenderObject.visitChildrenmethods must recursively callRenderObject.redepthChildand the visitor argument on each child, respectively.
What are the render tree building blocks?
RenderObjectWithChildMixinallocates storage for a single child within the object instance itself (RenderObjectWithChildMixin.child).ContainerRenderObjectMixinuses each child’s parent data (which must implementContainerParentDataMixin) to build a doubly linked list viaContainerParentDataMixin.nextSiblingandContainerParentDataMixin.previousSibling.A variety of container-type methods are included:
ContainerRenderObjectMixin.insert,ContainerRenderObjectMixin.add,ContainerRenderObjectMixin.move,ContainerRenderObjectMixin.remove, etc.These adopt, drop, attach, and detach children appropriately. Additionally, when the child list changes,
RenderObject.markNeedsLayoutis invoked (as this may alter layout).
How do render objects manage layout?
When a render object has been marked as dirty via
RenderObject.markNeedsLayout,RenderObject.layoutwill be invoked with constraints as input and a size as output. Both the constraints and the size are implementation-dependent; the constraints must implementConstraintswhereas the size is entirely arbitrary.If a parent depends on the child’s geometry, it must pass the
parentUsesSizeargument to layout. The implementation ofRenderObject.markNeedsLayout/RenderObject.sizedByParentwill need to callRenderObject.markParentNeedsLayout/RenderObject.markParentNeedsLayoutForSizedByParentChange, respectively.RenderObjectsthat solely determine their sizing using the input constraints must setRenderObject.sizedByParentto true and perform all layout inRenderObject.performResize.Layout cannot depend on position (typically stored using parent data). The position, if applicable, is solely determined by the parent. However, some
RenderObjectsubtypes may utilize additional out-of-band information when performing layout. If this information changes, and the parent used it during the last cycle,RenderObject.markParentNeedsLayoutmust be invoked.
How do render objects manage hit testing?
RenderView’s child is aRenderBox, which must therefore defineRenderBox.hitTest. To add a customRenderObjectto the render tree, the top levelRenderViewmust be replaced (which would be a massive undertaking) or aRenderBoxadapter added to the tree. It is up to the implementer to determine howRenderBox.hitTestis adapted to a customRenderObjectsubclass; indeed, that render object can implement any manner ofhitTest-like method of its choosing. Note that allRenderObjectsareHitTestTargetsand therefore will receive pointer events viaHitTestTarget.pointerEventonce registered viaHitTestEntry.
How are render objects composited into layers?
Render objects track a “
needsCompositing” bit (as well as an “alwaysNeedsCompositing” bit and a “isRepaintBoundary” bit, which are consulted when updating “needsCompositing”). This bit indicates to the framework that a render object will paint into its own layer.This bit is not set directly. Instead, it must be marked dirty whenever it might possibly change (e.g., when adding or removing a child).
RenderObject.markNeedsCompositingBitsUpdatewalks up the render tree, marking objects as dirty until it reaches a previously dirtied render object or a repaint boundary.Later, just before painting,
PipelineOwner.flushCompositingBitsvisits all dirty render objects, updating the “needsCompositing” bit by walking down the tree, looking for any descendent that needs compositing. This walk stops upon reaching a repaint boundary or an object that always needs compositing. If any descendent node needs compositing, all nodes along the walk need compositing, too.It is important to create small “repaint sandwiches” to avoid introducing too many layers. [?]
Composited render objects are associated with an optional
ContainerLayer. For render objects that are repaint boundaries,RenderObject.layerwill beOffsetLayer. In either case, by retaining a reference to a previously used layer,Flutteris able to better utilize retained rendering -- i.e., recycle or selectively update previously rasterized bitmaps. This is possible becauseLayerspreserve a reference to any underlyingEngineLayer, andSceneBuilderaccepts an “oldLayer” argument when building a new scene.
How do render objects handle transformations?
Visual transforms are implemented in
RenderObject.paint. The same transform is applied logically (i.e., when hit testing, computing semantics, mapping coordinates, etc) viaRenderObject.applyPaintTransform. This method applies the child transformation to the provided matrix.RenderObject.transformTochains paint transformation matrices mapping from the current coordinate space to the provided ancestor’s coordinate space. [WIP]
What other protocols are implemented in the framework?
RenderBox,RenderSliver.
Last updated
Was this helpful?