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'; |
8 | library; |
9 | |
10 | import 'dart:math' as math; |
11 | |
12 | import 'box.dart'; |
13 | import 'object.dart'; |
14 | import 'sliver.dart'; |
15 | import '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. |
32 | class 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. |
81 | class 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. |
148 | class 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. |
220 | class 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 | |