createBallisticSimulation method

  1. @override
Simulation createBallisticSimulation (ScrollMetrics position, double velocity)
override

Returns a simulation for ballistic scrolling starting from the given position with the given velocity.

This is used by ScrollPositionWithSingleContext in the ScrollPositionWithSingleContext.goBallistic method. If the result is non-null, ScrollPositionWithSingleContext will begin a BallisticScrollActivity with the returned value. Otherwise, it will begin an idle activity instead.

The given position is only valid during this method call. Do not keep a reference to it to use later, as the values may update, may not update, or may update to reflect an entirely unrelated scrollable.

Implementation

@override
Simulation createBallisticSimulation(ScrollMetrics position, double velocity) {
  assert(
    position is _FixedExtentScrollPosition,
    'FixedExtentScrollPhysics can only be used with Scrollables that uses '
    'the FixedExtentScrollController'
  );

  final _FixedExtentScrollPosition 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 int settlingItemIndex = _getItemFromOffset(
    offset: testFrictionSimulation?.x(double.infinity) ?? metrics.pixels,
    itemExtent: metrics.itemExtent,
    minScrollExtent: metrics.minScrollExtent,
    maxScrollExtent: metrics.maxScrollExtent,
  );

  final double settlingPixels = settlingItemIndex * metrics.itemExtent;

  // Scenario 3:
  // If there's no velocity and we're already at where we intend to land,
  // do nothing.
  if (velocity.abs() < tolerance.velocity
      && (settlingPixels - metrics.pixels).abs() < tolerance.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 == metrics.itemIndex) {
    return SpringSimulation(
      spring,
      metrics.pixels,
      settlingPixels,
      velocity,
      tolerance: tolerance,
    );
  }

  // 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,
    tolerance.velocity * velocity.sign,
  );
}