1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:math' as math;
6
7import 'box.dart';
8import 'object.dart';
9import 'sliver.dart';
10import 'sliver_fixed_extent_list.dart';
11
12/// A sliver that contains multiple box children that each fill the viewport.
13///
14/// [RenderSliverFillViewport] places its children in a linear array along the
15/// main axis. Each child is sized to fill the viewport, both in the main and
16/// cross axis. A [viewportFraction] factor can be provided to size the children
17/// to a multiple of the viewport's main axis dimension (typically a fraction
18/// less than 1.0).
19///
20/// See also:
21///
22/// * [RenderSliverFillRemaining], which sizes the children based on the
23/// remaining space rather than the viewport itself.
24/// * [RenderSliverFixedExtentList], which has a configurable [itemExtent].
25/// * [RenderSliverList], which does not require its children to have the same
26/// extent in the main axis.
27class RenderSliverFillViewport extends RenderSliverFixedExtentBoxAdaptor {
28 /// Creates a sliver that contains multiple box children that each fill the
29 /// viewport.
30 RenderSliverFillViewport({
31 required super.childManager,
32 double viewportFraction = 1.0,
33 }) : assert(viewportFraction > 0.0),
34 _viewportFraction = viewportFraction;
35
36 @override
37 double get itemExtent => constraints.viewportMainAxisExtent * viewportFraction;
38
39 /// The fraction of the viewport that each child should fill in the main axis.
40 ///
41 /// If this fraction is less than 1.0, more than one child will be visible at
42 /// once. If this fraction is greater than 1.0, each child will be larger than
43 /// the viewport in the main axis.
44 double get viewportFraction => _viewportFraction;
45 double _viewportFraction;
46 set viewportFraction(double value) {
47 if (_viewportFraction == value) {
48 return;
49 }
50 _viewportFraction = value;
51 markNeedsLayout();
52 }
53}
54
55/// A sliver that contains a single box child that contains a scrollable and
56/// fills the viewport.
57///
58/// [RenderSliverFillRemainingWithScrollable] sizes its child to fill the
59/// viewport in the cross axis and to fill the remaining space in the viewport
60/// in the main axis.
61///
62/// Typically this will be the last sliver in a viewport, since (by definition)
63/// there is never any room for anything beyond this sliver.
64///
65/// See also:
66///
67/// * [NestedScrollView], which uses this sliver for the inner scrollable.
68/// * [RenderSliverFillRemaining], which lays out its
69/// non-scrollable child slightly different than this widget.
70/// * [RenderSliverFillRemainingAndOverscroll], which incorporates the
71/// overscroll into the remaining space to fill.
72/// * [RenderSliverFillViewport], which sizes its children based on the
73/// size of the viewport, regardless of what else is in the scroll view.
74/// * [RenderSliverList], which shows a list of variable-sized children in a
75/// viewport.
76class RenderSliverFillRemainingWithScrollable extends RenderSliverSingleBoxAdapter {
77 /// Creates a [RenderSliver] that wraps a scrollable [RenderBox] which is
78 /// sized to fit the remaining space in the viewport.
79 RenderSliverFillRemainingWithScrollable({ super.child });
80
81 @override
82 void performLayout() {
83 final SliverConstraints constraints = this.constraints;
84 final double extent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
85
86 if (child != null) {
87 child!.layout(constraints.asBoxConstraints(
88 minExtent: extent,
89 maxExtent: extent,
90 ));
91 }
92
93 final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
94 assert(paintedChildSize.isFinite);
95 assert(paintedChildSize >= 0.0);
96 geometry = SliverGeometry(
97 scrollExtent: constraints.viewportMainAxisExtent,
98 paintExtent: paintedChildSize,
99 maxPaintExtent: paintedChildSize,
100 hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
101 );
102 if (child != null) {
103 setChildParentData(child!, constraints, geometry!);
104 }
105 }
106}
107
108/// A sliver that contains a single box child that is non-scrollable and fills
109/// the remaining space in the viewport.
110///
111/// [RenderSliverFillRemaining] sizes its child to fill the
112/// viewport in the cross axis and to fill the remaining space in the viewport
113/// in the main axis.
114///
115/// Typically this will be the last sliver in a viewport, since (by definition)
116/// there is never any room for anything beyond this sliver.
117///
118/// See also:
119///
120/// * [RenderSliverFillRemainingWithScrollable], which lays out its scrollable
121/// child slightly different than this widget.
122/// * [RenderSliverFillRemainingAndOverscroll], which incorporates the
123/// overscroll into the remaining space to fill.
124/// * [RenderSliverFillViewport], which sizes its children based on the
125/// size of the viewport, regardless of what else is in the scroll view.
126/// * [RenderSliverList], which shows a list of variable-sized children in a
127/// viewport.
128class RenderSliverFillRemaining extends RenderSliverSingleBoxAdapter {
129 /// Creates a [RenderSliver] that wraps a non-scrollable [RenderBox] which is
130 /// sized to fit the remaining space in the viewport.
131 RenderSliverFillRemaining({ super.child });
132
133 @override
134 void performLayout() {
135 final SliverConstraints constraints = this.constraints;
136 // The remaining space in the viewportMainAxisExtent. Can be <= 0 if we have
137 // scrolled beyond the extent of the screen.
138 double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;
139
140 if (child != null) {
141 final double childExtent;
142 switch (constraints.axis) {
143 case Axis.horizontal:
144 childExtent = child!.getMaxIntrinsicWidth(constraints.crossAxisExtent);
145 case Axis.vertical:
146 childExtent = child!.getMaxIntrinsicHeight(constraints.crossAxisExtent);
147 }
148
149 // If the childExtent is greater than the computed extent, we want to use
150 // that instead of potentially cutting off the child. This allows us to
151 // safely specify a maxExtent.
152 extent = math.max(extent, childExtent);
153 child!.layout(constraints.asBoxConstraints(
154 minExtent: extent,
155 maxExtent: extent,
156 ));
157 }
158
159 assert(extent.isFinite,
160 'The calculated extent for the child of SliverFillRemaining is not finite. '
161 'This can happen if the child is a scrollable, in which case, the '
162 'hasScrollBody property of SliverFillRemaining should not be set to '
163 'false.',
164 );
165 final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
166 assert(paintedChildSize.isFinite);
167 assert(paintedChildSize >= 0.0);
168 geometry = SliverGeometry(
169 scrollExtent: extent,
170 paintExtent: paintedChildSize,
171 maxPaintExtent: paintedChildSize,
172 hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
173 );
174 if (child != null) {
175 setChildParentData(child!, constraints, geometry!);
176 }
177 }
178}
179
180/// A sliver that contains a single box child that is non-scrollable and fills
181/// the remaining space in the viewport including any overscrolled area.
182///
183/// [RenderSliverFillRemainingAndOverscroll] sizes its child to fill the
184/// viewport in the cross axis and to fill the remaining space in the viewport
185/// in the main axis with the overscroll area included.
186///
187/// Typically this will be the last sliver in a viewport, since (by definition)
188/// there is never any room for anything beyond this sliver.
189///
190/// See also:
191///
192/// * [RenderSliverFillRemainingWithScrollable], which lays out its scrollable
193/// child without overscroll.
194/// * [RenderSliverFillRemaining], which lays out its
195/// non-scrollable child without overscroll.
196/// * [RenderSliverFillViewport], which sizes its children based on the
197/// size of the viewport, regardless of what else is in the scroll view.
198/// * [RenderSliverList], which shows a list of variable-sized children in a
199/// viewport.
200class RenderSliverFillRemainingAndOverscroll extends RenderSliverSingleBoxAdapter {
201 /// Creates a [RenderSliver] that wraps a non-scrollable [RenderBox] which is
202 /// sized to fit the remaining space plus any overscroll in the viewport.
203 RenderSliverFillRemainingAndOverscroll({ super.child });
204
205 @override
206 void performLayout() {
207 final SliverConstraints constraints = this.constraints;
208 // The remaining space in the viewportMainAxisExtent. Can be <= 0 if we have
209 // scrolled beyond the extent of the screen.
210 double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;
211 // The maxExtent includes any overscrolled area. Can be < 0 if we have
212 // overscroll in the opposite direction, away from the end of the list.
213 double maxExtent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
214
215 if (child != null) {
216 final double childExtent;
217 switch (constraints.axis) {
218 case Axis.horizontal:
219 childExtent = child!.getMaxIntrinsicWidth(constraints.crossAxisExtent);
220 case Axis.vertical:
221 childExtent = child!.getMaxIntrinsicHeight(constraints.crossAxisExtent);
222 }
223
224 // If the childExtent is greater than the computed extent, we want to use
225 // that instead of potentially cutting off the child. This allows us to
226 // safely specify a maxExtent.
227 extent = math.max(extent, childExtent);
228 // The extent could be larger than the maxExtent due to a larger child
229 // size or overscrolling at the top of the scrollable (rather than at the
230 // end where this sliver is).
231 maxExtent = math.max(extent, maxExtent);
232 child!.layout(constraints.asBoxConstraints(minExtent: extent, maxExtent: maxExtent));
233 }
234
235 assert(extent.isFinite,
236 'The calculated extent for the child of SliverFillRemaining is not finite. '
237 'This can happen if the child is a scrollable, in which case, the '
238 'hasScrollBody property of SliverFillRemaining should not be set to '
239 'false.',
240 );
241 final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
242 assert(paintedChildSize.isFinite);
243 assert(paintedChildSize >= 0.0);
244 geometry = SliverGeometry(
245 scrollExtent: extent,
246 paintExtent: math.min(maxExtent, constraints.remainingPaintExtent),
247 maxPaintExtent: maxExtent,
248 hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
249 );
250 if (child != null) {
251 setChildParentData(child!, constraints, geometry!);
252 }
253 }
254}
255