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'; |
9 | library; |
10 | |
11 | import 'package:flutter/foundation.dart'; |
12 | import 'package:flutter/rendering.dart'; |
13 | |
14 | import 'framework.dart'; |
15 | import 'scroll_delegate.dart'; |
16 | import '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. |
35 | class 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 | |
79 | class _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 | |
99 | class _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 | |
117 | class _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. |
258 | class 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 | |
319 | class _SliverFillRemainingWithScrollable extends SingleChildRenderObjectWidget { |
320 | const _SliverFillRemainingWithScrollable({super.child}); |
321 | |
322 | @override |
323 | RenderSliverFillRemainingWithScrollable createRenderObject(BuildContext context) => |
324 | RenderSliverFillRemainingWithScrollable(); |
325 | } |
326 | |
327 | class _SliverFillRemainingWithoutScrollable extends SingleChildRenderObjectWidget { |
328 | const _SliverFillRemainingWithoutScrollable({super.child}); |
329 | |
330 | @override |
331 | RenderSliverFillRemaining createRenderObject(BuildContext context) => RenderSliverFillRemaining(); |
332 | } |
333 | |
334 | class _SliverFillRemainingAndOverscroll extends SingleChildRenderObjectWidget { |
335 | const _SliverFillRemainingAndOverscroll({super.child}); |
336 | |
337 | @override |
338 | RenderSliverFillRemainingAndOverscroll createRenderObject(BuildContext context) => |
339 | RenderSliverFillRemainingAndOverscroll(); |
340 | } |
341 |
Definitions
- SliverFillViewport
- SliverFillViewport
- build
- _SliverFillViewportRenderObjectWidget
- _SliverFillViewportRenderObjectWidget
- createRenderObject
- updateRenderObject
- _SliverFractionalPadding
- _SliverFractionalPadding
- createRenderObject
- updateRenderObject
- _RenderSliverFractionalPadding
- _RenderSliverFractionalPadding
- viewportFraction
- viewportFraction
- resolvedPadding
- _markNeedsResolution
- _resolve
- performLayout
- SliverFillRemaining
- SliverFillRemaining
- build
- debugFillProperties
- _SliverFillRemainingWithScrollable
- _SliverFillRemainingWithScrollable
- createRenderObject
- _SliverFillRemainingWithoutScrollable
- _SliverFillRemainingWithoutScrollable
- createRenderObject
- _SliverFillRemainingAndOverscroll
- _SliverFillRemainingAndOverscroll
Learn more about Flutter for embedded and desktop on industrialflutter.com