endDrag method
Commits the drop: mutates treeController (via TreeController.moveNode, TreeController.reorderChildren, or TreeController.reorderRoots) and starts the FLIP slide animation to interpolate old → new positions.
If no valid target is currently resolved, behaves like cancelDrag.
The slide is installed IN-FRAME by the sliver render object: this
method asks the render object to capture a baseline of current painted
offsets BEFORE mutating the controller; the next performLayout
(triggered by that mutation) snapshots the post-mutation offsets and
installs a FLIP slide from baseline → current. The paint pass of the
same frame then renders rows at their prior painted position and
slides them toward their new structural position smoothly — no
one-frame "jump to new position, then slide back" flicker.
Implementation
Future<void> endDrag() async {
final session = _session;
if (session == null) return;
final target = session.currentTarget;
if (target == null) {
cancelDrag();
return;
}
_autoScrollTicker.stop();
_lastAutoScrollTick = null;
// Stage the FLIP baseline BEFORE mutating. The render object's next
// performLayout will consume it and install the slide in-frame,
// avoiding the post-frame gap that used to produce a single-frame
// flicker of each moved row at its destination.
session.renderObject.beginSlideBaseline(
duration: slideDuration,
curve: slideCurve,
);
final currentParent = treeController.getParent(session.draggedKey);
final sameParent = currentParent == target.parentKey;
if (sameParent) {
// Build the live final sibling list — reorderChildren/reorderRoots
// reject lists containing pending-deletion entries and re-append them
// internally after validating the live ordering.
final liveSiblings = target.parentKey == null
? treeController.liveRootKeys
: treeController.getLiveChildren(target.parentKey as TKey);
liveSiblings.remove(session.draggedKey);
final insertAt = target.indexInFinalList.clamp(0, liveSiblings.length);
liveSiblings.insert(insertAt, session.draggedKey);
if (target.parentKey == null) {
treeController.reorderRoots(liveSiblings);
} else {
treeController.reorderChildren(
target.parentKey as TKey,
liveSiblings,
);
}
} else {
// Cross-parent: moveNode's `index` is the position in the new parent's
// final child list — exactly indexInFinalList.
treeController.moveNode(
session.draggedKey,
target.parentKey,
index: target.indexInFinalList,
);
}
_session = null;
notifyListeners();
}