scrollToPageSection function

Future<void> scrollToPageSection(
  1. GlobalKey<State<StatefulWidget>> key, {
  2. double alignment = 0.08,
  3. ScrollController? controller,
  4. double? knownOffset,
  5. Duration ensureDuration = const Duration(milliseconds: 280),
})

Scroll helper: jump to a section key inside one scrollable.

Uses controller when given, otherwise the nearest Scrollable. Does not call Scrollable.ensureVisible (that walks every ancestor viewport and would drag an outer catalog/list).

Implementation

Future<void> scrollToPageSection(
  GlobalKey key, {
  double alignment = 0.08,
  ScrollController? controller,
  double? knownOffset,
  Duration ensureDuration = const Duration(milliseconds: 280),
}) async {
  Future<ScrollPosition?> resolvePosition() async {
    if (controller != null && controller.hasClients) {
      return controller.position;
    }
    final ctx = key.currentContext;
    if (ctx == null) return null;
    return Scrollable.maybeOf(ctx)?.position;
  }

  Future<bool> scrollNearest(ScrollPosition position) async {
    final ctx = key.currentContext;
    if (ctx == null) return false;
    final box = ctx.findRenderObject();
    if (box == null) return false;
    final viewport = RenderAbstractViewport.maybeOf(box);
    if (viewport == null) return false;
    final target = viewport
        .getOffsetToReveal(box, alignment)
        .offset
        .clamp(position.minScrollExtent, position.maxScrollExtent);
    final motion = safaehResolvedMotion(ctx, ensureDuration);
    if ((target - position.pixels).abs() < 0.5) return true;
    if (motion == Duration.zero) {
      position.jumpTo(target);
    } else {
      await position.animateTo(
        target,
        duration: motion,
        curve: Curves.easeOutCubic,
      );
    }
    return true;
  }

  var position = await resolvePosition();
  if (position != null && key.currentContext != null) {
    if (await scrollNearest(position)) return;
  }

  final c = controller;
  if (c == null || !c.hasClients) return;

  void jump(double offset) {
    c.jumpTo(offset.clamp(0.0, c.position.maxScrollExtent));
  }

  if (knownOffset != null) {
    jump(knownOffset);
  } else {
    for (final frac in const [0.0, 0.3, 0.6, 1.0]) {
      jump(c.position.maxScrollExtent * frac);
      await WidgetsBinding.instance.endOfFrame;
      if (key.currentContext != null) break;
    }
  }

  await WidgetsBinding.instance.endOfFrame;
  position = await resolvePosition();
  if (position != null && await scrollNearest(position)) return;

  for (final frac in const [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]) {
    jump(c.position.maxScrollExtent * frac);
    await WidgetsBinding.instance.endOfFrame;
    if (key.currentContext != null) {
      position = await resolvePosition();
      if (position != null) await scrollNearest(position);
      return;
    }
  }
}