Unverified Commit ce4d635a authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Fix visual overflow when overscrolling RenderShrinkWrappingViewport (#91620)

parent 42eb9032
...@@ -1919,7 +1919,10 @@ class RenderShrinkWrappingViewport extends RenderViewportBase<SliverLogicalConta ...@@ -1919,7 +1919,10 @@ class RenderShrinkWrappingViewport extends RenderViewportBase<SliverLogicalConta
assert(correctedOffset.isFinite); assert(correctedOffset.isFinite);
_maxScrollExtent = 0.0; _maxScrollExtent = 0.0;
_shrinkWrapExtent = 0.0; _shrinkWrapExtent = 0.0;
_hasVisualOverflow = false; // Since the viewport is shrinkwrapped, we know that any negative overscroll
// into the potentially infinite mainAxisExtent will overflow the end of
// the viewport.
_hasVisualOverflow = correctedOffset < 0.0;
switch (cacheExtentStyle) { switch (cacheExtentStyle) {
case CacheExtentStyle.pixel: case CacheExtentStyle.pixel:
_calculatedCacheExtent = cacheExtent; _calculatedCacheExtent = cacheExtent;
...@@ -1928,6 +1931,7 @@ class RenderShrinkWrappingViewport extends RenderViewportBase<SliverLogicalConta ...@@ -1928,6 +1931,7 @@ class RenderShrinkWrappingViewport extends RenderViewportBase<SliverLogicalConta
_calculatedCacheExtent = mainAxisExtent * _cacheExtent; _calculatedCacheExtent = mainAxisExtent * _cacheExtent;
break; break;
} }
return layoutChildSequence( return layoutChildSequence(
child: firstChild, child: firstChild,
scrollOffset: math.max(0.0, correctedOffset), scrollOffset: math.max(0.0, correctedOffset),
......
...@@ -1846,7 +1846,8 @@ void main() { ...@@ -1846,7 +1846,8 @@ void main() {
}); });
}); });
Widget _buildShrinkWrap({ group('Overscrolling RenderShrinkWrappingViewport', () {
Widget _buildSimpleShrinkWrap({
ScrollController? controller, ScrollController? controller,
Axis scrollDirection = Axis.vertical, Axis scrollDirection = Axis.vertical,
ScrollPhysics? physics, ScrollPhysics? physics,
...@@ -1868,19 +1869,27 @@ void main() { ...@@ -1868,19 +1869,27 @@ void main() {
); );
} }
testWidgets('Constrained Shrinkwrapping viewport will not overflow on overscroll', (WidgetTester tester) async { Widget _buildClippingShrinkWrap(
// Regression test for https://github.com/flutter/flutter/issues/89717 ScrollController controller, {
final ScrollController controller = ScrollController(); bool constrain = false,
await tester.pumpWidget( }) {
Directionality( return Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: MediaQuery( child: MediaQuery(
data: const MediaQueryData(), data: const MediaQueryData(),
child: Container(
color: const Color(0xFF000000),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Container(height: 100, color: const Color(0x00000000)), // Translucent boxes above and below the shrinkwrapped viewport
// make it easily discernible if the viewport is not being
// clipped properly.
Opacity(
opacity: 0.5,
child: Container(height: 100, color: const Color(0xFF00B0FF)),
),
Container( Container(
height: 150, height: constrain ? 150 : null,
color: const Color(0xFFF44336), color: const Color(0xFFF44336),
child: ListView.builder( child: ListView.builder(
controller: controller, controller: controller,
...@@ -1890,37 +1899,77 @@ void main() { ...@@ -1890,37 +1899,77 @@ void main() {
itemCount: 10, itemCount: 10,
), ),
), ),
Container(height: 100, color: const Color(0x00000000)), Opacity(
opacity: 0.5,
child: Container(height: 100, color: const Color(0xFF00B0FF)),
),
], ],
), ),
), ),
) ),
);
}
testWidgets('constrained viewport correctly clips overflow', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/89717
final ScrollController controller = ScrollController();
await tester.pumpWidget(
_buildClippingShrinkWrap(controller, constrain: true)
); );
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(tester.getTopLeft(find.text('Item 0')).dy, 100.0); expect(tester.getTopLeft(find.text('Item 0')).dy, 100.0);
expect(tester.getTopLeft(find.text('Item 9')).dy, 226.0);
// Overscroll // Overscroll
final TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.text('Item 0'))); final TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.text('Item 0')));
await overscrollGesture.moveBy(const Offset(0, 25)); await overscrollGesture.moveBy(const Offset(0, 100));
await tester.pump(); await tester.pump();
expect(controller.offset, -25.0); expect(controller.offset, -100.0);
expect(tester.getTopLeft(find.text('Item 0')).dy, 125.0); expect(tester.getTopLeft(find.text('Item 0')).dy, 200.0);
await expectLater( await expectLater(
find.byType(Directionality), find.byType(Directionality),
matchesGoldenFile('shrinkwrapped_overscroll.png'), matchesGoldenFile('shrinkwrap_clipped_constrained_overscroll.png'),
); );
await overscrollGesture.up(); await overscrollGesture.up();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(tester.getTopLeft(find.text('Item 0')).dy, 100.0); expect(tester.getTopLeft(find.text('Item 0')).dy, 100.0);
expect(tester.getTopLeft(find.text('Item 9')).dy, 226.0);
}); });
testWidgets('Shrinkwrap allows overscrolling on default platforms - vertical', (WidgetTester tester) async { testWidgets('correctly clips overflow without constraints', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/89717
final ScrollController controller = ScrollController();
await tester.pumpWidget(
_buildClippingShrinkWrap(controller)
);
expect(controller.offset, 0.0);
expect(tester.getTopLeft(find.text('Item 0')).dy, 100.0);
expect(tester.getTopLeft(find.text('Item 9')).dy, 226.0);
// Overscroll
final TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.text('Item 0')));
await overscrollGesture.moveBy(const Offset(0, 100));
await tester.pump();
expect(controller.offset, -100.0);
expect(tester.getTopLeft(find.text('Item 0')).dy, 200.0);
await expectLater(
find.byType(Directionality),
matchesGoldenFile('shrinkwrap_clipped_overscroll.png'),
);
await overscrollGesture.up();
await tester.pumpAndSettle();
expect(controller.offset, 0.0);
expect(tester.getTopLeft(find.text('Item 0')).dy, 100.0);
expect(tester.getTopLeft(find.text('Item 9')).dy, 226.0);
});
testWidgets('allows overscrolling on default platforms - vertical', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/10949 // Regression test for https://github.com/flutter/flutter/issues/10949
// Scrollables should overscroll by default on iOS and macOS // Scrollables should overscroll by default on iOS and macOS
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
await tester.pumpWidget( await tester.pumpWidget(
_buildShrinkWrap(controller: controller), _buildSimpleShrinkWrap(controller: controller),
); );
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0); expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0);
...@@ -1954,12 +2003,12 @@ void main() { ...@@ -1954,12 +2003,12 @@ void main() {
expect(tester.getBottomLeft(find.text('Item 19')).dy, 600.0); expect(tester.getBottomLeft(find.text('Item 19')).dy, 600.0);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Shrinkwrap allows overscrolling on default platforms - horizontal', (WidgetTester tester) async { testWidgets('allows overscrolling on default platforms - horizontal', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/10949 // Regression test for https://github.com/flutter/flutter/issues/10949
// Scrollables should overscroll by default on iOS and macOS // Scrollables should overscroll by default on iOS and macOS
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
await tester.pumpWidget( await tester.pumpWidget(
_buildShrinkWrap(controller: controller, scrollDirection: Axis.horizontal), _buildSimpleShrinkWrap(controller: controller, scrollDirection: Axis.horizontal),
); );
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(tester.getTopLeft(find.text('Item 0')).dx, 0.0); expect(tester.getTopLeft(find.text('Item 0')).dx, 0.0);
...@@ -1993,12 +2042,12 @@ void main() { ...@@ -1993,12 +2042,12 @@ void main() {
expect(tester.getTopRight(find.text('Item 19')).dx, 800.0); expect(tester.getTopRight(find.text('Item 19')).dx, 800.0);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Shrinkwrap allows overscrolling per physics - vertical', (WidgetTester tester) async { testWidgets('allows overscrolling per physics - vertical', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/10949 // Regression test for https://github.com/flutter/flutter/issues/10949
// Scrollables should overscroll when the scroll physics allow // Scrollables should overscroll when the scroll physics allow
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
await tester.pumpWidget( await tester.pumpWidget(
_buildShrinkWrap(controller: controller, physics: const BouncingScrollPhysics()), _buildSimpleShrinkWrap(controller: controller, physics: const BouncingScrollPhysics()),
); );
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0); expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0);
...@@ -2032,12 +2081,12 @@ void main() { ...@@ -2032,12 +2081,12 @@ void main() {
expect(tester.getBottomLeft(find.text('Item 19')).dy, 600.0); expect(tester.getBottomLeft(find.text('Item 19')).dy, 600.0);
}); });
testWidgets('Shrinkwrap allows overscrolling per physics - horizontal', (WidgetTester tester) async { testWidgets('allows overscrolling per physics - horizontal', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/10949 // Regression test for https://github.com/flutter/flutter/issues/10949
// Scrollables should overscroll when the scroll physics allow // Scrollables should overscroll when the scroll physics allow
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
await tester.pumpWidget( await tester.pumpWidget(
_buildShrinkWrap( _buildSimpleShrinkWrap(
controller: controller, controller: controller,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
...@@ -2074,6 +2123,7 @@ void main() { ...@@ -2074,6 +2123,7 @@ void main() {
expect(controller.offset, maxExtent); expect(controller.offset, maxExtent);
expect(tester.getTopRight(find.text('Item 19')).dx, 800.0); expect(tester.getTopRight(find.text('Item 19')).dx, 800.0);
}); });
});
testWidgets('Handles infinite constraints when TargetPlatform is iOS or macOS', (WidgetTester tester) async { testWidgets('Handles infinite constraints when TargetPlatform is iOS or macOS', (WidgetTester tester) async {
// regression test for https://github.com/flutter/flutter/issues/45866 // regression test for https://github.com/flutter/flutter/issues/45866
......
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