aves/lib/widgets/common/behaviour/known_extent_scroll_physics.dart
Thibault Deckers 7b9213e82e fixes, upgrades
2023-10-22 23:51:05 +03:00

83 lines
3.3 KiB
Dart

import 'package:flutter/physics.dart';
import 'package:flutter/widgets.dart';
// adapted from Flutter `FixedExtentScrollPhysics` in `/widgets/list_wheel_scroll_view.dart`
class KnownExtentScrollPhysics extends ScrollPhysics {
final double Function(int index) indexToScrollOffset;
final int Function(double offset) scrollOffsetToIndex;
const KnownExtentScrollPhysics({
required this.indexToScrollOffset,
required this.scrollOffsetToIndex,
super.parent,
});
@override
KnownExtentScrollPhysics applyTo(ScrollPhysics? ancestor) {
return KnownExtentScrollPhysics(
indexToScrollOffset: indexToScrollOffset,
scrollOffsetToIndex: scrollOffsetToIndex,
parent: buildParent(ancestor),
);
}
@override
Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
final ScrollMetrics metrics = position;
// Scenario 1:
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at the scrollable's boundary.
if ((velocity <= 0.0 && metrics.pixels <= metrics.minScrollExtent) || (velocity >= 0.0 && metrics.pixels >= metrics.maxScrollExtent)) {
return super.createBallisticSimulation(metrics, velocity);
}
// Create a test simulation to see where it would have ballistically fallen
// naturally without settling onto items.
final Simulation? testFrictionSimulation = super.createBallisticSimulation(metrics, velocity);
// Scenario 2:
// If it was going to end up past the scroll extent, defer back to the
// parent physics' ballistics again which should put us on the scrollable's
// boundary.
if (testFrictionSimulation != null && (testFrictionSimulation.x(double.infinity) == metrics.minScrollExtent || testFrictionSimulation.x(double.infinity) == metrics.maxScrollExtent)) {
return super.createBallisticSimulation(metrics, velocity);
}
// From the natural final position, find the nearest item it should have
// settled to.
final offset = (testFrictionSimulation?.x(double.infinity) ?? metrics.pixels).clamp(metrics.minScrollExtent, metrics.maxScrollExtent);
final int settlingItemIndex = scrollOffsetToIndex(offset);
final double settlingPixels = indexToScrollOffset(settlingItemIndex);
// Scenario 3:
// If there's no velocity and we're already at where we intend to land,
// do nothing.
if (velocity.abs() < toleranceFor(position).velocity && (settlingPixels - metrics.pixels).abs() < toleranceFor(position).distance) {
return null;
}
// Scenario 4:
// If we're going to end back at the same item because initial velocity
// is too low to break past it, use a spring simulation to get back.
if (settlingItemIndex == scrollOffsetToIndex(metrics.pixels)) {
return SpringSimulation(
spring,
metrics.pixels,
settlingPixels,
velocity,
tolerance: toleranceFor(position),
);
}
// Scenario 5:
// Create a new friction simulation except the drag will be tweaked to land
// exactly on the item closest to the natural stopping point.
return FrictionSimulation.through(
metrics.pixels,
settlingPixels,
velocity,
toleranceFor(position).velocity * velocity.sign,
);
}
}