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:

This separation allows the render object to only do full relayout when structure changes, and just update geometry/repaint during animations.

Inheritance

Constructors

TreeController({required TickerProvider vsync, Duration animationDuration = const Duration(milliseconds: 300), Curve animationCurve = Curves.easeInOut, double indentWidth = 0.0, Comparator<TreeNode<TKey, TData>>? comparator})
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 _ensureAnimatingKeys cache; 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 _structuralListeners for 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 scrollController to reveal key in 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) and currentOffsets (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 _visibleOrder index 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). No TKey hash. nid must 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 key so that key becomes 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, otherwise extentEstimator if 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 guarantee nid is 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 key in pre-order (children, grandchildren, ...). Does not include key itself. Returns an empty list if key has 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 the TKey→nid hash. Caller must guarantee nid is 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 key within the live sibling list of its parent (or the live root list, if key is a root). Returns -1 if key is not present or is itself pending deletion.
getLiveChildren(TKey parent) List<TKey>
Children of parent that are not pending deletion.
getMeasuredExtent(TKey key) double?
The measured full extent for key, or null if key has 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 key in 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 the TKey→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 _ensureAnimatingKeys set (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 nid is live and within range.
isBulkMember(TKey key) bool
Whether key is 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 operationKey is identity-equal to the one captured by captureOperationGroupToken. False once the group has been replaced (re-expand cycle) or removed (animation settled). Pass null token to ask "was there ever one?" — always false on null.
isPendingDeletion(TKey key) bool
Whether key is pending deletion — present in the structural maps but animating out and scheduled for purge once the animation settles.
isVisible(TKey key) bool
Whether key is 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 key to 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 _notifyAnimationListeners so the scroll orchestrator's internal AnimationController (scrollProgress) can fire ticks through the controller's listener channel without exposing _notifyAnimationListeners publicly.
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 parentKey to match orderedKeys.
reorderRoots(List<TKey> orderedKeys) → void
Reorders the root nodes to match orderedKeys.
runBatch<T>(T body()) → T
Runs body with structural notifications coalesced into a single notifyListeners call fired after body returns.
scrollOffsetOf(TKey key, {double extentEstimator(TKey key)?}) double?
Returns the sliver-space scroll offset of key, or null if key is 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<TKey, TData>> children) → void
Adds children to a node.
setFullExtent(TKey key, double extent) → void
Stores the measured full extent for a node.
setRoots(List<TreeNode<TKey, TData>> roots) → void
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 when nid is not currently in the visible order. O(1) typed-data read; no TKey hash. Hot-path equivalent of _order.indexByNid[nid].
visibleNidAt(int visibleIndex) int
Returns the nid of the visible node at visibleIndex. No TKey hash occurs. Panics (unchecked read) if visibleIndex is 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".