TreeController<TKey, TData> class
Controller for a SliverTree widget.
Manages:
- Tree structure (nodes, parent/child relationships, depth)
- Visibility (which nodes are in the flattened visible list)
- Expansion state (which nodes are expanded)
- Animation state (which nodes are animating and their progress)
Uses an ECS-style architecture where components are stored separately for efficient iteration and memory usage.
The controller provides two notification channels:
- addListener / removeListener from ChangeNotifier: for structure changes
- addAnimationListener / removeAnimationListener: for animation ticks
This separation allows the render object to only do full relayout when structure changes, and just update geometry/repaint during animations.
- Inheritance
-
- Object
- ChangeNotifier
- TreeController
Constructors
-
TreeController({required TickerProvider vsync, Duration animationDuration = const Duration(milliseconds: 300), Curve animationCurve = Curves.easeInOut, double indentWidth = 0.0, Comparator<
TreeNode< ? comparator})TKey, TData> > - Creates a tree controller.
Properties
- animationCurve ↔ Curve
-
Curve for expand/collapse animations.
getter/setter pair
- animationDuration ↔ Duration
-
Duration for expand/collapse animations.
getter/setter pair
- bulkAnimationGeneration → int
-
Monotonic counter that bumps whenever the bulk animation group is
created, destroyed, or its member set changes. The render object uses
this to detect when its cached per-position offset cumulatives are stale.
no setter
- bulkAnimationValue → double
-
Current animation value of the bulk animation group, or 0.0 if none.
no setter
-
comparator
→ Comparator<
TreeNode< ?TKey, TData> > -
Optional comparator for maintaining sorted order among siblings.
final
-
currentlyAnimatingKeys
→ Set<
TKey> -
Live, read-only view of every key currently animating across
standalone, operation-group, and bulk sources. Backed by the lazy
_ensureAnimatingKeyscache; do NOT mutate — same convention as orderNidsView. Iteration is stable within one frame.no setter -
debugAllKeys
→ Iterable<
TKey> -
All currently-live keys, in nid order. Debug-only accessor for tests
that need to pick a random key from the live set.
no setter
- hasActiveAnimations → bool
-
Whether any nodes are currently animating.
no setter
- hasActiveSlides → bool
-
Whether any FLIP slide animations are currently active.
no setter
- hasActiveXSlides → bool
-
Whether any in-flight slide has a non-zero X-axis component
(depth-changing reparent). Hot-path render code uses this to skip
per-row X-delta reads when no X-axis work is in flight — the common
case, since most reorders are same-depth.
no setter
- hashCode → int
-
The hash code for this object.
no setterinherited
- hasListeners → bool
-
Whether any listeners are currently registered.
no setterinherited
- hasOpGroupAnimations → bool
-
Whether any non-bulk animations (operation groups or standalone) are
currently active. When false and isBulkAnimating is true, the render
object can use its scalar-offset fast path for the whole frame.
no setter
- indentWidth ↔ double
-
Horizontal indent per depth level in logical pixels.
getter/setter pair
- isBulkAnimating → bool
-
True when a bulk animation group is currently active and has members
animating in either direction.
no setter
-
liveRootKeys
→ List<
TKey> -
Root keys that are not pending deletion.
no setter
- maxActiveSlideAbsDelta → double
-
Maximum |currentDelta| across every active slide entry, or 0.0 when
no slides are active.
no setter
- nidCapacity → int
-
The current high-water mark for allocated nids. Nid-indexed dense
arrays maintained externally should grow to at least this length.
no setter
- orderNidsView → Int32List
-
Read-only view over the visible-order nid buffer for hot-path
consumers that walk all visible positions and want to skip the
per-position visibleNidAt dispatch. The underlying buffer's
length may exceed visibleNodeCount — only the first N entries
are valid. The buffer itself is mutated in place by structural
changes; callers must not retain the reference across mutations.
no setter
- rootCount → int
-
no setter
-
rootKeys
→ List<
TKey> -
Root node IDs in order.
latefinal
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- slideClampOverhangViewports ↔ double
-
Padding past the viewport edge (in viewport-heights) used as the
start position (slide-IN) or end position (slide-OUT edge ghost) for
FLIP slides whose prior or current is off-screen. A small overhang
gives the row visible motion into / out of the viewport rather
than appearing/disappearing exactly at the edge.
getter/setter pair
- structureGeneration → int
-
no setter
- visibleNodeCount → int
-
Number of visible nodes.
no setter
-
visibleNodes
→ List<
TKey> -
The flattened list of visible node IDs in render order.
latefinal
Methods
-
addAnimationListener(
VoidCallback listener) → void - Registers a callback that fires on every animation tick.
-
addListener(
VoidCallback listener) → void -
Register a closure to be called when the object changes.
inherited
-
addNodeDataListener(
void listener(TKey key)) → void - Registers a callback that fires when a single node's data changes without any structural change (e.g. after updateNode).
-
addStructuralListener(
void listener(Set< TKey> ? affectedKeys)) → void -
Registers a callback that fires on structural mutations with an
optional set of affected keys. See
_structuralListenersfor the semantics of the argument. -
animateScrollToKey(
TKey key, {required ScrollController scrollController, Duration duration = const Duration(milliseconds: 300), Curve curve = Curves.easeInOut, double alignment = 0.0, AncestorExpansionMode ancestorExpansion = AncestorExpansionMode.immediate, double extentEstimator(TKey key)?, double sliverBaseOffset = 0.0}) → Future< bool> -
Animates
scrollControllerto revealkeyin its attached viewport. -
animateSlideFromOffsets(
Map< TKey, ({double x, double y})> priorOffsets, Map<TKey, ({double x, double y})> currentOffsets, {Duration duration = const Duration(milliseconds: 220), Curve curve = Curves.easeOutCubic, double maxSlideDistance = double.infinity}) → void -
Starts a FLIP slide animation for every visible node whose position in
scroll-space changed between
priorOffsets(pre-mutation) andcurrentOffsets(post-mutation). Produce both with RenderSliverTree.snapshotVisibleOffsets — the first before the structural mutation, the second from inside a WidgetsBinding.addPostFrameCallback after the mutation's layout has run. -
bulkAnimationData(
) → BulkAnimationData< TKey> -
Captures every per-frame bulk-animation field the render object reads
during a single layout —
isValid(formerly isBulkAnimating),value(formerly bulkAnimationValue),generation(formerly bulkAnimationGeneration),memberCount, and a per-key membership query (formerly isBulkMember) — into one snapshot. -
captureOperationGroupToken(
TKey operationKey) → Object? -
Captures an opaque token identifying the current operation group at
operationKey, or null if no group is installed there. The token is only valid as input to isOperationGroupSame — its concrete type is not part of the public contract and may change. -
collapse(
{required TKey key, bool animate = true}) → void - Collapses the given node, hiding its children.
-
collapseAll(
{bool animate = true, int? maxDepth}) → void - Collapses all nodes in the tree.
-
computeFirstAnimatingVisibleIndex(
) → int -
Returns the smallest
_visibleOrderindex among all currently-animating nodes, or visibleNodeCount when none are visible / none are animating. -
debugAssertVisibleSubtreeSizeConsistency(
) → void -
Debug-only consistency check for
_visibleSubtreeSizeByNid. Exposed for fuzz tests via debugAssertVisibleSubtreeSizeConsistency. -
debugPrintBulkAnimationState(
) → void - Debug helper to print bulk animation state. Call this to verify animation is running correctly.
-
depthOfNid(
int nid) → int -
Depth for
nid(0 for roots). NoTKeyhash.nidmust be live. -
dispose(
) → void -
Discards any resources used by the object. After this is called, the
object is not in a usable state and should be discarded (calls to
addListener will throw after the object is disposed).
override
-
ensureAncestorsExpanded(
TKey key) → int -
Immediately expands every collapsed ancestor of
keyso thatkeybecomes part of the visible order. Expansion is synchronous (no animation) so a subsequent scrollOffsetOf call sees the updated structure. Returns the number of ancestors that were expanded. -
expand(
{required TKey key, bool animate = true}) → void - Expands the given node, revealing its children.
-
expandAll(
{bool animate = true, int? maxDepth}) → void - Expands all nodes in the tree.
-
extentOf(
TKey key, {double extentEstimator(TKey key)?}) → double -
Returns the best-known full (non-animated) extent for
key: the measured value if the node has ever been laid out, otherwiseextentEstimatorif supplied, otherwise defaultExtent. Matches the fallback chain used by scrollOffsetOf. -
getAnimatedExtent(
TKey key, double fullExtent) → double - Gets the animated extent for a node.
-
getAnimationState(
TKey key) → AnimationState? - Gets the animation state for a node, or null if not animating.
-
getChildCount(
TKey key) → int - Gets the number of children for the given node.
-
getChildren(
TKey key) → List< TKey> - Gets the ordered list of child keys for the given node.
-
getCurrentExtent(
TKey key) → double - Gets the current extent for a node, accounting for animation.
-
getCurrentExtentNid(
int nid) → double -
Current animated extent for the live
nid. Hot-path equivalent of getCurrentExtent; resolves the chain bulk → operation-group → standalone → fullExtent. Caller must guaranteenidis live and within range. -
getDepth(
TKey key) → int - Gets the depth of the given node (0 for roots).
-
getDescendants(
TKey key) → List< TKey> -
Returns all descendants of
keyin pre-order (children, grandchildren, ...). Does not includekeyitself. Returns an empty list ifkeyhas no children or is not present. -
getEstimatedExtent(
TKey key) → double - Gets the estimated full extent for a node.
-
getEstimatedExtentNid(
int nid) → double -
Estimated full extent for the live
nid— measured value when available, defaultExtent otherwise. Hot-path equivalent of getEstimatedExtent that avoids theTKey→nid hash. Caller must guaranteenidis live and within range. -
getIndent(
TKey key) → double - Gets the horizontal indent for the given node.
-
getIndexInParent(
TKey key) → int -
Returns the zero-based index of
keywithin the live sibling list of its parent (or the live root list, ifkeyis a root). Returns -1 ifkeyis not present or is itself pending deletion. -
getLiveChildren(
TKey parent) → List< TKey> -
Children of
parentthat are not pending deletion. -
getMeasuredExtent(
TKey key) → double? -
The measured full extent for
key, or null ifkeyhas never been laid out. Distinct from getEstimatedExtent, which falls back to defaultExtent for unmeasured nodes. -
getNodeData(
TKey key) → TreeNode< TKey, TData> ? - Gets the node data for the given key, or null if not found.
-
getParent(
TKey key) → TKey? - Gets the parent key for the given node, or null if it is a root.
-
getSlideDelta(
TKey key) → double -
Current slide delta for
keyin scroll-space y, or 0.0 if the node is not currently sliding. Read by RenderSliverTree.paint, RenderSliverTree.applyPaintTransform, and the hit-test path on every frame (no caching — staleness-safe under tick-without-paint). -
getSlideDeltaNid(
int nid) → double -
Slide delta for the live
nid(paint-only FLIP offset), or 0.0 when the node is not currently sliding. Hot-path equivalent of getSlideDelta — read every paint, hit-test, and transform call for visible rows, so saving theTKey→nid hash matters. -
getSlideDeltaX(
TKey key) → double -
X-axis (cross-axis indent) slide delta for
key, or 0.0 when the node is not currently sliding (or not registered). -
getSlideDeltaXNid(
int nid) → double -
X-axis (cross-axis indent) slide delta for the live
nid, or 0.0 when the node is not currently sliding. Hot-path equivalent of getSlideDeltaX — read on every paint, hit-test, and transform call for visible rows during a depth-changing reparent. -
getVisibleIndex(
TKey key) → int - Gets the index of a node in the visible order, or -1 if not visible.
-
hasChildren(
TKey key) → bool - Whether the given node has children.
-
insert(
{required TKey parentKey, required TreeNode< TKey, TData> node, int? index, bool animate = true, bool preservePendingSubtreeState = false}) → void - Inserts a new node as a child of the given parent.
-
insertRoot(
TreeNode< TKey, TData> node, {int? index, bool animate = true, bool preservePendingSubtreeState = false}) → void - Adds a new root node to the tree.
-
isAnimating(
TKey key) → bool -
Whether the given node is currently animating. O(1) via the cached
_ensureAnimatingKeysset (rebuilt lazily when animation membership changes). -
isAnimatingNid(
int nid) → bool -
Hot-path equivalent of isAnimating: O(1) array read instead of a
HashMap-keyed Set lookup. Caller must guarantee
nidis live and within range. -
isBulkMember(
TKey key) → bool -
Whether
keyis a member of the bulk animation group (either active or pending removal at animation end). -
isExiting(
TKey key) → bool - Whether the given node is currently exiting (animating out).
-
isExitingNid(
int nid) → bool - Hot-path equivalent of isExiting.
-
isExpanded(
TKey key) → bool - Whether the given node is expanded.
-
isOperationGroupSame(
TKey operationKey, Object? token) → bool -
Whether the operation group at
operationKeyis identity-equal to the one captured by captureOperationGroupToken. False once the group has been replaced (re-expand cycle) or removed (animation settled). Passnulltoken to ask "was there ever one?" — always false on null. -
isPendingDeletion(
TKey key) → bool -
Whether
keyis pending deletion — present in the structural maps but animating out and scheduled for purge once the animation settles. -
isVisible(
TKey key) → bool -
Whether
keyis currently in the flattened visible order — i.e. every ancestor (if any) is expanded AND the node itself exists. -
keyOfNid(
int nid) → TKey? -
Returns the key associated with
nid, or null if the nid has been released. O(1). Consumers that cache nid-indexed state can use this to detect stale entries after node removal. -
markSlidePreserveProgress(
TKey key) → void -
Internal-use-only: marks the slide entry for
keyto bypass the engine's "un-touched re-baseline" branch on subsequent batch installs. -
moveNode(
TKey key, TKey? newParentKey, {int? index, bool animate = false, Duration slideDuration = const Duration(milliseconds: 220), Curve slideCurve = Curves.easeOutCubic}) → void -
Moves a node from its current parent to
newParentKey. -
nidOf(
TKey key) → int -
Returns the internal nid for
key, or noNid if the key isn't currently registered. O(1). -
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
notifyAnimationListenersForScroll(
) → void -
Forwards to
_notifyAnimationListenersso the scroll orchestrator's internalAnimationController(scrollProgress) can fire ticks through the controller's listener channel without exposing_notifyAnimationListenerspublicly. -
notifyListeners(
) → void -
Call all the registered listeners.
inherited
-
registerRenderHost(
TreeRenderHost host) → void -
Registers a render host. Called by
RenderSliverTree.attach. Idempotent. -
remove(
{required TKey key, bool animate = true}) → void - Removes a node and all its descendants from the tree.
-
removeAnimationListener(
VoidCallback listener) → void - Removes a previously registered animation listener.
-
removeListener(
VoidCallback listener) → void -
Remove a previously registered closure from the list of closures that are
notified when the object changes.
inherited
-
removeNodeDataListener(
void listener(TKey key)) → void - Removes a previously registered node-data listener.
-
removeStructuralListener(
void listener(Set< TKey> ? affectedKeys)) → void - Removes a previously registered structural listener.
-
reorderChildren(
TKey parentKey, List< TKey> orderedKeys) → void -
Reorders the children of
parentKeyto matchorderedKeys. -
reorderRoots(
List< TKey> orderedKeys) → void -
Reorders the root nodes to match
orderedKeys. -
runBatch<
T> (T body()) → T -
Runs
bodywith structural notifications coalesced into a single notifyListeners call fired afterbodyreturns. -
scrollOffsetOf(
TKey key, {double extentEstimator(TKey key)?}) → double? -
Returns the sliver-space scroll offset of
key, or null ifkeyis not in the current visible order (e.g., ancestors collapsed, or key not registered). The offset corresponds to the node's top edge within the SliverTree's own scroll extent. -
setChildren(
TKey parentKey, List< TreeNode< children) → voidTKey, TData> > - Adds children to a node.
-
setFullExtent(
TKey key, double extent) → void - Stores the measured full extent for a node.
-
setRoots(
List< TreeNode< roots) → voidTKey, TData> > - Initializes the tree with the given root nodes.
-
takePendingExitPhantomAnchors(
) → Map< TKey, TKey> ? - Returns and clears the staged exit-phantom relationships. Companion to takePendingPhantomAnchors for the visible-to-hidden case.
-
takePendingPhantomAnchors(
) → Map< TKey, TKey> ? - Returns and clears the staged phantom-anchor relationships. Called by the render object during baseline consumption. Returns null when no relationships were staged (the common case — only animated reparents of hidden subtrees produce entries).
-
toggle(
{required TKey key, bool animate = true}) → void - Toggles the expansion state of the given node.
-
toString(
) → String -
A string representation of this object.
inherited
-
unregisterRenderHost(
TreeRenderHost host) → void -
Unregisters a render host. Called by
RenderSliverTree.detach. Tolerant of a host that was never registered (no-op). -
updateNode(
TreeNode< TKey, TData> node) → void - Updates the data payload for an existing node without structural changes.
-
visibleIndexOfNid(
int nid) → int -
Visible-order index for the live nid
nid, or VisibleOrderBuffer.kNotVisible whennidis not currently in the visible order. O(1) typed-data read; noTKeyhash. Hot-path equivalent of_order.indexByNid[nid]. -
visibleNidAt(
int visibleIndex) → int -
Returns the nid of the visible node at
visibleIndex. NoTKeyhash occurs. Panics (unchecked read) ifvisibleIndexis out of range.
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited
Constants
- defaultExtent → const double
- Default extent for nodes that haven't been measured yet.
- noNid → const int
- Sentinel returned by nidOf when the key isn't registered. Same value as the internal VisibleOrderBuffer.kNotVisible but exposed separately since callers should treat it as "unknown key".