SliverFillRemaining flag for different use cases (#33627)
* WIP
* Fixing nested scroll view tests
* Fixing nested scroll view tests
* Fixing nested scroll view tests
* WIP
* Added flag for SliverFillRemaining
* Clean up
* Review
* Reverted nested scroll view tests
* Updating tests
* Updated for overscroll behavior
diff --git a/packages/flutter/lib/src/rendering/sliver_fill.dart b/packages/flutter/lib/src/rendering/sliver_fill.dart
index 89ca497..1adbe83 100644
--- a/packages/flutter/lib/src/rendering/sliver_fill.dart
+++ b/packages/flutter/lib/src/rendering/sliver_fill.dart
@@ -113,18 +113,35 @@
/// the remaining space in the viewport.
RenderSliverFillRemaining({
RenderBox child,
- }) : super(child: child);
+ this.hasScrollBody = true,
+ }) : assert(hasScrollBody != null),
+ super(child: child);
+
+ /// Whether the child has a scrollable body, this value cannot be null.
+ ///
+ /// Defaults to true such that the child will extend beyond the viewport and
+ /// scroll, as seen in [NestedScrollView].
+ ///
+ /// Setting this value to false will allow the child to fill the remainder of
+ /// the viewport and not extend further.
+ bool hasScrollBody;
@override
void performLayout() {
- final double extent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
+ final double extent = constraints.remainingPaintExtent
+ - math.min(constraints.overlap, 0.0)
+ // Adding the offset for when this SliverFillRemaining is not scrollable,
+ // so it will stretch to fill on overscroll.
+ + (hasScrollBody ? 0.0 : constraints.scrollOffset);
if (child != null)
child.layout(constraints.asBoxConstraints(minExtent: extent, maxExtent: extent), parentUsesSize: true);
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
assert(paintedChildSize.isFinite);
assert(paintedChildSize >= 0.0);
geometry = SliverGeometry(
- scrollExtent: constraints.viewportMainAxisExtent,
+ // 0.0 can be applied here for cases when there is not scroll body since
+ // SliverFillRemaining will not have any slivers following it.
+ scrollExtent: hasScrollBody ? constraints.viewportMainAxisExtent : 0.0,
paintExtent: paintedChildSize,
maxPaintExtent: paintedChildSize,
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
diff --git a/packages/flutter/lib/src/widgets/sliver.dart b/packages/flutter/lib/src/widgets/sliver.dart
index 53a28a6..0e0fb86 100644
--- a/packages/flutter/lib/src/widgets/sliver.dart
+++ b/packages/flutter/lib/src/widgets/sliver.dart
@@ -1368,10 +1368,24 @@
const SliverFillRemaining({
Key key,
Widget child,
- }) : super(key: key, child: child);
+ this.hasScrollBody = true,
+ }) : assert(hasScrollBody != null),
+ super(key: key, child: child);
+
+ /// Whether the child has a scrollable body, this value cannot be null.
+ ///
+ /// Defaults to true such that the child will extend beyond the viewport and
+ /// scroll, as seen in [NestedScrollView].
+ ///
+ /// Setting this value to false will allow the child to fill the remainder of
+ /// the viewport and not extend further.
+ final bool hasScrollBody;
@override
- RenderSliverFillRemaining createRenderObject(BuildContext context) => RenderSliverFillRemaining();
+ RenderSliverFillRemaining createRenderObject(BuildContext context) => RenderSliverFillRemaining(hasScrollBody: hasScrollBody);
+
+ @override
+ void updateRenderObject(BuildContext context, RenderSliverFillRemaining renderObject) => renderObject.hasScrollBody = hasScrollBody;
}
/// Mark a child as needing to stay alive even when it's in a lazy list that
diff --git a/packages/flutter/test/widgets/sliver_fill_remaining_test.dart b/packages/flutter/test/widgets/sliver_fill_remaining_test.dart
index ff2090b..1b58378 100644
--- a/packages/flutter/test/widgets/sliver_fill_remaining_test.dart
+++ b/packages/flutter/test/widgets/sliver_fill_remaining_test.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
void main() {
@@ -62,4 +63,63 @@
await tester.pump();
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(500.0));
});
+
+ testWidgets('SliverFillRemaining does not extend past viewport.', (WidgetTester tester) async {
+ final ScrollController controller = ScrollController();
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: CustomScrollView(
+ controller: controller,
+ slivers: <Widget>[
+ SliverToBoxAdapter(
+ child: Container(
+ color: Colors.red,
+ height: 150.0,
+ ),
+ ),
+ SliverFillRemaining(
+ child: Container(color: Colors.white),
+ hasScrollBody: false,
+ ),
+ ],
+ ),
+ ),
+ );
+ expect(controller.offset, 0.0);
+ expect(find.byType(Container), findsNWidgets(2));
+ controller.jumpTo(150.0);
+ await tester.pumpAndSettle();
+ expect(controller.offset, 0.0);
+ expect(find.byType(Container), findsNWidgets(2));
+ });
+
+ testWidgets('SliverFillRemaining scrolls beyond viewport by default.', (WidgetTester tester) async {
+ final ScrollController controller = ScrollController();
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: CustomScrollView(
+ controller: controller,
+ slivers: <Widget>[
+ SliverToBoxAdapter(
+ child: Container(
+ color: Colors.red,
+ height: 150.0,
+ ),
+ ),
+ SliverFillRemaining(
+ child: Container(color: Colors.white),
+ ),
+ ],
+ ),
+ ),
+ );
+ expect(controller.offset, 0.0);
+ expect(find.byType(Container), findsNWidgets(2));
+ controller.jumpTo(150.0);
+ await tester.pumpAndSettle();
+ expect(controller.offset, 150.0);
+ expect(find.byType(Container), findsOneWidget);
+ });
}
diff --git a/packages/flutter/test/widgets/sliver_fill_viewport_test.dart b/packages/flutter/test/widgets/sliver_fill_viewport_test.dart
index e71db18..e5b60dd 100644
--- a/packages/flutter/test/widgets/sliver_fill_viewport_test.dart
+++ b/packages/flutter/test/widgets/sliver_fill_viewport_test.dart
@@ -6,7 +6,7 @@
import 'package:flutter/widgets.dart';
void main() {
- testWidgets('SliverFillRemaining control test', (WidgetTester tester) async {
+ testWidgets('SliverFillViewport control test', (WidgetTester tester) async {
final List<Widget> children = List<Widget>.generate(20, (int i) {
return Container(child: Text('$i', textDirection: TextDirection.ltr));
});