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';
9library;
10
11import 'dart:math' as math;
12
13import 'package:flutter/foundation.dart';
14
15import 'box.dart';
16import 'object.dart';
17import 'sliver.dart';
18import '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
41class 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
130abstract 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.
171class 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.
294abstract 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.
346class 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.
445class 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].
536class 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.
561class 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

Provided by KDAB

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