RepaintBoundary class

Marks child's subtree as its own paint isolate.

On frames where nothing inside the boundary has changed, the framework composites the boundary's cached cells into the frame buffer instead of walking the subtree's paint methods — one pass over the cached cells instead of a recursive paint chain. Cells the subtree left empty show what lies beneath, as they would painted directly.

The cache covers the child's layout size. Content outside that rectangle is clipped, including its focus and semantic bounds. Give overflowing content enough layout space, or place it in an Overlay outside the boundary instead.

Wrap a subtree whose paint you want isolated from a neighbour's churn (a sidebar beside a live log, a static header above an animating body). Direct use is opt-in, like Flutter's RepaintBoundary.

One place inserts them automatically: ListView wraps each item (its addRepaintBoundaries flag, on by default), because a list is the canonical "one row changes, the rest don't" shape. Measured on the paint-walk probe, a localized update in an N-row list is ~3x faster even for trivially cheap rows and 6-16x for styled full-width rows — the paint walk over N rows costs more than blitting N-1 cached rows regardless of per-row cost. The trade is one reused cache buffer per visible item (bounded by the viewport), which for a TUI is a small, fixed memory cost for a large CPU win.

The boundary's child paints into the cache the first time, and again whenever a RenderObjectElement inside it is reconciled or any render object's child list changes. Stable subtrees (const widgets, or instances kept identical across builds) stay cached across many frames. A list row that merely SCROLLS keeps its content and only changes offset, so it cache-hits and blits at the new row — the pruning survives scrolling.

The one anti-pattern: do NOT combine boundaries with KEYS to "recycle" rows of changing content (the keyed-list pattern from DOM frameworks). Keyed boundaries make every moved-and-mutated row a reconciled subtree — each invalidates its cache, so you pay the boundary bookkeeping AND the repaint (measured ~2x slower), and it defeats scroll detection (which turns shifted rows into a single buffer move). ListView's per-item boundaries are deliberately POSITIONAL (unkeyed), which is exactly why they cache-hit on scroll instead of thrashing.

Inheritance
Available extensions

Constructors

RepaintBoundary({Key? key, Widget? child})
const

Properties

child → Widget?
The widget below this widget in the tree.
finalinherited
hashCode → int
The hash code for this object.
no setterinherited
key → Key?
finalinherited
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited

Methods

animate({Duration? duration, Curve? curve, Object? trigger, bool repeat = false}) → Animate

Available on Widget, provided by the AnimateExtension extension

Starts an Animate chain over this widget. duration / curve apply to the whole chain. With no trigger it plays on mount; with a non-null trigger it rests on mount and replays whenever the token changes.
createElement() → SingleChildRenderObjectElement
Creates the Element that will mount this widget into the tree.
inherited
createRenderObject(BuildContext context) → RenderRepaintBoundary
Creates the render object backing this widget. Called once when the element first mounts.
override
hasEquivalentWidgetConfiguration(Widget other) → bool
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toString() → String
A string representation of this object.
inherited
updateRenderObject(BuildContext context, covariant RenderObject renderObject) → void
Applies any changes from a new widget instance to the existing render object. Default is a no-op; subclasses override when the widget carries layout/paint configuration.
inherited

Operators

operator ==(Object other) → bool
The equality operator.
inherited