Commit 473d75a6 authored by Jeff McGlynn's avatar Jeff McGlynn Committed by xster

PageView ballistics overshoot the page on some devices (#12884)

* PageView ballistics overshoot the page on some devices

On some devices, such as Cupertino “Plus”-sized devices, scrolling left on the first page of a PageView will overshoot the first page and land on the second page.

The issue is that applyContentDimensions incorrectly detects a content size change due to a floating point comparison on certain screen sizes (18257.400000000005 vs 18257.4)

To fix this, perform a nearEqual comparison in applyContentDimensions.

* Apply style changes to nearEqual for code review feedback.
parent b6fb4a8a
...@@ -4,9 +4,14 @@ ...@@ -4,9 +4,14 @@
/// Whether two doubles are within a given distance of each other. /// Whether two doubles are within a given distance of each other.
/// ///
/// The epsilon argument must be positive. /// The `epsilon` argument must be positive and not null.
/// The `a` and `b` arguments may be null. A null value is only considered
/// near-equal to another null value.
bool nearEqual(double a, double b, double epsilon) { bool nearEqual(double a, double b, double epsilon) {
assert(epsilon != null);
assert(epsilon >= 0.0); assert(epsilon >= 0.0);
if (a == null || b == null)
return a == b;
return (a > (b - epsilon)) && (a < (b + epsilon)); return (a > (b - epsilon)) && (a < (b + epsilon));
} }
......
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
...@@ -412,8 +413,8 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { ...@@ -412,8 +413,8 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
@override @override
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
if (_minScrollExtent != minScrollExtent || if (!nearEqual(_minScrollExtent, minScrollExtent, Tolerance.defaultTolerance.distance) ||
_maxScrollExtent != maxScrollExtent || !nearEqual(_maxScrollExtent, maxScrollExtent, Tolerance.defaultTolerance.distance) ||
_didChangeViewportDimension) { _didChangeViewportDimension) {
_minScrollExtent = minScrollExtent; _minScrollExtent = minScrollExtent;
_maxScrollExtent = maxScrollExtent; _maxScrollExtent = maxScrollExtent;
......
...@@ -13,4 +13,10 @@ void main() { ...@@ -13,4 +13,10 @@ void main() {
expect(nearEqual(5.0, 6.0, 0.5), isFalse); expect(nearEqual(5.0, 6.0, 0.5), isFalse);
expect(nearEqual(6.0, 5.0, 0.5), isFalse); expect(nearEqual(6.0, 5.0, 0.5), isFalse);
}); });
test('test_null', () {
expect(nearEqual(5.0, null, 2.0), isFalse);
expect(nearEqual(null, 5.0, 2.0), isFalse);
expect(nearEqual(null, null, 2.0), isTrue);
});
} }
...@@ -325,6 +325,60 @@ void main() { ...@@ -325,6 +325,60 @@ void main() {
expect(find.text('Alaska'), findsOneWidget); expect(find.text('Alaska'), findsOneWidget);
}); });
testWidgets('Bouncing scroll physics ballistics does not overshoot', (WidgetTester tester) async {
final List<int> log = <int>[];
final PageController controller = new PageController(viewportFraction: 0.9);
Widget build(PageController controller, {Size size}) {
final Widget pageView = new Directionality(
textDirection: TextDirection.ltr,
child: new PageView(
controller: controller,
onPageChanged: log.add,
physics: const BouncingScrollPhysics(),
children: kStates.map<Widget>((String state) => new Text(state)).toList(),
),
);
if (size != null) {
return new OverflowBox(
child: pageView,
minWidth: size.width,
minHeight: size.height,
maxWidth: size.width,
maxHeight: size.height,
);
} else {
return pageView;
}
}
await tester.pumpWidget(build(controller));
expect(log, isEmpty);
// Fling right to move to a non-existent page at the beginning of the
// PageView, and confirm that the PageView settles back on the first page.
await tester.fling(find.byType(PageView), const Offset(100.0, 0.0), 800.0);
await tester.pumpAndSettle();
expect(log, isEmpty);
expect(find.text('Alabama'), findsOneWidget);
expect(find.text('Alaska'), findsOneWidget);
expect(find.text('Arizona'), findsNothing);
// Try again with a Cupertino "Plus" device size.
await tester.pumpWidget(build(controller, size: const Size(414.0, 736.0)));
expect(log, isEmpty);
await tester.fling(find.byType(PageView), const Offset(100.0, 0.0), 800.0);
await tester.pumpAndSettle();
expect(log, isEmpty);
expect(find.text('Alabama'), findsOneWidget);
expect(find.text('Alaska'), findsOneWidget);
expect(find.text('Arizona'), findsNothing);
});
testWidgets('PageView viewportFraction', (WidgetTester tester) async { testWidgets('PageView viewportFraction', (WidgetTester tester) async {
PageController controller = new PageController(viewportFraction: 7/8); PageController controller = new PageController(viewportFraction: 7/8);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment