Reland "Remove single-view assumption from ScrollPhysics (#117503)" (#117916)

This reverts commit c956121ac0.
This commit is contained in:
Michael Goderbauer 2023-01-03 13:34:08 -08:00 committed by GitHub
parent 084be5e6db
commit fdc25a1700
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 102 additions and 41 deletions

View file

@ -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
@ -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
@ -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;

View file

@ -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);
},
);
}
@ -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
@ -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),
);
}
@ -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) {

View file

@ -316,6 +316,7 @@ class FixedExtentMetrics extends FixedScrollMetrics {
required super.viewportDimension,
required super.axisDirection,
required this.itemIndex,
required super.devicePixelRatio,
});
@override
@ -326,6 +327,7 @@ class FixedExtentMetrics extends FixedScrollMetrics {
double? viewportDimension,
AxisDirection? axisDirection,
int? itemIndex,
double? devicePixelRatio,
}) {
return FixedExtentMetrics(
minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
@ -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,
);
}
@ -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),
@ -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,
);
}
}
@ -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;
}
@ -519,7 +524,7 @@ class FixedExtentScrollPhysics extends ScrollPhysics {
metrics.pixels,
settlingPixels,
velocity,
tolerance: tolerance,
tolerance: toleranceFor(position),
);
}
@ -530,7 +535,7 @@ class FixedExtentScrollPhysics extends ScrollPhysics {
metrics.pixels,
settlingPixels,
velocity,
tolerance.velocity * velocity.sign,
toleranceFor(position).velocity * velocity.sign,
);
}
}

View file

@ -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,
@ -538,6 +539,7 @@ class _NestedScrollMetrics extends FixedScrollMetrics {
double? pixels,
double? viewportDimension,
AxisDirection? axisDirection,
double? devicePixelRatio,
double? minRange,
double? maxRange,
double? correctionOffset,
@ -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,
@ -815,6 +818,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
minRange: minRange,
maxRange: maxRange,
correctionOffset: correctionOffset,
devicePixelRatio: _outerPosition!.devicePixelRatio,
);
}

View file

@ -273,6 +273,7 @@ class PageMetrics extends FixedScrollMetrics {
required super.viewportDimension,
required super.axisDirection,
required this.viewportFraction,
required super.devicePixelRatio,
});
@override
@ -283,6 +284,7 @@ class PageMetrics extends FixedScrollMetrics {
double? viewportDimension,
AxisDirection? axisDirection,
double? viewportFraction,
double? devicePixelRatio,
}) {
return PageMetrics(
minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
@ -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,
);
}
@ -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),
@ -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,
);
}
}
@ -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);

View file

@ -46,6 +46,7 @@ mixin ScrollMetrics {
double? pixels,
double? viewportDimension,
AxisDirection? axisDirection,
double? devicePixelRatio,
}) {
return FixedScrollMetrics(
minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
@ -53,6 +54,7 @@ mixin ScrollMetrics {
pixels: pixels ?? (hasPixels ? this.pixels : null),
viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
axisDirection: axisDirection ?? this.axisDirection,
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
);
}
@ -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.
@ -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,
@ -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)})';

View file

@ -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;
@ -383,16 +384,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.
@ -696,7 +710,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) {
@ -840,7 +854,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) {

View file

@ -12,6 +12,7 @@ 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';
@ -19,6 +20,7 @@ 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;
@ -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

View file

@ -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();

View file

@ -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);

View file

@ -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);

View file

@ -1048,6 +1048,7 @@ void main() {
viewportDimension: 25.0,
axisDirection: AxisDirection.right,
viewportFraction: 1.0,
devicePixelRatio: tester.binding.window.devicePixelRatio,
);
expect(page.page, 6);
final PageMetrics page2 = page.copyWith(

View file

@ -107,6 +107,7 @@ void main() {
pixels: 20.0,
viewportDimension: 500.0,
axisDirection: AxisDirection.down,
devicePixelRatio: 3.0,
);
const BouncingScrollPhysics bounce = BouncingScrollPhysics();
@ -134,6 +135,7 @@ void main() {
pixels: -20.0,
viewportDimension: 100.0,
axisDirection: AxisDirection.down,
devicePixelRatio: 3.0,
);
final ScrollMetrics moreOverscrolledPosition = FixedScrollMetrics(
@ -142,6 +144,7 @@ void main() {
pixels: -40.0,
viewportDimension: 100.0,
axisDirection: AxisDirection.down,
devicePixelRatio: 3.0,
);
final double lessOverscrollApplied =
@ -170,6 +173,7 @@ void main() {
pixels: -20.0,
viewportDimension: 100.0,
axisDirection: AxisDirection.down,
devicePixelRatio: 3.0,
);
final double easingApplied =
@ -186,6 +190,7 @@ void main() {
pixels: 300.0,
viewportDimension: 100.0,
axisDirection: AxisDirection.down,
devicePixelRatio: 3.0,
);
expect(
@ -205,6 +210,7 @@ void main() {
pixels: -20.0,
viewportDimension: 100.0,
axisDirection: AxisDirection.down,
devicePixelRatio: 3.0,
);
final double easingApplied =
@ -222,6 +228,7 @@ void main() {
pixels: -20.0,
viewportDimension: 100.0,
axisDirection: AxisDirection.down,
devicePixelRatio: 3.0,
);
final ScrollMetrics bigListOverscrolledPosition = FixedScrollMetrics(
@ -230,6 +237,7 @@ void main() {
pixels: -20.0,
viewportDimension: 100.0,
axisDirection: AxisDirection.down,
devicePixelRatio: 3.0,
);
final double smallListOverscrollApplied =
@ -254,6 +262,7 @@ void main() {
maxScrollExtent: 1000,
viewportDimension: 0,
axisDirection: AxisDirection.down,
devicePixelRatio: 3.0,
);
expect(position.pixels, pixels);
late FlutterError error;

View file

@ -41,7 +41,7 @@ void main() {
expectNoAnimation();
final double halfTolerance = controller.position.physics.tolerance.distance / 2;
final double halfTolerance = controller.position.physics.toleranceFor(controller.position).distance / 2;
expect(halfTolerance, isNonZero);
final double targetPosition = controller.position.pixels + halfTolerance;
controller.position.animateTo(targetPosition, duration: const Duration(seconds: 10), curve: Curves.linear);
@ -64,7 +64,7 @@ void main() {
expectNoAnimation();
final double doubleTolerance = controller.position.physics.tolerance.distance * 2;
final double doubleTolerance = controller.position.physics.toleranceFor(controller.position).distance * 2;
expect(doubleTolerance, isNonZero);
final double targetPosition = controller.position.pixels + doubleTolerance;
controller.position.animateTo(targetPosition, duration: const Duration(seconds: 10), curve: Curves.linear);

View file

@ -83,6 +83,7 @@ void main() {
pixels: 0,
viewportDimension: 100,
axisDirection: AxisDirection.down,
devicePixelRatio: 3.0,
);
test(

View file

@ -53,7 +53,7 @@ class TestScrollPhysics extends ClampingScrollPhysics {
}
@override
Tolerance get tolerance => const Tolerance(velocity: 20.0, distance: 1.0);
Tolerance toleranceFor(ScrollMetrics metrics) => const Tolerance(velocity: 20.0, distance: 1.0);
}
void main() {