applyPaintTransform method
Applies the transform that would be applied when painting the given child to the given matrix.
Used by coordinate conversion functions (getTransformTo, for example) to translate coordinates local to one render object into coordinates local to another render object.
Some RenderObjects will provide a zeroed out matrix in this method,
indicating that the child should not paint anything or respond to hit
tests currently. A parent may supply a non-zero matrix even though it
does not paint its child currently, for example if the parent is a
RenderOffstage with offstage set to true. In both of these cases,
the parent must return false from paintsChild.
Implementation
@override
void applyPaintTransform(covariant RenderBox child, Matrix4 transform) {
final parentData = child.parentData! as SliverTreeParentData;
final nodeId = parentData.nodeId;
// Resolve nid once and reuse it for sticky / animating / slide
// checks below — three queries that all share the same key→nid hash.
final nid = nodeId == null ? -1 : _controller.nidOf(nodeId as TKey);
// Check if this child is a sticky header (O(1) lookup).
if (nid >= 0) {
final sticky = _sticky.infoForNid(nid);
if (sticky != null) {
transform.translateByDouble(sticky.indent, sticky.pinnedY, 0.0, 1.0);
return;
}
}
// Mirror paint's clip-and-translate trick. When a node is animating and
// its visible extent is smaller than its intrinsic box, paint shifts the
// child up by (height - extent) so the bottom slice peeks through the
// clipped strip. The transform must include that same Y shift or callers
// that resolve via applyPaintTransform (localToGlobal, layer composition,
// showOnScreen, semantics) will be off by (height - extent) pixels.
final yAdjust =
(nid >= 0 &&
controller.isAnimatingNid(nid) &&
parentData.visibleExtent < child.size.height)
? (child.size.height - parentData.visibleExtent)
: 0.0;
// Include the node's current slide delta (paint-only FLIP offset) so
// callers that resolve coordinates via applyPaintTransform — localToGlobal,
// focus traversal, semantics, Scrollable.ensureVisible — track the
// visually-displaced row during a slide. Skip the reads entirely when
// no slides are in flight (idle-state fast path).
final hasSlides = controller.hasActiveSlides;
final hasXSlides = hasSlides && controller.hasActiveXSlides;
final slideDelta =
(hasSlides && nid >= 0) ? controller.getSlideDeltaNid(nid) : 0.0;
final slideDeltaX =
(hasXSlides && nid >= 0) ? controller.getSlideDeltaXNid(nid) : 0.0;
final scrollOffset = constraints.scrollOffset;
// Edge-ghost rows paint at `_edgeGhostBaseY + slideDelta`, not at
// `parentData.layoutOffset + slideDelta`. Substitute the live edge
// base so framework code (`localToGlobal`, semantics, focus
// traversal) sees the row at its actual painted position. The live
// base re-anchors to the current viewport edge under concurrent
// scrolling. Settled-check: if the slide is settled but lazy-prune
// hasn't run, fall back to the structural offset so post-settlement
// queries report the row's real (off-screen) position.
final edgeEntry = nodeId == null ? null : _phantomEdgeExits?[nodeId];
final useGhost = edgeEntry != null
&& (slideDelta != 0.0 || slideDeltaX != 0.0);
final base = useGhost
? _edgeGhostBaseY(edgeEntry, _currentViewportSnapshot())
: parentData.layoutOffset;
transform.translateByDouble(
parentData.indent + slideDeltaX,
base - scrollOffset - yAdjust + slideDelta,
0.0,
1.0,
);
}