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