Render Tree
What are the render object building blocks?
RenderObject
provides 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 “==” andhashCode
implementations.ParentData
is opaque data stored in a childRenderObject
by its parent. The only requirement is thatParentData
instances implement aParentData.detach
method to react to their render object being removed from the tree.
How is the render object lifecycle managed?
The
RendererBinding
establishes aPipelineOwner
as part ofRendererBinding.initInstances
(i.e., the binding’s “constructor”). ThePipelineOwner
is 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.requestVisualUpdate
to schedule a frame and update the UI. This calls thePipelineOwner.onNeedVisualUpdate
handler which schedules a frame viaRendererBinding.ensureVisualUpdate
andSchedulerBinding.scheduleFrame
. The rendering pipeline (as facilitated byRendererBinding.drawFrame
orWidgetsBinding.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
AbstractNode
instances (RenderObject
is a subclass). When a child is added or removed,RenderObject.adoptChild
andRenderObject.dropChild
must be called, respectively. When altering the depth of a child,RenderObject.redepthChild
must be called to keepRenderObject.depth
in sync.Every node also needs to use
RenderObject.attach
andRenderObject.detach
to set and clear the pipeline owner (RenderObject.owner
), respectively. EveryRenderObject
has a parent (RenderObject.parent
) and an attached state (RenderObject.attached
).Parent data is a
ParentData
subclass 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
RenderObjects
implement the visitor pattern viaRenderObject.RenderObject.visitChildren
which invokes aRenderObjectVisitor
once per child.
What is necessary when altering the child model?
When adding or removing a child, call
RenderObject.adoptChild
andRenderObject.dropChild
respectively.The parent’s
RenderObject.attach
andRenderObject.detach
methods must call their counterpart on each child.The parent’s
RenderObject.redepthChildren
andRenderObject.visitChildren
methods must recursively callRenderObject.redepthChild
and the visitor argument on each child, respectively.
What are the render tree building blocks?
RenderObjectWithChildMixin
allocates storage for a single child within the object instance itself (RenderObjectWithChildMixin.child
).ContainerRenderObjectMixin
uses each child’s parent data (which must implementContainerParentDataMixin
) to build a doubly linked list viaContainerParentDataMixin.nextSibling
andContainerParentDataMixin.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.markNeedsLayout
is 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.layout
will be invoked with constraints as input and a size as output. Both the constraints and the size are implementation-dependent; the constraints must implementConstraints
whereas the size is entirely arbitrary.If a parent depends on the child’s geometry, it must pass the
parentUsesSize
argument to layout. The implementation ofRenderObject.markNeedsLayout
/RenderObject.sizedByParent
will need to callRenderObject.markParentNeedsLayout
/RenderObject.markParentNeedsLayoutForSizedByParentChange
, respectively.RenderObjects
that solely determine their sizing using the input constraints must setRenderObject.sizedByParent
to 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
RenderObject
subtypes may utilize additional out-of-band information when performing layout. If this information changes, and the parent used it during the last cycle,RenderObject.markParentNeedsLayout
must be invoked.
How do render objects manage hit testing?
RenderView
’s child is aRenderBox
, which must therefore defineRenderBox.hitTest
. To add a customRenderObject
to the render tree, the top levelRenderView
must be replaced (which would be a massive undertaking) or aRenderBox
adapter added to the tree. It is up to the implementer to determine howRenderBox.hitTest
is adapted to a customRenderObject
subclass; indeed, that render object can implement any manner ofhitTest
-like method of its choosing. Note that allRenderObjects
areHitTestTargets
and therefore will receive pointer events viaHitTestTarget.pointerEvent
once 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.markNeedsCompositingBitsUpdate
walks 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.flushCompositingBits
visits 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.layer
will beOffsetLayer
. In either case, by retaining a reference to a previously used layer,Flutter
is able to better utilize retained rendering -- i.e., recycle or selectively update previously rasterized bitmaps. This is possible becauseLayers
preserve a reference to any underlyingEngineLayer
, andSceneBuilder
accepts 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.transformTo
chains 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