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 are HitTestTargets
), 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 “==” and hashCode
implementations.
ParentData
is opaque data stored in a child RenderObject
by its parent. The only requirement is that ParentData
instances implement a ParentData.detach
method to react to their render object being removed from the tree.
The RendererBinding
establishes a PipelineOwner
as part of RendererBinding.initInstances
(i.e., the binding’s “constructor”). The PipelineOwner
is analogous to the BuildOwner
, 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 via RendererBinding.drawFrame
.
Whenever an object marks itself dirty, it will generally also invoke PipelineOwner.requestVisualUpdate
to schedule a frame and update the UI. This calls the PipelineOwner.onNeedVisualUpdate
handler which schedules a frame via RendererBinding.ensureVisualUpdate
and SchedulerBinding.scheduleFrame
. The rendering pipeline (as facilitated by RendererBinding.drawFrame
or WidgetsBinding.drawFrame
) will invoke the PipelineOwner
’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
).
The render tree consists of AbstractNode
instances (RenderObject
is a subclass). When a child is added or removed, RenderObject.adoptChild
and RenderObject.dropChild
must be called, respectively. When altering the depth of a child, RenderObject.redepthChild
must be called to keep RenderObject.depth
in sync.
Every node also needs to use RenderObject.attach
and RenderObject.detach
to set and clear the pipeline owner (RenderObject.owner
), respectively. Every RenderObject
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 calling RenderObject.setupParentData
. The parent data may then be mutated in, e.g., RenderObject.performLayout
.
All RenderObjects
implement the visitor pattern via RenderObject.RenderObject.visitChildren
which invokes a RenderObjectVisitor
once per child.
When adding or removing a child, call RenderObject.adoptChild
and RenderObject.dropChild
respectively.
The parent’s RenderObject.attach
and RenderObject.detach
methods must call their counterpart on each child.
The parent’s RenderObject.redepthChildren
and RenderObject.visitChildren
methods must recursively call RenderObject.redepthChild
and the visitor argument on each child, respectively.
RenderObjectWithChildMixin
allocates storage for a single child within the object instance itself (RenderObjectWithChildMixin.child
).
ContainerRenderObjectMixin
uses each child’s parent data (which must implement ContainerParentDataMixin
) to build a doubly linked list via ContainerParentDataMixin.nextSibling
and ContainerParentDataMixin.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).
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 implement Constraints
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 of RenderObject.markNeedsLayout
/ RenderObject.sizedByParent
will need to call RenderObject.markParentNeedsLayout
/ RenderObject.markParentNeedsLayoutForSizedByParentChange
, respectively.
RenderObjects
that solely determine their sizing using the input constraints must set RenderObject.sizedByParent
to true and perform all layout in RenderObject.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.
RenderView
’s child is a RenderBox
, which must therefore define RenderBox.hitTest
. To add a custom RenderObject
to the render tree, the top level RenderView
must be replaced (which would be a massive undertaking) or a RenderBox
adapter added to the tree. It is up to the implementer to determine how RenderBox.hitTest
is adapted to a custom RenderObject
subclass; indeed, that render object can implement any manner of hitTest
-like method of its choosing. Note that all RenderObjects
are HitTestTargets
and therefore will receive pointer events via HitTestTarget.pointerEvent
once registered via HitTestEntry
.
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 be OffsetLayer
. 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 because Layers
preserve a reference to any underlying EngineLayer
, and SceneBuilder
accepts an “oldLayer
” argument when building a new scene.
Visual transforms are implemented in RenderObject.paint
. The same transform is applied logically (i.e., when hit testing, computing semantics, mapping coordinates, etc) via RenderObject.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
]
RenderBox
, RenderSliver
.