diff --git a/packages/flutter/lib/src/rendering/sliver.dart b/packages/flutter/lib/src/rendering/sliver.dart index 0be986e59b..a53d948164 100644 --- a/packages/flutter/lib/src/rendering/sliver.dart +++ b/packages/flutter/lib/src/rendering/sliver.dart @@ -1211,17 +1211,17 @@ abstract class RenderSliver extends RenderObject { } )); assert(() { - if (geometry.paintExtent > constraints.remainingPaintExtent) { + if (geometry.paintOrigin + geometry.paintExtent > constraints.remainingPaintExtent) { throw FlutterError.fromParts([ ErrorSummary('SliverGeometry has a paintOffset that exceeds the remainingPaintExtent from the constraints.'), describeForError('The render object whose geometry violates the constraints is the following'), ..._debugCompareFloats( 'remainingPaintExtent', constraints.remainingPaintExtent, - 'paintExtent', geometry.paintExtent, + 'paintOrigin + paintExtent', geometry.paintOrigin + geometry.paintExtent, ), ErrorDescription( - 'The paintExtent must cause the child sliver to paint within the viewport, and so ' - 'cannot exceed the remainingPaintExtent.', + 'The paintOrigin and paintExtent must cause the child sliver to paint ' + 'within the viewport, and so cannot exceed the remainingPaintExtent.', ), ]); } diff --git a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart index 71264ab1b2..1e236892d8 100644 --- a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart @@ -373,14 +373,15 @@ abstract class RenderSliverPinnedPersistentHeader extends RenderSliverPersistent final bool overlapsContent = constraints.overlap > 0.0; excludeFromSemanticsScrolling = overlapsContent || (constraints.scrollOffset > maxExtent - minExtent); layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent); - final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent) as double; + final double effectiveRemainingPaintExtent = math.max(0, constraints.remainingPaintExtent - constraints.overlap); + final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, effectiveRemainingPaintExtent) as double; final double stretchOffset = stretchConfiguration != null ? constraints.overlap.abs() : 0.0; geometry = SliverGeometry( scrollExtent: maxExtent, paintOrigin: constraints.overlap, - paintExtent: math.min(childExtent, constraints.remainingPaintExtent), + paintExtent: math.min(childExtent, effectiveRemainingPaintExtent), layoutExtent: layoutExtent, maxPaintExtent: maxExtent + stretchOffset, maxScrollObstructionExtent: minExtent, diff --git a/packages/flutter/test/cupertino/refresh_test.dart b/packages/flutter/test/cupertino/refresh_test.dart index 9a641c0c12..64fbe51193 100644 --- a/packages/flutter/test/cupertino/refresh_test.dart +++ b/packages/flutter/test/cupertino/refresh_test.dart @@ -1028,6 +1028,30 @@ void main() { debugDefaultTargetPlatformOverride = null; }, ); + + testWidgets('Should not crash when dragged', (WidgetTester tester) async { + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: CustomScrollView( + physics: const BouncingScrollPhysics(), + slivers: [ + CupertinoSliverRefreshControl( + onRefresh: () async => Future.delayed(const Duration(days: 2000)), + ), + ], + ), + ), + ); + + await tester.dragFrom(const Offset(100, 10), const Offset(0.0, 50.0), touchSlopY: 0); + await tester.pump(); + + await tester.dragFrom(const Offset(100, 10), const Offset(0, 500), touchSlopY: 0); + await tester.pump(); + + expect(tester.takeException(), isNull); + }); }; final VoidCallback stateMachineTestGroup = () { @@ -1465,6 +1489,36 @@ void main() { // Test the internal state machine directly to make sure the UI aren't just // correct by coincidence. group('state machine test short list', stateMachineTestGroup); + + testWidgets( + 'Does not crash when paintExtent > remainingPaintExtent', + (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/46871. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: CustomScrollView( + physics: const BouncingScrollPhysics(), + slivers: [ + const CupertinoSliverRefreshControl(), + SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) => const SizedBox(height: 100), + childCount: 20, + ), + ), + ], + ), + ), + ); + + // Drag the content down far enough so that + // geometry.paintExent > constraints.maxPaintExtent + await tester.dragFrom(const Offset(10, 10), const Offset(0, 500)); + await tester.pump(); + + expect(tester.takeException(), isNull); + }); } class MockHelper extends Mock {