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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com