Skip to content

Commit

Permalink
Remove single-view assumption from ScrollPhysics (#117503)
Browse files Browse the repository at this point in the history
* Remove single-view assumption from ScrollPhysics

* fix scrollable_dispose_test.dart

* add deprecated method back
  • Loading branch information
goderbauer authored Dec 22, 2022
1 parent 725c141 commit 999356b
Show file tree
Hide file tree
Showing 16 changed files with 102 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -371,14 +371,14 @@ class _SnappingScrollPhysics extends ClampingScrollPhysics {
return _SnappingScrollPhysics(parent: buildParent(ancestor), midScrollOffset: midScrollOffset);
}

Simulation _toMidScrollOffsetSimulation(double offset, double dragVelocity) {
Simulation _toMidScrollOffsetSimulation(double offset, double dragVelocity, ScrollMetrics metrics) {
final double velocity = math.max(dragVelocity, minFlingVelocity);
return ScrollSpringSimulation(spring, offset, midScrollOffset, velocity, tolerance: tolerance);
return ScrollSpringSimulation(spring, offset, midScrollOffset, velocity, tolerance: toleranceFor(metrics));
}

Simulation _toZeroScrollOffsetSimulation(double offset, double dragVelocity) {
Simulation _toZeroScrollOffsetSimulation(double offset, double dragVelocity, ScrollMetrics metrics) {
final double velocity = math.max(dragVelocity, minFlingVelocity);
return ScrollSpringSimulation(spring, offset, 0.0, velocity, tolerance: tolerance);
return ScrollSpringSimulation(spring, offset, 0.0, velocity, tolerance: toleranceFor(metrics));
}

@override
Expand All @@ -396,10 +396,10 @@ class _SnappingScrollPhysics extends ClampingScrollPhysics {
return simulation;
}
if (dragVelocity > 0.0) {
return _toMidScrollOffsetSimulation(offset, dragVelocity);
return _toMidScrollOffsetSimulation(offset, dragVelocity, position);
}
if (dragVelocity < 0.0) {
return _toZeroScrollOffsetSimulation(offset, dragVelocity);
return _toZeroScrollOffsetSimulation(offset, dragVelocity, position);
}
} else {
// The user ended the drag with little or no velocity. If they
Expand All @@ -408,10 +408,10 @@ class _SnappingScrollPhysics extends ClampingScrollPhysics {
// otherwise snap to zero.
final double snapThreshold = midScrollOffset / 2.0;
if (offset >= snapThreshold && offset < midScrollOffset) {
return _toMidScrollOffsetSimulation(offset, dragVelocity);
return _toMidScrollOffsetSimulation(offset, dragVelocity, position);
}
if (offset > 0.0 && offset < snapThreshold) {
return _toZeroScrollOffsetSimulation(offset, dragVelocity);
return _toZeroScrollOffsetSimulation(offset, dragVelocity, position);
}
}
return simulation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,7 @@ class _DraggableScrollableSheetScrollPosition extends ScrollPositionWithSingleCo
bool get _isAtSnapSize {
return extent.snapSizes.any(
(double snapSize) {
return (extent.currentSize - snapSize).abs() <= extent.pixelsToSize(physics.tolerance.distance);
return (extent.currentSize - snapSize).abs() <= extent.pixelsToSize(physics.toleranceFor(this).distance);
},
);
}
Expand Down Expand Up @@ -937,7 +937,7 @@ class _DraggableScrollableSheetScrollPosition extends ScrollPositionWithSingleCo
initialVelocity: velocity,
pixelSnapSize: extent.pixelSnapSizes,
snapAnimationDuration: extent.snapAnimationDuration,
tolerance: physics.tolerance,
tolerance: physics.toleranceFor(this),
);
} else {
// The iOS bouncing simulation just isn't right here - once we delegate
Expand All @@ -946,7 +946,7 @@ class _DraggableScrollableSheetScrollPosition extends ScrollPositionWithSingleCo
// Run the simulation in terms of pixels, not extent.
position: extent.currentPixels,
velocity: velocity,
tolerance: physics.tolerance,
tolerance: physics.toleranceFor(this),
);
}

Expand All @@ -965,7 +965,7 @@ class _DraggableScrollableSheetScrollPosition extends ScrollPositionWithSingleCo
// Make sure we pass along enough velocity to keep scrolling - otherwise
// we just "bounce" off the top making it look like the list doesn't
// have more to scroll.
velocity = ballisticController.velocity + (physics.tolerance.velocity * ballisticController.velocity.sign);
velocity = ballisticController.velocity + (physics.toleranceFor(this).velocity * ballisticController.velocity.sign);
super.goBallistic(velocity);
ballisticController.stop();
} else if (ballisticController.isCompleted) {
Expand Down
13 changes: 9 additions & 4 deletions packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ class FixedExtentMetrics extends FixedScrollMetrics {
required super.viewportDimension,
required super.axisDirection,
required this.itemIndex,
required super.devicePixelRatio,
});

@override
Expand All @@ -326,6 +327,7 @@ class FixedExtentMetrics extends FixedScrollMetrics {
double? viewportDimension,
AxisDirection? axisDirection,
int? itemIndex,
double? devicePixelRatio,
}) {
return FixedExtentMetrics(
minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
Expand All @@ -334,6 +336,7 @@ class FixedExtentMetrics extends FixedScrollMetrics {
viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
axisDirection: axisDirection ?? this.axisDirection,
itemIndex: itemIndex ?? this.itemIndex,
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
);
}

Expand Down Expand Up @@ -399,6 +402,7 @@ class _FixedExtentScrollPosition extends ScrollPositionWithSingleContext impleme
double? viewportDimension,
AxisDirection? axisDirection,
int? itemIndex,
double? devicePixelRatio,
}) {
return FixedExtentMetrics(
minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
Expand All @@ -407,6 +411,7 @@ class _FixedExtentScrollPosition extends ScrollPositionWithSingleContext impleme
viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
axisDirection: axisDirection ?? this.axisDirection,
itemIndex: itemIndex ?? this.itemIndex,
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
);
}
}
Expand Down Expand Up @@ -505,8 +510,8 @@ class FixedExtentScrollPhysics extends ScrollPhysics {
// 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) {
if (velocity.abs() < toleranceFor(position).velocity
&& (settlingPixels - metrics.pixels).abs() < toleranceFor(position).distance) {
return null;
}

Expand All @@ -519,7 +524,7 @@ class FixedExtentScrollPhysics extends ScrollPhysics {
metrics.pixels,
settlingPixels,
velocity,
tolerance: tolerance,
tolerance: toleranceFor(position),
);
}

Expand All @@ -530,7 +535,7 @@ class FixedExtentScrollPhysics extends ScrollPhysics {
metrics.pixels,
settlingPixels,
velocity,
tolerance.velocity * velocity.sign,
toleranceFor(position).velocity * velocity.sign,
);
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/flutter/lib/src/widgets/nested_scroll_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ class _NestedScrollMetrics extends FixedScrollMetrics {
required super.pixels,
required super.viewportDimension,
required super.axisDirection,
required super.devicePixelRatio,
required this.minRange,
required this.maxRange,
required this.correctionOffset,
Expand All @@ -538,6 +539,7 @@ class _NestedScrollMetrics extends FixedScrollMetrics {
double? pixels,
double? viewportDimension,
AxisDirection? axisDirection,
double? devicePixelRatio,
double? minRange,
double? maxRange,
double? correctionOffset,
Expand All @@ -548,6 +550,7 @@ class _NestedScrollMetrics extends FixedScrollMetrics {
pixels: pixels ?? (hasPixels ? this.pixels : null),
viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
axisDirection: axisDirection ?? this.axisDirection,
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
minRange: minRange ?? this.minRange,
maxRange: maxRange ?? this.maxRange,
correctionOffset: correctionOffset ?? this.correctionOffset,
Expand Down Expand Up @@ -815,6 +818,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
minRange: minRange,
maxRange: maxRange,
correctionOffset: correctionOffset,
devicePixelRatio: _outerPosition!.devicePixelRatio,
);
}

Expand Down
7 changes: 6 additions & 1 deletion packages/flutter/lib/src/widgets/page_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ class PageMetrics extends FixedScrollMetrics {
required super.viewportDimension,
required super.axisDirection,
required this.viewportFraction,
required super.devicePixelRatio,
});

@override
Expand All @@ -283,6 +284,7 @@ class PageMetrics extends FixedScrollMetrics {
double? viewportDimension,
AxisDirection? axisDirection,
double? viewportFraction,
double? devicePixelRatio,
}) {
return PageMetrics(
minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
Expand All @@ -291,6 +293,7 @@ class PageMetrics extends FixedScrollMetrics {
viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
axisDirection: axisDirection ?? this.axisDirection,
viewportFraction: viewportFraction ?? this.viewportFraction,
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
);
}

Expand Down Expand Up @@ -493,6 +496,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri
double? viewportDimension,
AxisDirection? axisDirection,
double? viewportFraction,
double? devicePixelRatio,
}) {
return PageMetrics(
minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
Expand All @@ -501,6 +505,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri
viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
axisDirection: axisDirection ?? this.axisDirection,
viewportFraction: viewportFraction ?? this.viewportFraction,
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
);
}
}
Expand Down Expand Up @@ -573,7 +578,7 @@ class PageScrollPhysics extends ScrollPhysics {
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
return super.createBallisticSimulation(position, velocity);
}
final Tolerance tolerance = this.tolerance;
final Tolerance tolerance = toleranceFor(position);
final double target = _getTargetPixels(position, tolerance, velocity);
if (target != position.pixels) {
return ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance);
Expand Down
10 changes: 10 additions & 0 deletions packages/flutter/lib/src/widgets/scroll_metrics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ mixin ScrollMetrics {
double? pixels,
double? viewportDimension,
AxisDirection? axisDirection,
double? devicePixelRatio,
}) {
return FixedScrollMetrics(
minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),
pixels: pixels ?? (hasPixels ? this.pixels : null),
viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
axisDirection: axisDirection ?? this.axisDirection,
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
);
}

Expand Down Expand Up @@ -124,6 +126,10 @@ mixin ScrollMetrics {
/// The quantity of content conceptually "below" the viewport in the scrollable.
/// This is the content below the content described by [extentInside].
double get extentAfter => math.max(maxScrollExtent - pixels, 0.0);

/// The [FlutterView.devicePixelRatio] of the view that the [Scrollable]
/// associated with this metrics object is drawn into.
double get devicePixelRatio;
}

/// An immutable snapshot of values associated with a [Scrollable] viewport.
Expand All @@ -137,6 +143,7 @@ class FixedScrollMetrics with ScrollMetrics {
required double? pixels,
required double? viewportDimension,
required this.axisDirection,
required this.devicePixelRatio,
}) : _minScrollExtent = minScrollExtent,
_maxScrollExtent = maxScrollExtent,
_pixels = pixels,
Expand Down Expand Up @@ -170,6 +177,9 @@ class FixedScrollMetrics with ScrollMetrics {
@override
final AxisDirection axisDirection;

@override
final double devicePixelRatio;

@override
String toString() {
return '${objectRuntimeType(this, 'FixedScrollMetrics')}(${extentBefore.toStringAsFixed(1)}..[${extentInside.toStringAsFixed(1)}]..${extentAfter.toStringAsFixed(1)})';
Expand Down
34 changes: 24 additions & 10 deletions packages/flutter/lib/src/widgets/scroll_physics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:math' as math;

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/painting.dart' show AxisDirection;
import 'package:flutter/physics.dart';

import 'binding.dart' show WidgetsBinding;
Expand Down Expand Up @@ -382,16 +383,29 @@ class ScrollPhysics {
/// The spring to use for ballistic simulations.
SpringDescription get spring => parent?.spring ?? _kDefaultSpring;

/// The default accuracy to which scrolling is computed.
static final Tolerance _kDefaultTolerance = Tolerance(
// TODO(ianh): Handle the case of the device pixel ratio changing.
// TODO(ianh): Get this from the local MediaQuery not dart:ui's window object.
velocity: 1.0 / (0.050 * WidgetsBinding.instance.window.devicePixelRatio), // logical pixels per second
distance: 1.0 / WidgetsBinding.instance.window.devicePixelRatio, // logical pixels
);
/// Deprecated. Call [toleranceFor] instead.
@Deprecated(
'Call toleranceFor instead. '
'This feature was deprecated after v3.7.0-13.0.pre.',
)
Tolerance get tolerance {
return toleranceFor(FixedScrollMetrics(
minScrollExtent: null,
maxScrollExtent: null,
pixels: null,
viewportDimension: null,
axisDirection: AxisDirection.down,
devicePixelRatio: WidgetsBinding.instance.window.devicePixelRatio,
));
}

/// The tolerance to use for ballistic simulations.
Tolerance get tolerance => parent?.tolerance ?? _kDefaultTolerance;
Tolerance toleranceFor(ScrollMetrics metrics) {
return parent?.toleranceFor(metrics) ?? Tolerance(
velocity: 1.0 / (0.050 * metrics.devicePixelRatio), // logical pixels per second
distance: 1.0 / metrics.devicePixelRatio, // logical pixels
);
}

/// The minimum distance an input pointer drag must have moved to
/// to be considered a scroll fling gesture.
Expand Down Expand Up @@ -695,7 +709,7 @@ class BouncingScrollPhysics extends ScrollPhysics {

@override
Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
final Tolerance tolerance = this.tolerance;
final Tolerance tolerance = toleranceFor(position);
if (velocity.abs() >= tolerance.velocity || position.outOfRange) {
double constantDeceleration;
switch (decelerationRate) {
Expand Down Expand Up @@ -839,7 +853,7 @@ class ClampingScrollPhysics extends ScrollPhysics {

@override
Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
final Tolerance tolerance = this.tolerance;
final Tolerance tolerance = toleranceFor(position);
if (position.outOfRange) {
double? end;
if (position.pixels > position.maxScrollExtent) {
Expand Down
5 changes: 5 additions & 0 deletions packages/flutter/lib/src/widgets/scroll_position.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import 'package:flutter/scheduler.dart';

import 'basic.dart';
import 'framework.dart';
import 'media_query.dart';
import 'notification_listener.dart';
import 'page_storage.dart';
import 'scroll_activity.dart';
import 'scroll_context.dart';
import 'scroll_metrics.dart';
import 'scroll_notification.dart';
import 'scroll_physics.dart';
import 'view.dart';

export 'scroll_activity.dart' show ScrollHoldController;

Expand Down Expand Up @@ -242,6 +244,9 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
isScrollingNotifier.value = activity!.isScrolling;
}

@override
double get devicePixelRatio => MediaQuery.maybeDevicePixelRatioOf(context.storageContext) ?? View.of(context.storageContext).devicePixelRatio;

/// Update the scroll position ([pixels]) to a given pixel value.
///
/// This should only be called by the current [ScrollActivity], either during
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
required Duration duration,
required Curve curve,
}) {
if (nearEqual(to, pixels, physics.tolerance.distance)) {
if (nearEqual(to, pixels, physics.toleranceFor(this).distance)) {
// Skip the animation, go straight to the position as we are already close.
jumpTo(to);
return Future<void>.value();
Expand Down
6 changes: 6 additions & 0 deletions packages/flutter/lib/src/widgets/scrollable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,12 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
}

void _handleDragCancel() {
if (_gestureDetectorKey.currentContext == null) {
// The cancel was caused by the GestureDetector getting disposed, which
// means we will get disposed momentarily as well and shouldn't do
// any work.
return;
}
// _hold might be null if the drag started.
// _drag might be null if the drag activity ended and called _disposeDrag.
assert(_hold == null || _drag == null);
Expand Down
1 change: 1 addition & 0 deletions packages/flutter/test/material/scrollbar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ void main() {
pixels: 0.0,
viewportDimension: 100.0,
axisDirection: AxisDirection.down,
devicePixelRatio: tester.binding.window.devicePixelRatio,
);
scrollPainter!.update(metrics, AxisDirection.down);

Expand Down
Loading

0 comments on commit 999356b

Please sign in to comment.