1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
import 'scroll_delegate.dart';
import 'sliver.dart';
/// A sliver that contains multiple box children that each fills the viewport.
///
/// _To learn more about slivers, see [CustomScrollView.slivers]._
///
/// [SliverFillViewport] places its children in a linear array along the main
/// axis. Each child is sized to fill the viewport, both in the main and cross
/// axis.
///
/// See also:
///
/// * [SliverFixedExtentList], which has a configurable
/// [SliverFixedExtentList.itemExtent].
/// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
/// except that it uses a prototype list item instead of a pixel value to define
/// the main axis extent of each item.
/// * [SliverList], which does not require its children to have the same
/// extent in the main axis.
class SliverFillViewport extends StatelessWidget {
/// Creates a sliver whose box children that each fill the viewport.
const SliverFillViewport({
super.key,
required this.delegate,
this.viewportFraction = 1.0,
this.padEnds = true,
}) : assert(viewportFraction > 0.0);
/// The fraction of the viewport that each child should fill in the main axis.
///
/// If this fraction is less than 1.0, more than one child will be visible at
/// once. If this fraction is greater than 1.0, each child will be larger than
/// the viewport in the main axis.
final double viewportFraction;
/// Whether to add padding to both ends of the list.
///
/// If this is set to true and [viewportFraction] < 1.0, padding will be added
/// such that the first and last child slivers will be in the center of
/// the viewport when scrolled all the way to the start or end, respectively.
/// You may want to set this to false if this [SliverFillViewport] is not the only
/// widget along this main axis, such as in a [CustomScrollView] with multiple
/// children.
///
/// This option cannot be null. If [viewportFraction] >= 1.0, this option has no
/// effect. Defaults to true.
final bool padEnds;
/// {@macro flutter.widgets.SliverMultiBoxAdaptorWidget.delegate}
final SliverChildDelegate delegate;
@override
Widget build(BuildContext context) {
return _SliverFractionalPadding(
viewportFraction: padEnds ? clampDouble(1 - viewportFraction, 0, 1) / 2 : 0,
sliver: _SliverFillViewportRenderObjectWidget(
viewportFraction: viewportFraction,
delegate: delegate,
),
);
}
}
class _SliverFillViewportRenderObjectWidget extends SliverMultiBoxAdaptorWidget {
const _SliverFillViewportRenderObjectWidget({
required super.delegate,
this.viewportFraction = 1.0,
}) : assert(viewportFraction > 0.0);
final double viewportFraction;
@override
RenderSliverFillViewport createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
return RenderSliverFillViewport(childManager: element, viewportFraction: viewportFraction);
}
@override
void updateRenderObject(BuildContext context, RenderSliverFillViewport renderObject) {
renderObject.viewportFraction = viewportFraction;
}
}
class _SliverFractionalPadding extends SingleChildRenderObjectWidget {
const _SliverFractionalPadding({
this.viewportFraction = 0,
Widget? sliver,
}) : assert(viewportFraction >= 0),
assert(viewportFraction <= 0.5),
super(child: sliver);
final double viewportFraction;
@override
RenderObject createRenderObject(BuildContext context) => _RenderSliverFractionalPadding(viewportFraction: viewportFraction);
@override
void updateRenderObject(BuildContext context, _RenderSliverFractionalPadding renderObject) {
renderObject.viewportFraction = viewportFraction;
}
}
class _RenderSliverFractionalPadding extends RenderSliverEdgeInsetsPadding {
_RenderSliverFractionalPadding({
double viewportFraction = 0,
}) : assert(viewportFraction <= 0.5),
assert(viewportFraction >= 0),
_viewportFraction = viewportFraction;
SliverConstraints? _lastResolvedConstraints;
double get viewportFraction => _viewportFraction;
double _viewportFraction;
set viewportFraction(double newValue) {
if (_viewportFraction == newValue) {
return;
}
_viewportFraction = newValue;
_markNeedsResolution();
}
@override
EdgeInsets? get resolvedPadding => _resolvedPadding;
EdgeInsets? _resolvedPadding;
void _markNeedsResolution() {
_resolvedPadding = null;
markNeedsLayout();
}
void _resolve() {
if (_resolvedPadding != null && _lastResolvedConstraints == constraints) {
return;
}
final double paddingValue = constraints.viewportMainAxisExtent * viewportFraction;
_lastResolvedConstraints = constraints;
switch (constraints.axis) {
case Axis.horizontal:
_resolvedPadding = EdgeInsets.symmetric(horizontal: paddingValue);
case Axis.vertical:
_resolvedPadding = EdgeInsets.symmetric(vertical: paddingValue);
}
return;
}
@override
void performLayout() {
_resolve();
super.performLayout();
}
}
/// A sliver that contains a single box child that fills the remaining space in
/// the viewport.
///
/// _To learn more about slivers, see [CustomScrollView.slivers]._
///
/// [SliverFillRemaining] will size its [child] to fill the viewport in the
/// cross axis. The extent of the sliver and its child's size in the main axis
/// is computed conditionally, described in further detail below.
///
/// Typically this will be the last sliver in a viewport, since (by definition)
/// there is never any room for anything beyond this sliver.
///
/// ## Main Axis Extent
///
/// ### When [SliverFillRemaining] has a scrollable child
///
/// The [hasScrollBody] flag indicates whether the sliver's child has a
/// scrollable body. This value is never null, and defaults to true. A common
/// example of this use is a [NestedScrollView]. In this case, the sliver will
/// size its child to fill the maximum available extent. [SliverFillRemaining]
/// will not constrain the scrollable area, as it could potentially have an
/// infinite depth. This is also true for use cases such as a [ScrollView] when
/// [ScrollView.shrinkWrap] is true.
///
/// ### When [SliverFillRemaining] does not have a scrollable child
///
/// When [hasScrollBody] is set to false, the child's size is taken into account
/// when considering the extent to which it should fill the space. The extent to
/// which the preceding slivers have been scrolled is also taken into
/// account in deciding how to layout this sliver.
///
/// [SliverFillRemaining] will size its [child] to fill the viewport in the
/// main axis if that space is larger than the child's extent, and the amount
/// of space that has been scrolled beforehand has not exceeded the main axis
/// extent of the viewport.
///
/// {@tool dartpad}
/// In this sample the [SliverFillRemaining] sizes its [child] to fill the
/// remaining extent of the viewport in both axes. The icon is centered in the
/// sliver, and would be in any computed extent for the sliver.
///
/// ** See code in examples/api/lib/widgets/sliver_fill/sliver_fill_remaining.0.dart **
/// {@end-tool}
///
/// [SliverFillRemaining] will defer to the size of its [child] if the
/// child's size exceeds the remaining space in the viewport.
///
/// {@tool dartpad}
/// In this sample the [SliverFillRemaining] defers to the size of its [child]
/// because the child's extent exceeds that of the remaining extent of the
/// viewport's main axis.
///
/// ** See code in examples/api/lib/widgets/sliver_fill/sliver_fill_remaining.1.dart **
/// {@end-tool}
///
/// [SliverFillRemaining] will defer to the size of its [child] if the
/// [SliverConstraints.precedingScrollExtent] exceeded the length of the viewport's main axis.
///
/// {@tool dartpad}
/// In this sample the [SliverFillRemaining] defers to the size of its [child]
/// because the [SliverConstraints.precedingScrollExtent] has gone
/// beyond that of the viewport's main axis.
///
/// ** See code in examples/api/lib/widgets/sliver_fill/sliver_fill_remaining.2.dart **
/// {@end-tool}
///
/// For [ScrollPhysics] that allow overscroll, such as
/// [BouncingScrollPhysics], setting the [fillOverscroll] flag to true allows
/// the size of the [child] to _stretch_, filling the overscroll area. It does
/// this regardless of the path chosen to provide the child's size.
///
/// {@animation 250 500 https://flutter.github.io/assets-for-api-docs/assets/widgets/sliver_fill_remaining_fill_overscroll.mp4}
///
/// {@tool dartpad}
/// In this sample the [SliverFillRemaining]'s child stretches to fill the
/// overscroll area when [fillOverscroll] is true. This sample also features a
/// button that is pinned to the bottom of the sliver, regardless of size or
/// overscroll behavior. Try switching [fillOverscroll] to see the difference.
///
/// This sample only shows the overscroll behavior on devices that support
/// overscroll.
///
/// ** See code in examples/api/lib/widgets/sliver_fill/sliver_fill_remaining.3.dart **
/// {@end-tool}
///
///
/// See also:
///
/// * [SliverFillViewport], which sizes its children based on the
/// size of the viewport, regardless of what else is in the scroll view.
/// * [SliverList], which shows a list of variable-sized children in a
/// viewport.
class SliverFillRemaining extends StatelessWidget {
/// Creates a sliver that fills the remaining space in the viewport.
const SliverFillRemaining({
super.key,
this.child,
this.hasScrollBody = true,
this.fillOverscroll = false,
});
/// Box child widget that fills the remaining space in the viewport.
///
/// The main [SliverFillRemaining] documentation contains more details.
final Widget? child;
/// Indicates 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. However, if the
/// [SliverConstraints.precedingScrollExtent] and/or the [child]'s
/// extent exceeds the size of the viewport, the sliver will defer to the
/// child's size rather than overriding it.
final bool hasScrollBody;
/// Indicates whether the child should stretch to fill the overscroll area
/// created by certain scroll physics, such as iOS' default scroll physics.
/// This value cannot be null. This flag is only relevant when the
/// [hasScrollBody] value is false.
///
/// Defaults to false, meaning the default behavior is for the child to
/// maintain its size and not extend into the overscroll area.
final bool fillOverscroll;
@override
Widget build(BuildContext context) {
if (hasScrollBody) {
return _SliverFillRemainingWithScrollable(child: child);
}
if (!fillOverscroll) {
return _SliverFillRemainingWithoutScrollable(child: child);
}
return _SliverFillRemainingAndOverscroll(child: child);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<Widget>(
'child',
child,
),
);
final List<String> flags = <String>[
if (hasScrollBody) 'scrollable',
if (fillOverscroll) 'fillOverscroll',
];
if (flags.isEmpty) {
flags.add('nonscrollable');
}
properties.add(IterableProperty<String>('mode', flags));
}
}
class _SliverFillRemainingWithScrollable extends SingleChildRenderObjectWidget {
const _SliverFillRemainingWithScrollable({
super.child,
});
@override
RenderSliverFillRemainingWithScrollable createRenderObject(BuildContext context) => RenderSliverFillRemainingWithScrollable();
}
class _SliverFillRemainingWithoutScrollable extends SingleChildRenderObjectWidget {
const _SliverFillRemainingWithoutScrollable({
super.child,
});
@override
RenderSliverFillRemaining createRenderObject(BuildContext context) => RenderSliverFillRemaining();
}
class _SliverFillRemainingAndOverscroll extends SingleChildRenderObjectWidget {
const _SliverFillRemainingAndOverscroll({
super.child,
});
@override
RenderSliverFillRemainingAndOverscroll createRenderObject(BuildContext context) => RenderSliverFillRemainingAndOverscroll();
}