adjustPositionForNewDimensions method

  1. @override
double adjustPositionForNewDimensions({
  1. required ScrollMetrics oldPosition,
  2. required ScrollMetrics newPosition,
  3. required bool isScrolling,
  4. required double velocity,
})
override

Describes what the scroll position should be given new viewport dimensions.

This is called by ScrollPosition.correctForNewDimensions.

The arguments consist of the scroll metrics as they stood in the previous frame and the scroll metrics as they now stand after the last layout, including the position and minimum and maximum scroll extents; a flag indicating if the current ScrollActivity considers that the user is actively scrolling (see ScrollActivity.isScrolling); and the current velocity of the scroll position, if it is being driven by the scroll activity (this is 0.0 during a user gesture) (see ScrollActivity.velocity).

The scroll metrics will be identical except for the ScrollMetrics.minScrollExtent and ScrollMetrics.maxScrollExtent. They are referred to as the oldPosition and newPosition (even though they both technically have the same "position", in the form of ScrollMetrics.pixels) because they are generated from the ScrollPosition before and after updating the scroll extents.

If the returned value does not exactly match the scroll offset given by the newPosition argument (see ScrollMetrics.pixels), then the ScrollPosition will call ScrollPosition.correctPixels to update the new scroll position to the returned value, and layout will be re-run. This is expensive. The new value is subject to further manipulation by applyBoundaryConditions.

If the returned value does match the newPosition.pixels scroll offset exactly, then ScrollPosition.applyNewDimensions will be called next. In that case, applyBoundaryConditions is not applied to the return value.

The given ScrollMetrics are only valid during this method call. Do not keep references to them to use later, as the values may update, may not update, or may update to reflect an entirely unrelated scrollable.

The default implementation returns the ScrollMetrics.pixels of the newPosition, which indicates that the current scroll offset is acceptable.

See also:

  • RangeMaintainingScrollPhysics, which is enabled by default, and which prevents unexpected changes to the content dimensions from causing the scroll position to get any further out of bounds.

Implementation

@override
double adjustPositionForNewDimensions({
  required ScrollMetrics oldPosition,
  required ScrollMetrics newPosition,
  required bool isScrolling,
  required double velocity,
}) {
  bool maintainOverscroll = true;
  bool enforceBoundary = true;
  if (velocity != 0.0) {
    // Don't try to adjust an animating position, the jumping around
    // would be distracting.
    maintainOverscroll = false;
    enforceBoundary = false;
  }
  if ((oldPosition.minScrollExtent == newPosition.minScrollExtent) &&
      (oldPosition.maxScrollExtent == newPosition.maxScrollExtent)) {
    // If the extents haven't changed then ignore overscroll.
    maintainOverscroll = false;
  }
  if (oldPosition.pixels != newPosition.pixels) {
    // If the position has been changed already, then it might have
    // been adjusted to expect new overscroll, so don't try to
    // maintain the relative overscroll.
    maintainOverscroll = false;
    if (oldPosition.minScrollExtent.isFinite && oldPosition.maxScrollExtent.isFinite &&
        newPosition.minScrollExtent.isFinite && newPosition.maxScrollExtent.isFinite) {
      // In addition, if the position changed then we don't enforce the new
      // boundary if both the new and previous boundaries are entirely finite.
      // A common case where the position changes while one
      // of the extents is infinite is a lazily-loaded list. (If the
      // boundaries were finite, and the position changed, then we
      // assume it was intentional.)
      enforceBoundary = false;
    }
  }
  if ((oldPosition.pixels < oldPosition.minScrollExtent) ||
      (oldPosition.pixels > oldPosition.maxScrollExtent)) {
    // If the old position was out of range, then we should
    // not try to keep the new position in range.
    enforceBoundary = false;
  }
  if (maintainOverscroll) {
    // Force the new position to be no more out of range than it was before, if:
    //  * it was overscrolled, and
    //  * the extents have decreased, meaning that some content was removed. The
    //    reason for this condition is that when new content is added, keeping
    //    the same overscroll would mean that instead of showing it to the user,
    //    all of it is being skipped by jumping right to the max extent.
    if (oldPosition.pixels < oldPosition.minScrollExtent &&
        newPosition.minScrollExtent > oldPosition.minScrollExtent) {
      final double oldDelta = oldPosition.minScrollExtent - oldPosition.pixels;
      return newPosition.minScrollExtent - oldDelta;
    }
    if (oldPosition.pixels > oldPosition.maxScrollExtent &&
        newPosition.maxScrollExtent < oldPosition.maxScrollExtent) {
      final double oldDelta = oldPosition.pixels - oldPosition.maxScrollExtent;
      return newPosition.maxScrollExtent + oldDelta;
    }
  }
  // If we're not forcing the overscroll, defer to other physics.
  double result = super.adjustPositionForNewDimensions(oldPosition: oldPosition, newPosition: newPosition, isScrolling: isScrolling, velocity: velocity);
  if (enforceBoundary) {
    // ...but if they put us out of range then reinforce the boundary.
    result = clampDouble(result, newPosition.minScrollExtent, newPosition.maxScrollExtent);
  }
  return result;
}