v1.12.13+hotfix.1 cherry-picks (#45962)
flutter/engine@c1e322b Dart cherry-picks for 1.12.13
37f9c54 Use RenderSliverPadding to inset SliverFillViewport
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 7ddc943..7ad3d1a 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-b6b54fd60631a3828c2e2c9b079b5d1d2d8c8c37
+c1e322b685a81c11c16bddd22282925b7d0272e8
diff --git a/packages/flutter/lib/src/rendering/sliver_fill.dart b/packages/flutter/lib/src/rendering/sliver_fill.dart
index fefafe3..5d99b35 100644
--- a/packages/flutter/lib/src/rendering/sliver_fill.dart
+++ b/packages/flutter/lib/src/rendering/sliver_fill.dart
@@ -57,41 +57,6 @@
_viewportFraction = value;
markNeedsLayout();
}
-
- double get _padding => (1.0 - viewportFraction) * constraints.viewportMainAxisExtent * 0.5;
-
- @override
- double indexToLayoutOffset(double itemExtent, int index) {
- return _padding + super.indexToLayoutOffset(itemExtent, index);
- }
-
- @override
- int getMinChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
- return super.getMinChildIndexForScrollOffset(math.max(scrollOffset - _padding, 0.0), itemExtent);
- }
-
- @override
- int getMaxChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
- return super.getMaxChildIndexForScrollOffset(math.max(scrollOffset - _padding, 0.0), itemExtent);
- }
-
- @override
- double estimateMaxScrollOffset(
- SliverConstraints constraints, {
- int firstIndex,
- int lastIndex,
- double leadingScrollOffset,
- double trailingScrollOffset,
- }) {
- final double padding = _padding;
- return childManager.estimateMaxScrollOffset(
- constraints,
- firstIndex: firstIndex,
- lastIndex: lastIndex,
- leadingScrollOffset: leadingScrollOffset - padding,
- trailingScrollOffset: trailingScrollOffset - padding,
- ) + padding + padding;
- }
}
/// A sliver that contains a single box child that fills the remaining space in
diff --git a/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart b/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart
index 5a607fb..7e6b2d1 100644
--- a/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart
+++ b/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart
@@ -195,7 +195,7 @@
if (firstChild == null) {
if (!addInitialChild(index: firstIndex, layoutOffset: indexToLayoutOffset(itemExtent, firstIndex))) {
// There are either no children, or we are past the end of all our children.
- // If it is the later, we will need to find the first available child.
+ // If it is the latter, we will need to find the first available child.
double max;
if (childManager.childCount != null) {
max = computeMaxScrollOffset(constraints, itemExtent);
diff --git a/packages/flutter/lib/src/rendering/sliver_padding.dart b/packages/flutter/lib/src/rendering/sliver_padding.dart
index 01cf8a2..56b53cb 100644
--- a/packages/flutter/lib/src/rendering/sliver_padding.dart
+++ b/packages/flutter/lib/src/rendering/sliver_padding.dart
@@ -12,73 +12,27 @@
import 'object.dart';
import 'sliver.dart';
-/// Inset a [RenderSliver], applying padding on each side.
+/// Insets a [RenderSliver] by applying [resolvedPadding] on each side.
///
-/// A [RenderSliverPadding] object wraps the [SliverGeometry.layoutExtent] of
-/// its child. Any incoming [SliverConstraints.overlap] is ignored and not
+/// A [RenderSliverEdgeInsetsPadding] subclass wraps the [SliverGeometry.layoutExtent]
+/// of its child. Any incoming [SliverConstraints.overlap] is ignored and not
/// passed on to the child.
///
+/// {@template flutter.rendering.sliverPadding.limitation}
/// Applying padding to anything but the most mundane sliver is likely to have
-/// undesired effects. For example, wrapping a
-/// [RenderSliverPinnedPersistentHeader] will cause the app bar to overlap
-/// earlier slivers (contrary to the normal behavior of pinned app bars), and
-/// while the app bar is pinned, the padding will scroll away.
-class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
- /// Creates a render object that insets its child in a viewport.
- ///
- /// The [padding] argument must not be null and must have non-negative insets.
- RenderSliverPadding({
- @required EdgeInsetsGeometry padding,
- TextDirection textDirection,
- RenderSliver child,
- }) : assert(padding != null),
- assert(padding.isNonNegative),
- _padding = padding,
- _textDirection = textDirection {
- this.child = child;
- }
-
- EdgeInsets _resolvedPadding;
-
- void _resolve() {
- if (_resolvedPadding != null)
- return;
- _resolvedPadding = padding.resolve(textDirection);
- assert(_resolvedPadding.isNonNegative);
- }
-
- void _markNeedResolution() {
- _resolvedPadding = null;
- markNeedsLayout();
- }
-
+/// undesired effects. For example, wrapping a [RenderSliverPinnedPersistentHeader]
+/// will cause the app bar to overlap earlier slivers (contrary to the normal
+/// behavior of pinned app bars), and while the app bar is pinned, the padding
+/// will scroll away.
+/// {@endtemplate}
+abstract class RenderSliverEdgeInsetsPadding extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
/// The amount to pad the child in each dimension.
///
- /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
- /// must not be null.
- EdgeInsetsGeometry get padding => _padding;
- EdgeInsetsGeometry _padding;
- set padding(EdgeInsetsGeometry value) {
- assert(value != null);
- assert(padding.isNonNegative);
- if (_padding == value)
- return;
- _padding = value;
- _markNeedResolution();
- }
-
- /// The text direction with which to resolve [padding].
+ /// The offsets are specified in terms of visual edges, left, top, right, and
+ /// bottom. These values are not affected by the [TextDirection].
///
- /// This may be changed to null, but only after the [padding] has been changed
- /// to a value that does not depend on the direction.
- TextDirection get textDirection => _textDirection;
- TextDirection _textDirection;
- set textDirection(TextDirection value) {
- if (_textDirection == value)
- return;
- _textDirection = value;
- _markNeedResolution();
- }
+ /// Must not be null or contain negative values when [performLayout] is called.
+ EdgeInsets get resolvedPadding;
/// The padding in the scroll direction on the side nearest the 0.0 scroll direction.
///
@@ -88,16 +42,16 @@
assert(constraints != null);
assert(constraints.axisDirection != null);
assert(constraints.growthDirection != null);
- assert(_resolvedPadding != null);
+ assert(resolvedPadding != null);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up:
- return _resolvedPadding.bottom;
+ return resolvedPadding.bottom;
case AxisDirection.right:
- return _resolvedPadding.left;
+ return resolvedPadding.left;
case AxisDirection.down:
- return _resolvedPadding.top;
+ return resolvedPadding.top;
case AxisDirection.left:
- return _resolvedPadding.right;
+ return resolvedPadding.right;
}
return null;
}
@@ -110,16 +64,16 @@
assert(constraints != null);
assert(constraints.axisDirection != null);
assert(constraints.growthDirection != null);
- assert(_resolvedPadding != null);
+ assert(resolvedPadding != null);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up:
- return _resolvedPadding.top;
+ return resolvedPadding.top;
case AxisDirection.right:
- return _resolvedPadding.right;
+ return resolvedPadding.right;
case AxisDirection.down:
- return _resolvedPadding.bottom;
+ return resolvedPadding.bottom;
case AxisDirection.left:
- return _resolvedPadding.left;
+ return resolvedPadding.left;
}
return null;
}
@@ -133,8 +87,8 @@
double get mainAxisPadding {
assert(constraints != null);
assert(constraints.axis != null);
- assert(_resolvedPadding != null);
- return _resolvedPadding.along(constraints.axis);
+ assert(resolvedPadding != null);
+ return resolvedPadding.along(constraints.axis);
}
/// The total padding in the cross-axis direction. (In other words, for a
@@ -146,12 +100,12 @@
double get crossAxisPadding {
assert(constraints != null);
assert(constraints.axis != null);
- assert(_resolvedPadding != null);
+ assert(resolvedPadding != null);
switch (constraints.axis) {
case Axis.horizontal:
- return _resolvedPadding.vertical;
+ return resolvedPadding.vertical;
case Axis.vertical:
- return _resolvedPadding.horizontal;
+ return resolvedPadding.horizontal;
}
return null;
}
@@ -164,8 +118,7 @@
@override
void performLayout() {
- _resolve();
- assert(_resolvedPadding != null);
+ assert(resolvedPadding != null);
final double beforePadding = this.beforePadding;
final double afterPadding = this.afterPadding;
final double mainAxisPadding = this.mainAxisPadding;
@@ -240,16 +193,16 @@
assert(constraints.growthDirection != null);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up:
- childParentData.paintOffset = Offset(_resolvedPadding.left, calculatePaintOffset(constraints, from: _resolvedPadding.bottom + childLayoutGeometry.scrollExtent, to: _resolvedPadding.bottom + childLayoutGeometry.scrollExtent + _resolvedPadding.top));
+ childParentData.paintOffset = Offset(resolvedPadding.left, calculatePaintOffset(constraints, from: resolvedPadding.bottom + childLayoutGeometry.scrollExtent, to: resolvedPadding.bottom + childLayoutGeometry.scrollExtent + resolvedPadding.top));
break;
case AxisDirection.right:
- childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: 0.0, to: _resolvedPadding.left), _resolvedPadding.top);
+ childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: 0.0, to: resolvedPadding.left), resolvedPadding.top);
break;
case AxisDirection.down:
- childParentData.paintOffset = Offset(_resolvedPadding.left, calculatePaintOffset(constraints, from: 0.0, to: _resolvedPadding.top));
+ childParentData.paintOffset = Offset(resolvedPadding.left, calculatePaintOffset(constraints, from: 0.0, to: resolvedPadding.top));
break;
case AxisDirection.left:
- childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: _resolvedPadding.right + childLayoutGeometry.scrollExtent, to: _resolvedPadding.right + childLayoutGeometry.scrollExtent + _resolvedPadding.left), _resolvedPadding.top);
+ childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: resolvedPadding.right + childLayoutGeometry.scrollExtent, to: resolvedPadding.right + childLayoutGeometry.scrollExtent + resolvedPadding.left), resolvedPadding.top);
break;
}
assert(childParentData.paintOffset != null);
@@ -289,14 +242,14 @@
assert(constraints != null);
assert(constraints.axisDirection != null);
assert(constraints.growthDirection != null);
- assert(_resolvedPadding != null);
+ assert(resolvedPadding != null);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up:
case AxisDirection.down:
- return _resolvedPadding.left;
+ return resolvedPadding.left;
case AxisDirection.left:
case AxisDirection.right:
- return _resolvedPadding.top;
+ return resolvedPadding.top;
}
return null;
}
@@ -346,6 +299,79 @@
return true;
}());
}
+}
+
+/// Insets a [RenderSliver], applying padding on each side.
+///
+/// A [RenderSliverPadding] object wraps the [SliverGeometry.layoutExtent] of
+/// its child. Any incoming [SliverConstraints.overlap] is ignored and not
+/// passed on to the child.
+///
+/// {@macro flutter.rendering.sliverPadding.limitation}
+class RenderSliverPadding extends RenderSliverEdgeInsetsPadding {
+ /// Creates a render object that insets its child in a viewport.
+ ///
+ /// The [padding] argument must not be null and must have non-negative insets.
+ RenderSliverPadding({
+ @required EdgeInsetsGeometry padding,
+ TextDirection textDirection,
+ RenderSliver child,
+ }) : assert(padding != null),
+ assert(padding.isNonNegative),
+ _padding = padding,
+ _textDirection = textDirection {
+ this.child = child;
+ }
+
+ @override
+ EdgeInsets get resolvedPadding => _resolvedPadding;
+ EdgeInsets _resolvedPadding;
+
+ void _resolve() {
+ if (resolvedPadding != null)
+ return;
+ _resolvedPadding = padding.resolve(textDirection);
+ assert(resolvedPadding.isNonNegative);
+ }
+
+ void _markNeedsResolution() {
+ _resolvedPadding = null;
+ markNeedsLayout();
+ }
+
+ /// The amount to pad the child in each dimension.
+ ///
+ /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
+ /// must not be null.
+ EdgeInsetsGeometry get padding => _padding;
+ EdgeInsetsGeometry _padding;
+ set padding(EdgeInsetsGeometry value) {
+ assert(value != null);
+ assert(padding.isNonNegative);
+ if (_padding == value)
+ return;
+ _padding = value;
+ _markNeedsResolution();
+ }
+
+ /// The text direction with which to resolve [padding].
+ ///
+ /// This may be changed to null, but only after the [padding] has been changed
+ /// to a value that does not depend on the direction.
+ TextDirection get textDirection => _textDirection;
+ TextDirection _textDirection;
+ set textDirection(TextDirection value) {
+ if (_textDirection == value)
+ return;
+ _textDirection = value;
+ _markNeedsResolution();
+ }
+
+ @override
+ void performLayout() {
+ _resolve();
+ super.performLayout();
+ }
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart
index b08d0ec..fd6c078 100644
--- a/packages/flutter/lib/src/widgets/page_view.dart
+++ b/packages/flutter/lib/src/widgets/page_view.dart
@@ -345,8 +345,16 @@
forcePixels(getPixelsFromPage(oldPage));
}
+ // The amount of offset that will be added to [minScrollExtent] and subtracted
+ // from [maxScrollExtent], such that every page will properly snap to the center
+ // of the viewport when viewportFraction is greater than 1.
+ //
+ // The value is 0 if viewportFraction is less than or equal to 1, larger than 0
+ // otherwise.
+ double get _initialPageOffset => math.max(0, viewportDimension * (viewportFraction - 1) / 2);
+
double getPageFromPixels(double pixels, double viewportDimension) {
- final double actual = math.max(0.0, pixels) / math.max(1.0, viewportDimension * viewportFraction);
+ final double actual = math.max(0.0, pixels - _initialPageOffset) / math.max(1.0, viewportDimension * viewportFraction);
final double round = actual.roundToDouble();
if ((actual - round).abs() < precisionErrorTolerance) {
return round;
@@ -355,7 +363,7 @@
}
double getPixelsFromPage(double page) {
- return page * viewportDimension * viewportFraction;
+ return page * viewportDimension * viewportFraction + _initialPageOffset;
}
@override
@@ -397,6 +405,15 @@
}
@override
+ bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
+ final double newMinScrollExtent = minScrollExtent + _initialPageOffset;
+ return super.applyContentDimensions(
+ newMinScrollExtent,
+ math.max(newMinScrollExtent, maxScrollExtent - _initialPageOffset),
+ );
+ }
+
+ @override
PageMetrics copyWith({
double minScrollExtent,
double maxScrollExtent,
diff --git a/packages/flutter/lib/src/widgets/sliver.dart b/packages/flutter/lib/src/widgets/sliver.dart
index 0cab703..d9f380e 100644
--- a/packages/flutter/lib/src/widgets/sliver.dart
+++ b/packages/flutter/lib/src/widgets/sliver.dart
@@ -725,6 +725,7 @@
}) : assert(delegate != null),
super(key: key);
+ /// {@template flutter.widgets.sliverMultiBoxAdaptor.delegate}
/// The delegate that provides the children for this widget.
///
/// The children are constructed lazily using this delegate to avoid creating
@@ -735,6 +736,7 @@
/// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], which are
/// commonly used subclasses of [SliverChildDelegate] that use a builder
/// callback and an explicit child list, respectively.
+ /// {@endtemplate}
final SliverChildDelegate delegate;
@override
@@ -1023,7 +1025,7 @@
}
}
-/// A sliver that contains a multiple box children that each fill the viewport.
+/// A sliver that contains multiple box children that each fills the viewport.
///
/// [SliverFillViewport] places its children in a linear array along the main
/// axis. Each child is sized to fill the viewport, both in the main and cross
@@ -1038,21 +1040,47 @@
/// the main axis extent of each item.
/// * [SliverList], which does not require its children to have the same
/// extent in the main axis.
-class SliverFillViewport extends SliverMultiBoxAdaptorWidget {
+class SliverFillViewport extends StatelessWidget {
/// Creates a sliver whose box children that each fill the viewport.
const SliverFillViewport({
Key key,
+ @required this.delegate,
+ this.viewportFraction = 1.0,
+ }) : assert(viewportFraction != null),
+ assert(viewportFraction > 0.0),
+ super(key: key);
+
+ /// The fraction of the viewport that each child should fill in the main axis.
+ ///
+ /// If this fraction is less than 1.0, more than one child will be visible at
+ /// once. If this fraction is greater than 1.0, each child will be larger than
+ /// the viewport in the main axis.
+ final double viewportFraction;
+
+ /// {@macro flutter.widgets.sliverMultiBoxAdaptor.delegate}
+ final SliverChildDelegate delegate;
+
+ @override
+ Widget build(BuildContext context) {
+ return _SliverFractionalPadding(
+ viewportFraction: (1 - viewportFraction).clamp(0, 1) / 2,
+ sliver: _SliverFillViewportRenderObjectWidget(
+ viewportFraction: viewportFraction,
+ delegate: delegate,
+ ),
+ );
+ }
+}
+
+class _SliverFillViewportRenderObjectWidget extends SliverMultiBoxAdaptorWidget {
+ const _SliverFillViewportRenderObjectWidget({
+ Key key,
@required SliverChildDelegate delegate,
this.viewportFraction = 1.0,
}) : assert(viewportFraction != null),
assert(viewportFraction > 0.0),
super(key: key, delegate: delegate);
- /// The fraction of the viewport that each child should fill in the main axis.
- ///
- /// If this fraction is less than 1.0, more than one child will be visible at
- /// once. If this fraction is greater than 1.0, each child will be larger than
- /// the viewport in the main axis.
final double viewportFraction;
@override
@@ -1067,6 +1095,77 @@
}
}
+class _SliverFractionalPadding extends SingleChildRenderObjectWidget {
+ const _SliverFractionalPadding({
+ this.viewportFraction = 0,
+ Widget sliver,
+ }) : assert(viewportFraction != null),
+ assert(viewportFraction >= 0),
+ assert(viewportFraction <= 0.5),
+ super(child: sliver);
+
+ final double viewportFraction;
+
+ @override
+ RenderObject createRenderObject(BuildContext context) => _RenderSliverFractionalPadding(viewportFraction: viewportFraction);
+
+ @override
+ void updateRenderObject(BuildContext context, _RenderSliverFractionalPadding renderObject) {
+ renderObject.viewportFraction = viewportFraction;
+ }
+}
+
+class _RenderSliverFractionalPadding extends RenderSliverEdgeInsetsPadding {
+ _RenderSliverFractionalPadding({
+ double viewportFraction = 0,
+ }) : assert(viewportFraction != null),
+ assert(viewportFraction <= 0.5),
+ assert(viewportFraction >= 0),
+ _viewportFraction = viewportFraction;
+
+ double get viewportFraction => _viewportFraction;
+ double _viewportFraction;
+ set viewportFraction(double newValue) {
+ assert(newValue != null);
+ if (_viewportFraction == newValue)
+ return;
+ _viewportFraction = newValue;
+ _markNeedsResolution();
+ }
+
+ @override
+ EdgeInsets get resolvedPadding => _resolvedPadding;
+ EdgeInsets _resolvedPadding;
+
+ void _markNeedsResolution() {
+ _resolvedPadding = null;
+ markNeedsLayout();
+ }
+
+ void _resolve() {
+ if (_resolvedPadding != null)
+ return;
+ assert(constraints.axis != null);
+ final double paddingValue = constraints.viewportMainAxisExtent * viewportFraction;
+ switch (constraints.axis) {
+ case Axis.horizontal:
+ _resolvedPadding = EdgeInsets.symmetric(horizontal: paddingValue);
+ break;
+ case Axis.vertical:
+ _resolvedPadding = EdgeInsets.symmetric(vertical: paddingValue);
+ break;
+ }
+
+ return;
+ }
+
+ @override
+ void performLayout() {
+ _resolve();
+ super.performLayout();
+ }
+}
+
/// An element that lazily builds children for a [SliverMultiBoxAdaptorWidget].
///
/// Implements [RenderSliverBoxChildManager], which lets this element manage
diff --git a/packages/flutter/test/widgets/page_view_test.dart b/packages/flutter/test/widgets/page_view_test.dart
index 94823e5..02b8d4e 100644
--- a/packages/flutter/test/widgets/page_view_test.dart
+++ b/packages/flutter/test/widgets/page_view_test.dart
@@ -532,8 +532,7 @@
});
testWidgets('PageView large viewportFraction', (WidgetTester tester) async {
- final PageController controller =
- PageController(viewportFraction: 5/4);
+ final PageController controller = PageController(viewportFraction: 5/4);
Widget build(PageController controller) {
return Directionality(
@@ -601,6 +600,89 @@
expect(tester.getTopLeft(find.text('Hawaii')), const Offset(-(4 - 1) * 800 / 2, 0));
});
+ testWidgets(
+ 'PageView large viewportFraction can scroll to the last page and snap',
+ (WidgetTester tester) async {
+ // Regression test for https://github.com/flutter/flutter/issues/45096.
+ final PageController controller = PageController(viewportFraction: 5/4);
+
+ Widget build(PageController controller) {
+ return Directionality(
+ textDirection: TextDirection.ltr,
+ child: PageView.builder(
+ controller: controller,
+ itemCount: 3,
+ itemBuilder: (BuildContext context, int index) {
+ return Container(
+ height: 200.0,
+ color: index % 2 == 0
+ ? const Color(0xFF0000FF)
+ : const Color(0xFF00FF00),
+ child: Text(index.toString()),
+ );
+ },
+ ),
+ );
+ }
+
+ await tester.pumpWidget(build(controller));
+
+ expect(tester.getCenter(find.text('0')), const Offset(400, 300));
+
+ controller.jumpToPage(2);
+ await tester.pump();
+ await tester.pumpAndSettle();
+
+ expect(tester.getCenter(find.text('2')), const Offset(400, 300));
+ });
+
+ testWidgets(
+ 'All visible pages are able to receive touch events',
+ (WidgetTester tester) async {
+ // Regression test for https://github.com/flutter/flutter/issues/23873.
+ final PageController controller = PageController(viewportFraction: 1/4, initialPage: 0);
+ int tappedIndex;
+
+ Widget build() {
+ return Directionality(
+ textDirection: TextDirection.ltr,
+ child: PageView.builder(
+ controller: controller,
+ itemCount: 20,
+ itemBuilder: (BuildContext context, int index) {
+ return GestureDetector(
+ onTap: () => tappedIndex = index,
+ child: SizedBox.expand(child: Text('$index')),
+ );
+ },
+ ),
+ );
+ }
+
+ Iterable<int> visiblePages = const <int> [0, 1, 2];
+ await tester.pumpWidget(build());
+
+ // The first 3 items should be visible and tappable.
+ for (int index in visiblePages) {
+ expect(find.text(index.toString()), findsOneWidget);
+ // The center of page 2's x-coordinate is 800, so we have to manually
+ // offset it a bit to make sure the tap lands within the screen.
+ final Offset center = tester.getCenter(find.text('$index')) - const Offset(3, 0);
+ await tester.tapAt(center);
+ expect(tappedIndex, index);
+ }
+
+ controller.jumpToPage(19);
+ await tester.pump();
+ // The last 3 items should be visible and tappable.
+ visiblePages = const <int> [17, 18, 19];
+ for (int index in visiblePages) {
+ expect(find.text('$index'), findsOneWidget);
+ await tester.tap(find.text('$index'));
+ expect(tappedIndex, index);
+ }
+ });
+
testWidgets('PageView does not report page changed on overscroll', (WidgetTester tester) async {
final PageController controller = PageController(
initialPage: kStates.length - 1,
diff --git a/packages/flutter/test/widgets/sliver_fill_viewport_test.dart b/packages/flutter/test/widgets/sliver_fill_viewport_test.dart
index 0a6683b..12778be 100644
--- a/packages/flutter/test/widgets/sliver_fill_viewport_test.dart
+++ b/packages/flutter/test/widgets/sliver_fill_viewport_test.dart
@@ -64,7 +64,7 @@
expect(
viewport.toStringDeep(minLevel: DiagnosticLevel.info),
equalsIgnoringHashCodes(
- 'RenderSliverFillViewport#00000 relayoutBoundary=up1\n'
+ '_RenderSliverFractionalPadding#00000 relayoutBoundary=up1\n'
' │ needs compositing\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
@@ -76,58 +76,71 @@
' │ geometry: SliverGeometry(scrollExtent: 12000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true,\n'
' │ cacheExtent: 850.0)\n'
- ' │ currently live children: 0 to 1\n'
' │\n'
- ' ├─child with index 0: RenderRepaintBoundary#00000\n'
- ' │ │ needs compositing\n'
- ' │ │ parentData: index=0; layoutOffset=0.0\n'
- ' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
- ' │ │ layer: OffsetLayer#00000\n'
- ' │ │ size: Size(800.0, 600.0)\n'
- ' │ │ metrics: 66.7% useful (1 bad vs 2 good)\n'
- ' │ │ diagnosis: insufficient data to draw conclusion (less than five\n'
- ' │ │ repaints)\n'
- ' │ │\n'
- ' │ └─child: RenderParagraph#00000\n'
- ' │ │ parentData: <none> (can use size)\n'
- ' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
- ' │ │ semantics node: SemanticsNode#2\n'
- ' │ │ size: Size(800.0, 600.0)\n'
- ' │ │ textAlign: start\n'
- ' │ │ textDirection: ltr\n'
- ' │ │ softWrap: wrapping at box width\n'
- ' │ │ overflow: clip\n'
- ' │ │ maxLines: unlimited\n'
- ' │ ╘═╦══ text ═══\n'
- ' │ ║ TextSpan:\n'
- ' │ ║ <all styles inherited>\n'
- ' │ ║ "0"\n'
- ' │ ╚═══════════\n'
- ' └─child with index 1: RenderRepaintBoundary#00000\n'
+ ' └─child: RenderSliverFillViewport#00000 relayoutBoundary=up2\n'
' │ needs compositing\n'
- ' │ parentData: index=1; layoutOffset=600.0\n'
- ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
- ' │ layer: OffsetLayer#00000 DETACHED\n'
- ' │ size: Size(800.0, 600.0)\n'
- ' │ metrics: 50.0% useful (1 bad vs 1 good)\n'
- ' │ diagnosis: insufficient data to draw conclusion (less than five\n'
- ' │ repaints)\n'
+ ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
+ ' │ constraints: SliverConstraints(AxisDirection.down,\n'
+ ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
+ ' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
+ ' │ crossAxisDirection: AxisDirection.right,\n'
+ ' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
+ ' │ cacheOrigin: 0.0 )\n'
+ ' │ geometry: SliverGeometry(scrollExtent: 12000.0, paintExtent:\n'
+ ' │ 600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true,\n'
+ ' │ cacheExtent: 850.0)\n'
+ ' │ currently live children: 0 to 1\n'
' │\n'
- ' └─child: RenderParagraph#00000\n'
- ' │ parentData: <none> (can use size)\n'
+ ' ├─child with index 0: RenderRepaintBoundary#00000\n'
+ ' │ │ needs compositing\n'
+ ' │ │ parentData: index=0; layoutOffset=0.0\n'
+ ' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
+ ' │ │ layer: OffsetLayer#00000\n'
+ ' │ │ size: Size(800.0, 600.0)\n'
+ ' │ │ metrics: 66.7% useful (1 bad vs 2 good)\n'
+ ' │ │ diagnosis: insufficient data to draw conclusion (less than five\n'
+ ' │ │ repaints)\n'
+ ' │ │\n'
+ ' │ └─child: RenderParagraph#00000\n'
+ ' │ │ parentData: <none> (can use size)\n'
+ ' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
+ ' │ │ semantics node: SemanticsNode#2\n'
+ ' │ │ size: Size(800.0, 600.0)\n'
+ ' │ │ textAlign: start\n'
+ ' │ │ textDirection: ltr\n'
+ ' │ │ softWrap: wrapping at box width\n'
+ ' │ │ overflow: clip\n'
+ ' │ │ maxLines: unlimited\n'
+ ' │ ╘═╦══ text ═══\n'
+ ' │ ║ TextSpan:\n'
+ ' │ ║ <all styles inherited>\n'
+ ' │ ║ "0"\n'
+ ' │ ╚═══════════\n'
+ ' └─child with index 1: RenderRepaintBoundary#00000\n'
+ ' │ needs compositing\n'
+ ' │ parentData: index=1; layoutOffset=600.0\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
- ' │ semantics node: SemanticsNode#3\n'
+ ' │ layer: OffsetLayer#00000 DETACHED\n'
' │ size: Size(800.0, 600.0)\n'
- ' │ textAlign: start\n'
- ' │ textDirection: ltr\n'
- ' │ softWrap: wrapping at box width\n'
- ' │ overflow: clip\n'
- ' │ maxLines: unlimited\n'
- ' ╘═╦══ text ═══\n'
- ' ║ TextSpan:\n'
- ' ║ <all styles inherited>\n'
- ' ║ "1"\n'
- ' ╚═══════════\n'
+ ' │ metrics: 50.0% useful (1 bad vs 1 good)\n'
+ ' │ diagnosis: insufficient data to draw conclusion (less than five\n'
+ ' │ repaints)\n'
+ ' │\n'
+ ' └─child: RenderParagraph#00000\n'
+ ' │ parentData: <none> (can use size)\n'
+ ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
+ ' │ semantics node: SemanticsNode#3\n'
+ ' │ size: Size(800.0, 600.0)\n'
+ ' │ textAlign: start\n'
+ ' │ textDirection: ltr\n'
+ ' │ softWrap: wrapping at box width\n'
+ ' │ overflow: clip\n'
+ ' │ maxLines: unlimited\n'
+ ' ╘═╦══ text ═══\n'
+ ' ║ TextSpan:\n'
+ ' ║ <all styles inherited>\n'
+ ' ║ "1"\n'
+ ' ╚═══════════\n'
''
),
);