hitTestChildren method

  1. @override
bool hitTestChildren(
  1. SliverHitTestResult result, {
  2. required double mainAxisPosition,
  3. required double crossAxisPosition,
})
override

Override this method to check whether any children are located at the given position.

Typically children should be hit-tested in reverse paint order so that hit tests at locations where children overlap hit the child that is visually "on top" (i.e., paints later).

Used by hitTest. If you override hitTest and do not call this function, then you don't need to implement this function.

For a discussion of the semantics of the arguments, see hitTest.

Implementation

@override
bool hitTestChildren(
  SliverHitTestResult result, {
  required double mainAxisPosition,
  required double crossAxisPosition,
}) {
  final scrollOffset = constraints.scrollOffset;
  final visibleNodes = controller.visibleNodes;
  final orderNids = controller.orderNidsView;

  // Phase 1: Test sticky headers first (they're visually on top).
  // Iterate shallowest first (index 0) = topmost = first hit priority.
  for (final sticky in _sticky.headers) {
    final child = getChildForNode(sticky.nodeId);
    if (child == null) continue;
    if (controller.isExiting(sticky.nodeId)) continue;

    final localMain = mainAxisPosition - sticky.pinnedY;
    if (localMain < 0 || localMain >= sticky.extent) continue;

    final localCross = crossAxisPosition - sticky.indent;
    if (localCross < 0) continue;

    final hit = result.addWithAxisOffset(
      paintOffset: Offset(sticky.indent, sticky.pinnedY),
      mainAxisOffset: sticky.pinnedY,
      crossAxisOffset: sticky.indent,
      mainAxisPosition: mainAxisPosition,
      crossAxisPosition: crossAxisPosition,
      hitTest:
          (
            SliverHitTestResult result, {
            required double mainAxisPosition,
            required double crossAxisPosition,
          }) {
            return child.hitTest(
              BoxHitTestResult.wrap(result),
              position: Offset(crossAxisPosition, mainAxisPosition),
            );
          },
    );

    if (hit) return true;
  }

  // Phase 2: Test normal nodes (skip sticky IDs). Widen the start by
  // the FLIP-slide overreach so a tap on a row whose structural y is
  // above the hit offset — but which has slid down into the tap point
  // — is still tested. The per-row `localMainAxisPosition` bounds
  // check below naturally skips non-overlapping rows, so iterating
  // extra rows at the top is cheap.
  final slideOverreach = controller.maxActiveSlideAbsDelta;
  final hitOffset = scrollOffset + mainAxisPosition;
  final startIndex =
      _findFirstVisibleIndex(hitOffset - slideOverreach);

  for (int i = startIndex; i < visibleNodes.length; i++) {
    final nid = orderNids[i];
    if (_sticky.isSticky(nid)) {
      continue;
    }

    final nodeId = visibleNodes[i];
    final child = getChildForNode(nodeId);
    if (child == null) continue;

    // Skip exiting nodes - they should not receive interactions
    // This prevents crashes when rapidly tapping delete buttons
    if (controller.isExitingNid(nid)) continue;

    final parentData = child.parentData! as SliverTreeParentData;
    final nodeOffset = parentData.layoutOffset;
    final nodeExtent = parentData.visibleExtent;

    // Shift the hit coordinate by the node's current slide delta so a
    // tap lands on the visually-displaced child rather than on the
    // structural position nobody sees during a slide.
    final slideDelta = controller.getSlideDeltaNid(nid);
    final localMainAxisPosition =
        mainAxisPosition + scrollOffset - nodeOffset - slideDelta;
    if (localMainAxisPosition < 0) continue;
    if (localMainAxisPosition >= nodeExtent) continue;

    final localCrossAxisPosition = crossAxisPosition - parentData.indent;
    if (localCrossAxisPosition < 0) continue;

    // Mirror paint's clip-and-translate trick. When a node is animating
    // and its visible extent is smaller than its intrinsic box, paint
    // draws the child shifted up by (height - extent) so the bottom slice
    // peeks through the clipped visible strip. Hit tests must apply the
    // same Y adjustment or taps on the visible slice would route to the
    // clipped-away top of the child box.
    final yAdjust =
        (controller.isAnimatingNid(nid) &&
            nodeExtent < child.size.height)
        ? (child.size.height - nodeExtent)
        : 0.0;

    final paintedMainOffset = nodeOffset - scrollOffset + slideDelta;
    final hit = result.addWithAxisOffset(
      paintOffset: Offset(parentData.indent, paintedMainOffset),
      mainAxisOffset: paintedMainOffset,
      crossAxisOffset: parentData.indent,
      mainAxisPosition: mainAxisPosition,
      crossAxisPosition: crossAxisPosition,
      hitTest:
          (
            SliverHitTestResult result, {
            required double mainAxisPosition,
            required double crossAxisPosition,
          }) {
            return child.hitTest(
              BoxHitTestResult.wrap(result),
              position: Offset(
                crossAxisPosition,
                mainAxisPosition + yAdjust,
              ),
            );
          },
    );

    if (hit) return true;
  }

  return false;
}