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