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_fixed_extent_list.dart'; |
8 | /// @docImport 'sliver_list.dart'; |
9 | library; |
10 | |
11 | import 'dart:math' as math; |
12 | |
13 | import 'package:flutter/foundation.dart'; |
14 | |
15 | import 'box.dart'; |
16 | import 'object.dart'; |
17 | import 'sliver.dart'; |
18 | import 'sliver_multi_box_adaptor.dart'; |
19 | |
20 | /// Describes the placement of a child in a [RenderSliverGrid]. |
21 | /// |
22 | /// This class is similar to [Rect], in that it gives a two-dimensional position |
23 | /// and a two-dimensional dimension, but is direction-agnostic. |
24 | /// |
25 | /// {@tool dartpad} |
26 | /// This example shows how a custom [SliverGridLayout] uses [SliverGridGeometry] |
27 | /// to lay out the children. |
28 | /// |
29 | /// ** See code in examples/api/lib/widgets/scroll_view/grid_view.0.dart ** |
30 | /// {@end-tool} |
31 | /// |
32 | /// See also: |
33 | /// |
34 | /// * [SliverGridLayout], which represents the geometry of all the tiles in a |
35 | /// grid. |
36 | /// * [SliverGridLayout.getGeometryForChildIndex], which returns this object |
37 | /// to describe the child's placement. |
38 | /// * [RenderSliverGrid], which uses this class during its |
39 | /// [RenderSliverGrid.performLayout] method. |
40 | @immutable |
41 | class SliverGridGeometry { |
42 | /// Creates an object that describes the placement of a child in a [RenderSliverGrid]. |
43 | const SliverGridGeometry({ |
44 | required this.scrollOffset, |
45 | required this.crossAxisOffset, |
46 | required this.mainAxisExtent, |
47 | required this.crossAxisExtent, |
48 | }); |
49 | |
50 | /// The scroll offset of the leading edge of the child relative to the leading |
51 | /// edge of the parent. |
52 | final double scrollOffset; |
53 | |
54 | /// The offset of the child in the non-scrolling axis. |
55 | /// |
56 | /// If the scroll axis is vertical, this offset is from the left-most edge of |
57 | /// the parent to the left-most edge of the child. If the scroll axis is |
58 | /// horizontal, this offset is from the top-most edge of the parent to the |
59 | /// top-most edge of the child. |
60 | final double crossAxisOffset; |
61 | |
62 | /// The extent of the child in the scrolling axis. |
63 | /// |
64 | /// If the scroll axis is vertical, this extent is the child's height. If the |
65 | /// scroll axis is horizontal, this extent is the child's width. |
66 | final double mainAxisExtent; |
67 | |
68 | /// The extent of the child in the non-scrolling axis. |
69 | /// |
70 | /// If the scroll axis is vertical, this extent is the child's width. If the |
71 | /// scroll axis is horizontal, this extent is the child's height. |
72 | final double crossAxisExtent; |
73 | |
74 | /// The scroll offset of the trailing edge of the child relative to the |
75 | /// leading edge of the parent. |
76 | double get trailingScrollOffset => scrollOffset + mainAxisExtent; |
77 | |
78 | /// Returns a tight [BoxConstraints] that forces the child to have the |
79 | /// required size, given a [SliverConstraints]. |
80 | BoxConstraints getBoxConstraints(SliverConstraints constraints) { |
81 | return constraints.asBoxConstraints( |
82 | minExtent: mainAxisExtent, |
83 | maxExtent: mainAxisExtent, |
84 | crossAxisExtent: crossAxisExtent, |
85 | ); |
86 | } |
87 | |
88 | @override |
89 | String toString() { |
90 | final List<String> properties = <String>[ |
91 | 'scrollOffset:$scrollOffset ', |
92 | 'crossAxisOffset:$crossAxisOffset ', |
93 | 'mainAxisExtent:$mainAxisExtent ', |
94 | 'crossAxisExtent:$crossAxisExtent ', |
95 | ]; |
96 | return 'SliverGridGeometry(${properties.join( ', ')} )'; |
97 | } |
98 | } |
99 | |
100 | /// The size and position of all the tiles in a [RenderSliverGrid]. |
101 | /// |
102 | /// Rather that providing a grid with a [SliverGridLayout] directly, the grid is |
103 | /// provided a [SliverGridDelegate], which computes a [SliverGridLayout] given a |
104 | /// set of [SliverConstraints]. This allows the algorithm to dynamically respond |
105 | /// to changes in the environment (e.g. the user rotating the device). |
106 | /// |
107 | /// The tiles can be placed arbitrarily, but it is more efficient to place tiles |
108 | /// roughly in order by scroll offset because grids reify a contiguous sequence |
109 | /// of children. |
110 | /// |
111 | /// {@tool dartpad} |
112 | /// This example shows how to construct a custom [SliverGridLayout] to lay tiles |
113 | /// in a grid form with some cells stretched to fit the entire width of the |
114 | /// grid (sometimes called "hero tiles"). |
115 | /// |
116 | /// ** See code in examples/api/lib/widgets/scroll_view/grid_view.0.dart ** |
117 | /// {@end-tool} |
118 | /// |
119 | /// See also: |
120 | /// |
121 | /// * [SliverGridRegularTileLayout], which represents a layout that uses |
122 | /// equally sized and spaced tiles. |
123 | /// * [SliverGridGeometry], which represents the size and position of a single |
124 | /// tile in a grid. |
125 | /// * [SliverGridDelegate.getLayout], which returns this object to describe the |
126 | /// delegate's layout. |
127 | /// * [RenderSliverGrid], which uses this class during its |
128 | /// [RenderSliverGrid.performLayout] method. |
129 | @immutable |
130 | abstract class SliverGridLayout { |
131 | /// Abstract const constructor. This constructor enables subclasses to provide |
132 | /// const constructors so that they can be used in const expressions. |
133 | const SliverGridLayout(); |
134 | |
135 | /// The minimum child index that intersects with (or is after) this scroll offset. |
136 | int getMinChildIndexForScrollOffset(double scrollOffset); |
137 | |
138 | /// The maximum child index that intersects with (or is before) this scroll offset. |
139 | int getMaxChildIndexForScrollOffset(double scrollOffset); |
140 | |
141 | /// The size and position of the child with the given index. |
142 | SliverGridGeometry getGeometryForChildIndex(int index); |
143 | |
144 | /// The scroll extent needed to fully display all the tiles if there are |
145 | /// `childCount` children in total. |
146 | /// |
147 | /// The child count will never be null. |
148 | double computeMaxScrollOffset(int childCount); |
149 | } |
150 | |
151 | /// A [SliverGridLayout] that uses equally sized and spaced tiles. |
152 | /// |
153 | /// Rather that providing a grid with a [SliverGridLayout] directly, you instead |
154 | /// provide the grid a [SliverGridDelegate], which can compute a |
155 | /// [SliverGridLayout] given the current [SliverConstraints]. |
156 | /// |
157 | /// This layout is used by [SliverGridDelegateWithFixedCrossAxisCount] and |
158 | /// [SliverGridDelegateWithMaxCrossAxisExtent]. |
159 | /// |
160 | /// See also: |
161 | /// |
162 | /// * [SliverGridDelegateWithFixedCrossAxisCount], which uses this layout. |
163 | /// * [SliverGridDelegateWithMaxCrossAxisExtent], which uses this layout. |
164 | /// * [SliverGridLayout], which represents an arbitrary tile layout. |
165 | /// * [SliverGridGeometry], which represents the size and position of a single |
166 | /// tile in a grid. |
167 | /// * [SliverGridDelegate.getLayout], which returns this object to describe the |
168 | /// delegate's layout. |
169 | /// * [RenderSliverGrid], which uses this class during its |
170 | /// [RenderSliverGrid.performLayout] method. |
171 | class SliverGridRegularTileLayout extends SliverGridLayout { |
172 | /// Creates a layout that uses equally sized and spaced tiles. |
173 | /// |
174 | /// All of the arguments must not be negative. The `crossAxisCount` argument |
175 | /// must be greater than zero. |
176 | const SliverGridRegularTileLayout({ |
177 | required this.crossAxisCount, |
178 | required this.mainAxisStride, |
179 | required this.crossAxisStride, |
180 | required this.childMainAxisExtent, |
181 | required this.childCrossAxisExtent, |
182 | required this.reverseCrossAxis, |
183 | }) : assert(crossAxisCount > 0), |
184 | assert(mainAxisStride >= 0), |
185 | assert(crossAxisStride >= 0), |
186 | assert(childMainAxisExtent >= 0), |
187 | assert(childCrossAxisExtent >= 0); |
188 | |
189 | /// The number of children in the cross axis. |
190 | final int crossAxisCount; |
191 | |
192 | /// The number of pixels from the leading edge of one tile to the leading edge |
193 | /// of the next tile in the main axis. |
194 | final double mainAxisStride; |
195 | |
196 | /// The number of pixels from the leading edge of one tile to the leading edge |
197 | /// of the next tile in the cross axis. |
198 | final double crossAxisStride; |
199 | |
200 | /// The number of pixels from the leading edge of one tile to the trailing |
201 | /// edge of the same tile in the main axis. |
202 | final double childMainAxisExtent; |
203 | |
204 | /// The number of pixels from the leading edge of one tile to the trailing |
205 | /// edge of the same tile in the cross axis. |
206 | final double childCrossAxisExtent; |
207 | |
208 | /// Whether the children should be placed in the opposite order of increasing |
209 | /// coordinates in the cross axis. |
210 | /// |
211 | /// For example, if the cross axis is horizontal, the children are placed from |
212 | /// left to right when [reverseCrossAxis] is false and from right to left when |
213 | /// [reverseCrossAxis] is true. |
214 | /// |
215 | /// Typically set to the return value of [axisDirectionIsReversed] applied to |
216 | /// the [SliverConstraints.crossAxisDirection]. |
217 | final bool reverseCrossAxis; |
218 | |
219 | @override |
220 | int getMinChildIndexForScrollOffset(double scrollOffset) { |
221 | return mainAxisStride > precisionErrorTolerance |
222 | ? crossAxisCount * (scrollOffset ~/ mainAxisStride) |
223 | : 0; |
224 | } |
225 | |
226 | @override |
227 | int getMaxChildIndexForScrollOffset(double scrollOffset) { |
228 | if (mainAxisStride > 0.0) { |
229 | final int mainAxisCount = (scrollOffset / mainAxisStride).ceil(); |
230 | return math.max(0, crossAxisCount * mainAxisCount - 1); |
231 | } |
232 | return 0; |
233 | } |
234 | |
235 | double _getOffsetFromStartInCrossAxis(double crossAxisStart) { |
236 | if (reverseCrossAxis) { |
237 | return crossAxisCount * crossAxisStride - |
238 | crossAxisStart - |
239 | childCrossAxisExtent - |
240 | (crossAxisStride - childCrossAxisExtent); |
241 | } |
242 | return crossAxisStart; |
243 | } |
244 | |
245 | @override |
246 | SliverGridGeometry getGeometryForChildIndex(int index) { |
247 | final double crossAxisStart = (index % crossAxisCount) * crossAxisStride; |
248 | return SliverGridGeometry( |
249 | scrollOffset: (index ~/ crossAxisCount) * mainAxisStride, |
250 | crossAxisOffset: _getOffsetFromStartInCrossAxis(crossAxisStart), |
251 | mainAxisExtent: childMainAxisExtent, |
252 | crossAxisExtent: childCrossAxisExtent, |
253 | ); |
254 | } |
255 | |
256 | @override |
257 | double computeMaxScrollOffset(int childCount) { |
258 | if (childCount == 0) { |
259 | // There are no children in the grid. The max scroll offset should be |
260 | // zero. |
261 | return 0.0; |
262 | } |
263 | final int mainAxisCount = ((childCount - 1) ~/ crossAxisCount) + 1; |
264 | final double mainAxisSpacing = mainAxisStride - childMainAxisExtent; |
265 | return mainAxisStride * mainAxisCount - mainAxisSpacing; |
266 | } |
267 | } |
268 | |
269 | /// Controls the layout of tiles in a grid. |
270 | /// |
271 | /// Given the current constraints on the grid, a [SliverGridDelegate] computes |
272 | /// the layout for the tiles in the grid. The tiles can be placed arbitrarily, |
273 | /// but it is more efficient to place tiles roughly in order by scroll offset |
274 | /// because grids reify a contiguous sequence of children. |
275 | /// |
276 | /// {@tool dartpad} |
277 | /// This example shows how a [SliverGridDelegate] returns a [SliverGridLayout] |
278 | /// configured based on the provided [SliverConstraints] in [getLayout]. |
279 | /// |
280 | /// ** See code in examples/api/lib/widgets/scroll_view/grid_view.0.dart ** |
281 | /// {@end-tool} |
282 | /// |
283 | /// See also: |
284 | /// |
285 | /// * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with |
286 | /// a fixed number of tiles in the cross axis. |
287 | /// * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with |
288 | /// tiles that have a maximum cross-axis extent. |
289 | /// * [GridView], which uses this delegate to control the layout of its tiles. |
290 | /// * [SliverGrid], which uses this delegate to control the layout of its |
291 | /// tiles. |
292 | /// * [RenderSliverGrid], which uses this delegate to control the layout of its |
293 | /// tiles. |
294 | abstract class SliverGridDelegate { |
295 | /// Abstract const constructor. This constructor enables subclasses to provide |
296 | /// const constructors so that they can be used in const expressions. |
297 | const SliverGridDelegate(); |
298 | |
299 | /// Returns information about the size and position of the tiles in the grid. |
300 | SliverGridLayout getLayout(SliverConstraints constraints); |
301 | |
302 | /// Override this method to return true when the children need to be |
303 | /// laid out. |
304 | /// |
305 | /// This should compare the fields of the current delegate and the given |
306 | /// `oldDelegate` and return true if the fields are such that the layout would |
307 | /// be different. |
308 | bool shouldRelayout(covariant SliverGridDelegate oldDelegate); |
309 | } |
310 | |
311 | /// Creates grid layouts with a fixed number of tiles in the cross axis. |
312 | /// |
313 | /// For example, if the grid is vertical, this delegate will create a layout |
314 | /// with a fixed number of columns. If the grid is horizontal, this delegate |
315 | /// will create a layout with a fixed number of rows. |
316 | /// |
317 | /// This delegate creates grids with equally sized and spaced tiles. |
318 | /// |
319 | /// {@tool dartpad} |
320 | /// Here is an example using the [childAspectRatio] property. On a device with a |
321 | /// screen width of 800.0, it creates a GridView with each tile with a width of |
322 | /// 200.0 and a height of 100.0. |
323 | /// |
324 | /// ** See code in examples/api/lib/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.0.dart ** |
325 | /// {@end-tool} |
326 | /// |
327 | /// {@tool dartpad} |
328 | /// Here is an example using the [mainAxisExtent] property. On a device with a |
329 | /// screen width of 800.0, it creates a GridView with each tile with a width of |
330 | /// 200.0 and a height of 150.0. |
331 | /// |
332 | /// ** See code in examples/api/lib/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.1.dart ** |
333 | /// {@end-tool} |
334 | /// |
335 | /// See also: |
336 | /// |
337 | /// * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with |
338 | /// tiles that have a maximum cross-axis extent. |
339 | /// * [SliverGridDelegate], which creates arbitrary layouts. |
340 | /// * [GridView], which can use this delegate to control the layout of its |
341 | /// tiles. |
342 | /// * [SliverGrid], which can use this delegate to control the layout of its |
343 | /// tiles. |
344 | /// * [RenderSliverGrid], which can use this delegate to control the layout of |
345 | /// its tiles. |
346 | class SliverGridDelegateWithFixedCrossAxisCount extends SliverGridDelegate { |
347 | /// Creates a delegate that makes grid layouts with a fixed number of tiles in |
348 | /// the cross axis. |
349 | /// |
350 | /// The `mainAxisSpacing`, `mainAxisExtent` and `crossAxisSpacing` arguments |
351 | /// must not be negative. The `crossAxisCount` and `childAspectRatio` |
352 | /// arguments must be greater than zero. |
353 | const SliverGridDelegateWithFixedCrossAxisCount({ |
354 | required this.crossAxisCount, |
355 | this.mainAxisSpacing = 0.0, |
356 | this.crossAxisSpacing = 0.0, |
357 | this.childAspectRatio = 1.0, |
358 | this.mainAxisExtent, |
359 | }) : assert(crossAxisCount > 0), |
360 | assert(mainAxisSpacing >= 0), |
361 | assert(crossAxisSpacing >= 0), |
362 | assert(childAspectRatio > 0), |
363 | assert(mainAxisExtent == null || mainAxisExtent >= 0); |
364 | |
365 | /// The number of children in the cross axis. |
366 | final int crossAxisCount; |
367 | |
368 | /// The number of logical pixels between each child along the main axis. |
369 | final double mainAxisSpacing; |
370 | |
371 | /// The number of logical pixels between each child along the cross axis. |
372 | final double crossAxisSpacing; |
373 | |
374 | /// The ratio of the cross-axis to the main-axis extent of each child. |
375 | final double childAspectRatio; |
376 | |
377 | /// The extent of each tile in the main axis. If provided it would define the |
378 | /// logical pixels taken by each tile in the main-axis. |
379 | /// |
380 | /// If null, [childAspectRatio] is used instead. |
381 | final double? mainAxisExtent; |
382 | |
383 | bool _debugAssertIsValid() { |
384 | assert(crossAxisCount > 0); |
385 | assert(mainAxisSpacing >= 0.0); |
386 | assert(crossAxisSpacing >= 0.0); |
387 | assert(childAspectRatio > 0.0); |
388 | return true; |
389 | } |
390 | |
391 | @override |
392 | SliverGridLayout getLayout(SliverConstraints constraints) { |
393 | assert(_debugAssertIsValid()); |
394 | final double usableCrossAxisExtent = math.max( |
395 | 0.0, |
396 | constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1), |
397 | ); |
398 | final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount; |
399 | final double childMainAxisExtent = mainAxisExtent ?? childCrossAxisExtent / childAspectRatio; |
400 | return SliverGridRegularTileLayout( |
401 | crossAxisCount: crossAxisCount, |
402 | mainAxisStride: childMainAxisExtent + mainAxisSpacing, |
403 | crossAxisStride: childCrossAxisExtent + crossAxisSpacing, |
404 | childMainAxisExtent: childMainAxisExtent, |
405 | childCrossAxisExtent: childCrossAxisExtent, |
406 | reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), |
407 | ); |
408 | } |
409 | |
410 | @override |
411 | bool shouldRelayout(SliverGridDelegateWithFixedCrossAxisCount oldDelegate) { |
412 | return oldDelegate.crossAxisCount != crossAxisCount || |
413 | oldDelegate.mainAxisSpacing != mainAxisSpacing || |
414 | oldDelegate.crossAxisSpacing != crossAxisSpacing || |
415 | oldDelegate.childAspectRatio != childAspectRatio || |
416 | oldDelegate.mainAxisExtent != mainAxisExtent; |
417 | } |
418 | } |
419 | |
420 | /// Creates grid layouts with tiles that each have a maximum cross-axis extent. |
421 | /// |
422 | /// This delegate will select a cross-axis extent for the tiles that is as |
423 | /// large as possible subject to the following conditions: |
424 | /// |
425 | /// - The extent evenly divides the cross-axis extent of the grid. |
426 | /// - The extent is at most [maxCrossAxisExtent]. |
427 | /// |
428 | /// For example, if the grid is vertical, the grid is 500.0 pixels wide, and |
429 | /// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4 |
430 | /// columns that are 125.0 pixels wide. |
431 | /// |
432 | /// This delegate creates grids with equally sized and spaced tiles. |
433 | /// |
434 | /// See also: |
435 | /// |
436 | /// * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with |
437 | /// a fixed number of tiles in the cross axis. |
438 | /// * [SliverGridDelegate], which creates arbitrary layouts. |
439 | /// * [GridView], which can use this delegate to control the layout of its |
440 | /// tiles. |
441 | /// * [SliverGrid], which can use this delegate to control the layout of its |
442 | /// tiles. |
443 | /// * [RenderSliverGrid], which can use this delegate to control the layout of |
444 | /// its tiles. |
445 | class SliverGridDelegateWithMaxCrossAxisExtent extends SliverGridDelegate { |
446 | /// Creates a delegate that makes grid layouts with tiles that have a maximum |
447 | /// cross-axis extent. |
448 | /// |
449 | /// The [maxCrossAxisExtent], [mainAxisExtent], [mainAxisSpacing], |
450 | /// and [crossAxisSpacing] arguments must not be negative. |
451 | /// The [childAspectRatio] argument must be greater than zero. |
452 | const SliverGridDelegateWithMaxCrossAxisExtent({ |
453 | required this.maxCrossAxisExtent, |
454 | this.mainAxisSpacing = 0.0, |
455 | this.crossAxisSpacing = 0.0, |
456 | this.childAspectRatio = 1.0, |
457 | this.mainAxisExtent, |
458 | }) : assert(maxCrossAxisExtent > 0), |
459 | assert(mainAxisSpacing >= 0), |
460 | assert(crossAxisSpacing >= 0), |
461 | assert(childAspectRatio > 0), |
462 | assert(mainAxisExtent == null || mainAxisExtent >= 0); |
463 | |
464 | /// The maximum extent of tiles in the cross axis. |
465 | /// |
466 | /// This delegate will select a cross-axis extent for the tiles that is as |
467 | /// large as possible subject to the following conditions: |
468 | /// |
469 | /// - The extent evenly divides the cross-axis extent of the grid. |
470 | /// - The extent is at most [maxCrossAxisExtent]. |
471 | /// |
472 | /// For example, if the grid is vertical, the grid is 500.0 pixels wide, and |
473 | /// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4 |
474 | /// columns that are 125.0 pixels wide. |
475 | final double maxCrossAxisExtent; |
476 | |
477 | /// The number of logical pixels between each child along the main axis. |
478 | final double mainAxisSpacing; |
479 | |
480 | /// The number of logical pixels between each child along the cross axis. |
481 | final double crossAxisSpacing; |
482 | |
483 | /// The ratio of the cross-axis to the main-axis extent of each child. |
484 | final double childAspectRatio; |
485 | |
486 | /// The extent of each tile in the main axis. If provided it would define the |
487 | /// logical pixels taken by each tile in the main-axis. |
488 | /// |
489 | /// If null, [childAspectRatio] is used instead. |
490 | final double? mainAxisExtent; |
491 | |
492 | bool _debugAssertIsValid(double crossAxisExtent) { |
493 | assert(crossAxisExtent > 0.0); |
494 | assert(maxCrossAxisExtent > 0.0); |
495 | assert(mainAxisSpacing >= 0.0); |
496 | assert(crossAxisSpacing >= 0.0); |
497 | assert(childAspectRatio > 0.0); |
498 | return true; |
499 | } |
500 | |
501 | @override |
502 | SliverGridLayout getLayout(SliverConstraints constraints) { |
503 | assert(_debugAssertIsValid(constraints.crossAxisExtent)); |
504 | int crossAxisCount = |
505 | (constraints.crossAxisExtent / (maxCrossAxisExtent + crossAxisSpacing)).ceil(); |
506 | // Ensure a minimum count of 1, can be zero and result in an infinite extent |
507 | // below when the window size is 0. |
508 | crossAxisCount = math.max(1, crossAxisCount); |
509 | final double usableCrossAxisExtent = math.max( |
510 | 0.0, |
511 | constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1), |
512 | ); |
513 | final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount; |
514 | final double childMainAxisExtent = mainAxisExtent ?? childCrossAxisExtent / childAspectRatio; |
515 | return SliverGridRegularTileLayout( |
516 | crossAxisCount: crossAxisCount, |
517 | mainAxisStride: childMainAxisExtent + mainAxisSpacing, |
518 | crossAxisStride: childCrossAxisExtent + crossAxisSpacing, |
519 | childMainAxisExtent: childMainAxisExtent, |
520 | childCrossAxisExtent: childCrossAxisExtent, |
521 | reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), |
522 | ); |
523 | } |
524 | |
525 | @override |
526 | bool shouldRelayout(SliverGridDelegateWithMaxCrossAxisExtent oldDelegate) { |
527 | return oldDelegate.maxCrossAxisExtent != maxCrossAxisExtent || |
528 | oldDelegate.mainAxisSpacing != mainAxisSpacing || |
529 | oldDelegate.crossAxisSpacing != crossAxisSpacing || |
530 | oldDelegate.childAspectRatio != childAspectRatio || |
531 | oldDelegate.mainAxisExtent != mainAxisExtent; |
532 | } |
533 | } |
534 | |
535 | /// Parent data structure used by [RenderSliverGrid]. |
536 | class SliverGridParentData extends SliverMultiBoxAdaptorParentData { |
537 | /// The offset of the child in the non-scrolling axis. |
538 | /// |
539 | /// If the scroll axis is vertical, this offset is from the left-most edge of |
540 | /// the parent to the left-most edge of the child. If the scroll axis is |
541 | /// horizontal, this offset is from the top-most edge of the parent to the |
542 | /// top-most edge of the child. |
543 | double? crossAxisOffset; |
544 | |
545 | @override |
546 | String toString() => 'crossAxisOffset=$crossAxisOffset ;${super.toString()} '; |
547 | } |
548 | |
549 | /// A sliver that places multiple box children in a two dimensional arrangement. |
550 | /// |
551 | /// [RenderSliverGrid] places its children in arbitrary positions determined by |
552 | /// [gridDelegate]. Each child is forced to have the size specified by the |
553 | /// [gridDelegate]. |
554 | /// |
555 | /// See also: |
556 | /// |
557 | /// * [RenderSliverList], which places its children in a linear |
558 | /// array. |
559 | /// * [RenderSliverFixedExtentList], which places its children in a linear |
560 | /// array with a fixed extent in the main axis. |
561 | class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { |
562 | /// Creates a sliver that contains multiple box children that whose size and |
563 | /// position are determined by a delegate. |
564 | RenderSliverGrid({required super.childManager, required SliverGridDelegate gridDelegate}) |
565 | : _gridDelegate = gridDelegate; |
566 | |
567 | @override |
568 | void setupParentData(RenderObject child) { |
569 | if (child.parentData is! SliverGridParentData) { |
570 | child.parentData = SliverGridParentData(); |
571 | } |
572 | } |
573 | |
574 | /// The delegate that controls the size and position of the children. |
575 | SliverGridDelegate get gridDelegate => _gridDelegate; |
576 | SliverGridDelegate _gridDelegate; |
577 | set gridDelegate(SliverGridDelegate value) { |
578 | if (_gridDelegate == value) { |
579 | return; |
580 | } |
581 | if (value.runtimeType != _gridDelegate.runtimeType || value.shouldRelayout(_gridDelegate)) { |
582 | markNeedsLayout(); |
583 | } |
584 | _gridDelegate = value; |
585 | } |
586 | |
587 | @override |
588 | double childCrossAxisPosition(RenderBox child) { |
589 | final SliverGridParentData childParentData = child.parentData! as SliverGridParentData; |
590 | return childParentData.crossAxisOffset!; |
591 | } |
592 | |
593 | @override |
594 | void performLayout() { |
595 | final SliverConstraints constraints = this.constraints; |
596 | childManager.didStartLayout(); |
597 | childManager.setDidUnderflow(false); |
598 | |
599 | final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin; |
600 | assert(scrollOffset >= 0.0); |
601 | final double remainingExtent = constraints.remainingCacheExtent; |
602 | assert(remainingExtent >= 0.0); |
603 | final double targetEndScrollOffset = scrollOffset + remainingExtent; |
604 | |
605 | final SliverGridLayout layout = _gridDelegate.getLayout(constraints); |
606 | |
607 | final int firstIndex = layout.getMinChildIndexForScrollOffset(scrollOffset); |
608 | final int? targetLastIndex = |
609 | targetEndScrollOffset.isFinite |
610 | ? layout.getMaxChildIndexForScrollOffset(targetEndScrollOffset) |
611 | : null; |
612 | if (firstChild != null) { |
613 | final int leadingGarbage = calculateLeadingGarbage(firstIndex: firstIndex); |
614 | final int trailingGarbage = |
615 | targetLastIndex != null ? calculateTrailingGarbage(lastIndex: targetLastIndex) : 0; |
616 | collectGarbage(leadingGarbage, trailingGarbage); |
617 | } else { |
618 | collectGarbage(0, 0); |
619 | } |
620 | |
621 | final SliverGridGeometry firstChildGridGeometry = layout.getGeometryForChildIndex(firstIndex); |
622 | |
623 | if (firstChild == null) { |
624 | if (!addInitialChild(index: firstIndex, layoutOffset: firstChildGridGeometry.scrollOffset)) { |
625 | // There are either no children, or we are past the end of all our children. |
626 | final double max = layout.computeMaxScrollOffset(childManager.childCount); |
627 | geometry = SliverGeometry(scrollExtent: max, maxPaintExtent: max); |
628 | childManager.didFinishLayout(); |
629 | return; |
630 | } |
631 | } |
632 | |
633 | final double leadingScrollOffset = firstChildGridGeometry.scrollOffset; |
634 | double trailingScrollOffset = firstChildGridGeometry.trailingScrollOffset; |
635 | RenderBox? trailingChildWithLayout; |
636 | bool reachedEnd = false; |
637 | |
638 | for (int index = indexOf(firstChild!) - 1; index >= firstIndex; --index) { |
639 | final SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index); |
640 | final RenderBox child = |
641 | insertAndLayoutLeadingChild(gridGeometry.getBoxConstraints(constraints))!; |
642 | final SliverGridParentData childParentData = child.parentData! as SliverGridParentData; |
643 | childParentData.layoutOffset = gridGeometry.scrollOffset; |
644 | childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; |
645 | assert(childParentData.index == index); |
646 | trailingChildWithLayout ??= child; |
647 | trailingScrollOffset = math.max(trailingScrollOffset, gridGeometry.trailingScrollOffset); |
648 | } |
649 | |
650 | if (trailingChildWithLayout == null) { |
651 | firstChild!.layout(firstChildGridGeometry.getBoxConstraints(constraints)); |
652 | final SliverGridParentData childParentData = firstChild!.parentData! as SliverGridParentData; |
653 | childParentData.layoutOffset = firstChildGridGeometry.scrollOffset; |
654 | childParentData.crossAxisOffset = firstChildGridGeometry.crossAxisOffset; |
655 | trailingChildWithLayout = firstChild; |
656 | } |
657 | |
658 | for ( |
659 | int index = indexOf(trailingChildWithLayout!) + 1; |
660 | targetLastIndex == null || index <= targetLastIndex; |
661 | ++index |
662 | ) { |
663 | final SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index); |
664 | final BoxConstraints childConstraints = gridGeometry.getBoxConstraints(constraints); |
665 | RenderBox? child = childAfter(trailingChildWithLayout!); |
666 | if (child == null || indexOf(child) != index) { |
667 | child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout); |
668 | if (child == null) { |
669 | reachedEnd = true; |
670 | // We have run out of children. |
671 | break; |
672 | } |
673 | } else { |
674 | child.layout(childConstraints); |
675 | } |
676 | trailingChildWithLayout = child; |
677 | final SliverGridParentData childParentData = child.parentData! as SliverGridParentData; |
678 | childParentData.layoutOffset = gridGeometry.scrollOffset; |
679 | childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; |
680 | assert(childParentData.index == index); |
681 | trailingScrollOffset = math.max(trailingScrollOffset, gridGeometry.trailingScrollOffset); |
682 | } |
683 | |
684 | final int lastIndex = indexOf(lastChild!); |
685 | |
686 | assert(debugAssertChildListIsNonEmptyAndContiguous()); |
687 | assert(indexOf(firstChild!) == firstIndex); |
688 | assert(targetLastIndex == null || lastIndex <= targetLastIndex); |
689 | |
690 | final double estimatedTotalExtent = |
691 | reachedEnd |
692 | ? trailingScrollOffset |
693 | : childManager.estimateMaxScrollOffset( |
694 | constraints, |
695 | firstIndex: firstIndex, |
696 | lastIndex: lastIndex, |
697 | leadingScrollOffset: leadingScrollOffset, |
698 | trailingScrollOffset: trailingScrollOffset, |
699 | ); |
700 | final double paintExtent = calculatePaintOffset( |
701 | constraints, |
702 | from: math.min(constraints.scrollOffset, leadingScrollOffset), |
703 | to: trailingScrollOffset, |
704 | ); |
705 | final double cacheExtent = calculateCacheOffset( |
706 | constraints, |
707 | from: leadingScrollOffset, |
708 | to: trailingScrollOffset, |
709 | ); |
710 | |
711 | geometry = SliverGeometry( |
712 | scrollExtent: estimatedTotalExtent, |
713 | paintExtent: paintExtent, |
714 | maxPaintExtent: estimatedTotalExtent, |
715 | cacheExtent: cacheExtent, |
716 | hasVisualOverflow: |
717 | estimatedTotalExtent > paintExtent || |
718 | constraints.scrollOffset > 0.0 || |
719 | constraints.overlap != 0.0, |
720 | ); |
721 | |
722 | // We may have started the layout while scrolled to the end, which |
723 | // would not expose a new child. |
724 | if (estimatedTotalExtent == trailingScrollOffset) { |
725 | childManager.setDidUnderflow(true); |
726 | } |
727 | childManager.didFinishLayout(); |
728 | } |
729 | } |
730 |
Definitions
- SliverGridGeometry
- SliverGridGeometry
- trailingScrollOffset
- getBoxConstraints
- toString
- SliverGridLayout
- SliverGridLayout
- getMinChildIndexForScrollOffset
- getMaxChildIndexForScrollOffset
- getGeometryForChildIndex
- computeMaxScrollOffset
- SliverGridRegularTileLayout
- SliverGridRegularTileLayout
- getMinChildIndexForScrollOffset
- getMaxChildIndexForScrollOffset
- _getOffsetFromStartInCrossAxis
- getGeometryForChildIndex
- computeMaxScrollOffset
- SliverGridDelegate
- SliverGridDelegate
- getLayout
- shouldRelayout
- SliverGridDelegateWithFixedCrossAxisCount
- SliverGridDelegateWithFixedCrossAxisCount
- _debugAssertIsValid
- getLayout
- shouldRelayout
- SliverGridDelegateWithMaxCrossAxisExtent
- SliverGridDelegateWithMaxCrossAxisExtent
- _debugAssertIsValid
- getLayout
- shouldRelayout
- SliverGridParentData
- toString
- RenderSliverGrid
- RenderSliverGrid
- setupParentData
- gridDelegate
- gridDelegate
- childCrossAxisPosition
Learn more about Flutter for embedded and desktop on industrialflutter.com