scrollToPageSection function
Future<void>
scrollToPageSection(
- GlobalKey<
State< key, {StatefulWidget> > - double alignment = 0.08,
- ScrollController? controller,
- double? knownOffset,
- 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;
}
}
}