| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:math' as math; |
| |
| import 'package:flutter/foundation.dart'; |
| |
| import 'box.dart'; |
| import 'object.dart'; |
| import 'sliver.dart'; |
| import 'sliver_multi_box_adaptor.dart'; |
| |
| /// Describes the placement of a child in a [RenderSliverGrid]. |
| /// |
| /// See also: |
| /// |
| /// * [SliverGridLayout], which represents the geometry of all the tiles in a |
| /// grid. |
| /// * [SliverGridLayout.getGeometryForChildIndex], which returns this object |
| /// to describe the child's placement. |
| /// * [RenderSliverGrid], which uses this class during its |
| /// [RenderSliverGrid.performLayout] method. |
| @immutable |
| class SliverGridGeometry { |
| /// Creates an object that describes the placement of a child in a [RenderSliverGrid]. |
| const SliverGridGeometry({ |
| @required this.scrollOffset, |
| @required this.crossAxisOffset, |
| @required this.mainAxisExtent, |
| @required this.crossAxisExtent, |
| }); |
| |
| /// The scroll offset of the leading edge of the child relative to the leading |
| /// edge of the parent. |
| final double scrollOffset; |
| |
| /// The offset of the child in the non-scrolling axis. |
| /// |
| /// If the scroll axis is vertical, this offset is from the left-most edge of |
| /// the parent to the left-most edge of the child. If the scroll axis is |
| /// horizontal, this offset is from the top-most edge of the parent to the |
| /// top-most edge of the child. |
| final double crossAxisOffset; |
| |
| /// The extent of the child in the scrolling axis. |
| /// |
| /// If the scroll axis is vertical, this extent is the child's height. If the |
| /// scroll axis is horizontal, this extent is the child's width. |
| final double mainAxisExtent; |
| |
| /// The extent of the child in the non-scrolling axis. |
| /// |
| /// If the scroll axis is vertical, this extent is the child's width. If the |
| /// scroll axis is horizontal, this extent is the child's height. |
| final double crossAxisExtent; |
| |
| /// The scroll offset of the trailing edge of the child relative to the |
| /// leading edge of the parent. |
| double get trailingScrollOffset => scrollOffset + mainAxisExtent; |
| |
| /// Returns a tight [BoxConstraints] that forces the child to have the |
| /// required size. |
| BoxConstraints getBoxConstraints(SliverConstraints constraints) { |
| return constraints.asBoxConstraints( |
| minExtent: mainAxisExtent, |
| maxExtent: mainAxisExtent, |
| crossAxisExtent: crossAxisExtent, |
| ); |
| } |
| |
| @override |
| String toString() { |
| final List<String> properties = <String>[ |
| 'scrollOffset: $scrollOffset', |
| 'crossAxisOffset: $crossAxisOffset', |
| 'mainAxisExtent: $mainAxisExtent', |
| 'crossAxisExtent: $crossAxisExtent', |
| ]; |
| return 'SliverGridGeometry(${properties.join(', ')})'; |
| } |
| } |
| |
| /// The size and position of all the tiles in a [RenderSliverGrid]. |
| /// |
| /// Rather that providing a grid with a [SliverGridLayout] directly, you instead |
| /// provide the grid a [SliverGridDelegate], which can compute a |
| /// [SliverGridLayout] given the current [SliverConstraints]. |
| /// |
| /// The tiles can be placed arbitrarily, but it is more efficient to place tiles |
| /// in roughly in order by scroll offset because grids reify a contiguous |
| /// sequence of children. |
| /// |
| /// See also: |
| /// |
| /// * [SliverGridRegularTileLayout], which represents a layout that uses |
| /// equally sized and spaced tiles. |
| /// * [SliverGridGeometry], which represents the size and position of a single |
| /// tile in a grid. |
| /// * [SliverGridDelegate.getLayout], which returns this object to describe the |
| /// delegate's layout. |
| /// * [RenderSliverGrid], which uses this class during its |
| /// [RenderSliverGrid.performLayout] method. |
| @immutable |
| abstract class SliverGridLayout { |
| /// Abstract const constructor. This constructor enables subclasses to provide |
| /// const constructors so that they can be used in const expressions. |
| const SliverGridLayout(); |
| |
| /// The minimum child index that is visible at (or after) this scroll offset. |
| int getMinChildIndexForScrollOffset(double scrollOffset); |
| |
| /// The maximum child index that is visible at (or before) this scroll offset. |
| int getMaxChildIndexForScrollOffset(double scrollOffset); |
| |
| /// The size and position of the child with the given index. |
| SliverGridGeometry getGeometryForChildIndex(int index); |
| |
| /// The scroll extent needed to fully display all the tiles if there are |
| /// `childCount` children in total. |
| /// |
| /// The child count will never be null. |
| double computeMaxScrollOffset(int childCount); |
| } |
| |
| /// A [SliverGridLayout] that uses equally sized and spaced tiles. |
| /// |
| /// Rather that providing a grid with a [SliverGridLayout] directly, you instead |
| /// provide the grid a [SliverGridDelegate], which can compute a |
| /// [SliverGridLayout] given the current [SliverConstraints]. |
| /// |
| /// This layout is used by [SliverGridDelegateWithFixedCrossAxisCount] and |
| /// [SliverGridDelegateWithMaxCrossAxisExtent]. |
| /// |
| /// See also: |
| /// |
| /// * [SliverGridDelegateWithFixedCrossAxisCount], which uses this layout. |
| /// * [SliverGridDelegateWithMaxCrossAxisExtent], which uses this layout. |
| /// * [SliverGridLayout], which represents an arbitrary tile layout. |
| /// * [SliverGridGeometry], which represents the size and position of a single |
| /// tile in a grid. |
| /// * [SliverGridDelegate.getLayout], which returns this object to describe the |
| /// delegate's layout. |
| /// * [RenderSliverGrid], which uses this class during its |
| /// [RenderSliverGrid.performLayout] method. |
| class SliverGridRegularTileLayout extends SliverGridLayout { |
| /// Creates a layout that uses equally sized and spaced tiles. |
| /// |
| /// All of the arguments must not be null and must not be negative. The |
| /// `crossAxisCount` argument must be greater than zero. |
| const SliverGridRegularTileLayout({ |
| @required this.crossAxisCount, |
| @required this.mainAxisStride, |
| @required this.crossAxisStride, |
| @required this.childMainAxisExtent, |
| @required this.childCrossAxisExtent, |
| @required this.reverseCrossAxis, |
| }) : assert(crossAxisCount != null && crossAxisCount > 0), |
| assert(mainAxisStride != null && mainAxisStride >= 0), |
| assert(crossAxisStride != null && crossAxisStride >= 0), |
| assert(childMainAxisExtent != null && childMainAxisExtent >= 0), |
| assert(childCrossAxisExtent != null && childCrossAxisExtent >= 0), |
| assert(reverseCrossAxis != null); |
| |
| /// The number of children in the cross axis. |
| final int crossAxisCount; |
| |
| /// The number of pixels from the leading edge of one tile to the leading edge |
| /// of the next tile in the main axis. |
| final double mainAxisStride; |
| |
| /// The number of pixels from the leading edge of one tile to the leading edge |
| /// of the next tile in the cross axis. |
| final double crossAxisStride; |
| |
| /// The number of pixels from the leading edge of one tile to the trailing |
| /// edge of the same tile in the main axis. |
| final double childMainAxisExtent; |
| |
| /// The number of pixels from the leading edge of one tile to the trailing |
| /// edge of the same tile in the cross axis. |
| final double childCrossAxisExtent; |
| |
| /// Whether the children should be placed in the opposite order of increasing |
| /// coordinates in the cross axis. |
| /// |
| /// For example, if the cross axis is horizontal, the children are placed from |
| /// left to right when [reverseCrossAxis] is false and from right to left when |
| /// [reverseCrossAxis] is true. |
| /// |
| /// Typically set to the return value of [axisDirectionIsReversed] applied to |
| /// the [SliverConstraints.crossAxisDirection]. |
| final bool reverseCrossAxis; |
| |
| @override |
| int getMinChildIndexForScrollOffset(double scrollOffset) { |
| return mainAxisStride > 0.0 ? crossAxisCount * (scrollOffset ~/ mainAxisStride) : 0; |
| } |
| |
| @override |
| int getMaxChildIndexForScrollOffset(double scrollOffset) { |
| if (mainAxisStride > 0.0) { |
| final int mainAxisCount = (scrollOffset / mainAxisStride).ceil(); |
| return math.max(0, crossAxisCount * mainAxisCount - 1); |
| } |
| return 0; |
| } |
| |
| double _getOffsetFromStartInCrossAxis(double crossAxisStart) { |
| if (reverseCrossAxis) |
| return crossAxisCount * crossAxisStride - crossAxisStart - childCrossAxisExtent - (crossAxisStride - childCrossAxisExtent); |
| return crossAxisStart; |
| } |
| |
| @override |
| SliverGridGeometry getGeometryForChildIndex(int index) { |
| final double crossAxisStart = (index % crossAxisCount) * crossAxisStride; |
| return SliverGridGeometry( |
| scrollOffset: (index ~/ crossAxisCount) * mainAxisStride, |
| crossAxisOffset: _getOffsetFromStartInCrossAxis(crossAxisStart), |
| mainAxisExtent: childMainAxisExtent, |
| crossAxisExtent: childCrossAxisExtent, |
| ); |
| } |
| |
| @override |
| double computeMaxScrollOffset(int childCount) { |
| assert(childCount != null); |
| final int mainAxisCount = ((childCount - 1) ~/ crossAxisCount) + 1; |
| final double mainAxisSpacing = mainAxisStride - childMainAxisExtent; |
| return mainAxisStride * mainAxisCount - mainAxisSpacing; |
| } |
| } |
| |
| /// Controls the layout of tiles in a grid. |
| /// |
| /// Given the current constraints on the grid, a [SliverGridDelegate] computes |
| /// the layout for the tiles in the grid. The tiles can be placed arbitrarily, |
| /// but it is more efficient to place tiles in roughly in order by scroll offset |
| /// because grids reify a contiguous sequence of children. |
| /// |
| /// See also: |
| /// |
| /// * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with |
| /// a fixed number of tiles in the cross axis. |
| /// * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with |
| /// tiles that have a maximum cross-axis extent. |
| /// * [GridView], which uses this delegate to control the layout of its tiles. |
| /// * [SliverGrid], which uses this delegate to control the layout of its |
| /// tiles. |
| /// * [RenderSliverGrid], which uses this delegate to control the layout of its |
| /// tiles. |
| abstract class SliverGridDelegate { |
| /// Abstract const constructor. This constructor enables subclasses to provide |
| /// const constructors so that they can be used in const expressions. |
| const SliverGridDelegate(); |
| |
| /// Returns information about the size and position of the tiles in the grid. |
| SliverGridLayout getLayout(SliverConstraints constraints); |
| |
| /// Override this method to return true when the children need to be |
| /// laid out. |
| /// |
| /// This should compare the fields of the current delegate and the given |
| /// `oldDelegate` and return true if the fields are such that the layout would |
| /// be different. |
| bool shouldRelayout(covariant SliverGridDelegate oldDelegate); |
| } |
| |
| /// Creates grid layouts with a fixed number of tiles in the cross axis. |
| /// |
| /// For example, if the grid is vertical, this delegate will create a layout |
| /// with a fixed number of columns. If the grid is horizontal, this delegate |
| /// will create a layout with a fixed number of rows. |
| /// |
| /// This delegate creates grids with equally sized and spaced tiles. |
| /// |
| /// See also: |
| /// |
| /// * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with |
| /// tiles that have a maximum cross-axis extent. |
| /// * [SliverGridDelegate], which creates arbitrary layouts. |
| /// * [GridView], which can use this delegate to control the layout of its |
| /// tiles. |
| /// * [SliverGrid], which can use this delegate to control the layout of its |
| /// tiles. |
| /// * [RenderSliverGrid], which can use this delegate to control the layout of |
| /// its tiles. |
| class SliverGridDelegateWithFixedCrossAxisCount extends SliverGridDelegate { |
| /// Creates a delegate that makes grid layouts with a fixed number of tiles in |
| /// the cross axis. |
| /// |
| /// All of the arguments must not be null. The `mainAxisSpacing` and |
| /// `crossAxisSpacing` arguments must not be negative. The `crossAxisCount` |
| /// and `childAspectRatio` arguments must be greater than zero. |
| const SliverGridDelegateWithFixedCrossAxisCount({ |
| @required this.crossAxisCount, |
| this.mainAxisSpacing = 0.0, |
| this.crossAxisSpacing = 0.0, |
| this.childAspectRatio = 1.0, |
| }) : assert(crossAxisCount != null && crossAxisCount > 0), |
| assert(mainAxisSpacing != null && mainAxisSpacing >= 0), |
| assert(crossAxisSpacing != null && crossAxisSpacing >= 0), |
| assert(childAspectRatio != null && childAspectRatio > 0); |
| |
| /// The number of children in the cross axis. |
| final int crossAxisCount; |
| |
| /// The number of logical pixels between each child along the main axis. |
| final double mainAxisSpacing; |
| |
| /// The number of logical pixels between each child along the cross axis. |
| final double crossAxisSpacing; |
| |
| /// The ratio of the cross-axis to the main-axis extent of each child. |
| final double childAspectRatio; |
| |
| bool _debugAssertIsValid() { |
| assert(crossAxisCount > 0); |
| assert(mainAxisSpacing >= 0.0); |
| assert(crossAxisSpacing >= 0.0); |
| assert(childAspectRatio > 0.0); |
| return true; |
| } |
| |
| @override |
| SliverGridLayout getLayout(SliverConstraints constraints) { |
| assert(_debugAssertIsValid()); |
| final double usableCrossAxisExtent = constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1); |
| final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount; |
| final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio; |
| return SliverGridRegularTileLayout( |
| crossAxisCount: crossAxisCount, |
| mainAxisStride: childMainAxisExtent + mainAxisSpacing, |
| crossAxisStride: childCrossAxisExtent + crossAxisSpacing, |
| childMainAxisExtent: childMainAxisExtent, |
| childCrossAxisExtent: childCrossAxisExtent, |
| reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), |
| ); |
| } |
| |
| @override |
| bool shouldRelayout(SliverGridDelegateWithFixedCrossAxisCount oldDelegate) { |
| return oldDelegate.crossAxisCount != crossAxisCount |
| || oldDelegate.mainAxisSpacing != mainAxisSpacing |
| || oldDelegate.crossAxisSpacing != crossAxisSpacing |
| || oldDelegate.childAspectRatio != childAspectRatio; |
| } |
| } |
| |
| /// Creates grid layouts with tiles that each have a maximum cross-axis extent. |
| /// |
| /// This delegate will select a cross-axis extent for the tiles that is as |
| /// large as possible subject to the following conditions: |
| /// |
| /// - The extent evenly divides the cross-axis extent of the grid. |
| /// - The extent is at most [maxCrossAxisExtent]. |
| /// |
| /// For example, if the grid is vertical, the grid is 500.0 pixels wide, and |
| /// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4 |
| /// columns that are 125.0 pixels wide. |
| /// |
| /// This delegate creates grids with equally sized and spaced tiles. |
| /// |
| /// See also: |
| /// |
| /// * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with |
| /// a fixed number of tiles in the cross axis. |
| /// * [SliverGridDelegate], which creates arbitrary layouts. |
| /// * [GridView], which can use this delegate to control the layout of its |
| /// tiles. |
| /// * [SliverGrid], which can use this delegate to control the layout of its |
| /// tiles. |
| /// * [RenderSliverGrid], which can use this delegate to control the layout of |
| /// its tiles. |
| class SliverGridDelegateWithMaxCrossAxisExtent extends SliverGridDelegate { |
| /// Creates a delegate that makes grid layouts with tiles that have a maximum |
| /// cross-axis extent. |
| /// |
| /// All of the arguments must not be null. The [maxCrossAxisExtent] and |
| /// [mainAxisSpacing], and [crossAxisSpacing] arguments must not be negative. |
| /// The [childAspectRatio] argument must be greater than zero. |
| const SliverGridDelegateWithMaxCrossAxisExtent({ |
| @required this.maxCrossAxisExtent, |
| this.mainAxisSpacing = 0.0, |
| this.crossAxisSpacing = 0.0, |
| this.childAspectRatio = 1.0, |
| }) : assert(maxCrossAxisExtent != null && maxCrossAxisExtent >= 0), |
| assert(mainAxisSpacing != null && mainAxisSpacing >= 0), |
| assert(crossAxisSpacing != null && crossAxisSpacing >= 0), |
| assert(childAspectRatio != null && childAspectRatio > 0); |
| |
| /// The maximum extent of tiles in the cross axis. |
| /// |
| /// This delegate will select a cross-axis extent for the tiles that is as |
| /// large as possible subject to the following conditions: |
| /// |
| /// - The extent evenly divides the cross-axis extent of the grid. |
| /// - The extent is at most [maxCrossAxisExtent]. |
| /// |
| /// For example, if the grid is vertical, the grid is 500.0 pixels wide, and |
| /// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4 |
| /// columns that are 125.0 pixels wide. |
| final double maxCrossAxisExtent; |
| |
| /// The number of logical pixels between each child along the main axis. |
| final double mainAxisSpacing; |
| |
| /// The number of logical pixels between each child along the cross axis. |
| final double crossAxisSpacing; |
| |
| /// The ratio of the cross-axis to the main-axis extent of each child. |
| final double childAspectRatio; |
| |
| bool _debugAssertIsValid() { |
| assert(maxCrossAxisExtent > 0.0); |
| assert(mainAxisSpacing >= 0.0); |
| assert(crossAxisSpacing >= 0.0); |
| assert(childAspectRatio > 0.0); |
| return true; |
| } |
| |
| @override |
| SliverGridLayout getLayout(SliverConstraints constraints) { |
| assert(_debugAssertIsValid()); |
| final int crossAxisCount = (constraints.crossAxisExtent / (maxCrossAxisExtent + crossAxisSpacing)).ceil(); |
| final double usableCrossAxisExtent = constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1); |
| final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount; |
| final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio; |
| return SliverGridRegularTileLayout( |
| crossAxisCount: crossAxisCount, |
| mainAxisStride: childMainAxisExtent + mainAxisSpacing, |
| crossAxisStride: childCrossAxisExtent + crossAxisSpacing, |
| childMainAxisExtent: childMainAxisExtent, |
| childCrossAxisExtent: childCrossAxisExtent, |
| reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), |
| ); |
| } |
| |
| @override |
| bool shouldRelayout(SliverGridDelegateWithMaxCrossAxisExtent oldDelegate) { |
| return oldDelegate.maxCrossAxisExtent != maxCrossAxisExtent |
| || oldDelegate.mainAxisSpacing != mainAxisSpacing |
| || oldDelegate.crossAxisSpacing != crossAxisSpacing |
| || oldDelegate.childAspectRatio != childAspectRatio; |
| } |
| } |
| |
| /// Parent data structure used by [RenderSliverGrid]. |
| class SliverGridParentData extends SliverMultiBoxAdaptorParentData { |
| /// The offset of the child in the non-scrolling axis. |
| /// |
| /// If the scroll axis is vertical, this offset is from the left-most edge of |
| /// the parent to the left-most edge of the child. If the scroll axis is |
| /// horizontal, this offset is from the top-most edge of the parent to the |
| /// top-most edge of the child. |
| double crossAxisOffset; |
| |
| @override |
| String toString() => 'crossAxisOffset=$crossAxisOffset; ${super.toString()}'; |
| } |
| |
| /// A sliver that places multiple box children in a two dimensional arrangement. |
| /// |
| /// [RenderSliverGrid] places its children in arbitrary positions determined by |
| /// [gridDelegate]. Each child is forced to have the size specified by the |
| /// [gridDelegate]. |
| /// |
| /// See also: |
| /// |
| /// * [RenderSliverList], which places its children in a linear |
| /// array. |
| /// * [RenderSliverFixedExtentList], which places its children in a linear |
| /// array with a fixed extent in the main axis. |
| class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { |
| /// Creates a sliver that contains multiple box children that whose size and |
| /// position are determined by a delegate. |
| /// |
| /// The [childManager] and [gridDelegate] arguments must not be null. |
| RenderSliverGrid({ |
| @required RenderSliverBoxChildManager childManager, |
| @required SliverGridDelegate gridDelegate, |
| }) : assert(gridDelegate != null), |
| _gridDelegate = gridDelegate, |
| super(childManager: childManager); |
| |
| @override |
| void setupParentData(RenderObject child) { |
| if (child.parentData is! SliverGridParentData) |
| child.parentData = SliverGridParentData(); |
| } |
| |
| /// The delegate that controls the size and position of the children. |
| SliverGridDelegate get gridDelegate => _gridDelegate; |
| SliverGridDelegate _gridDelegate; |
| set gridDelegate(SliverGridDelegate value) { |
| assert(value != null); |
| if (_gridDelegate == value) |
| return; |
| if (value.runtimeType != _gridDelegate.runtimeType || |
| value.shouldRelayout(_gridDelegate)) |
| markNeedsLayout(); |
| _gridDelegate = value; |
| } |
| |
| @override |
| double childCrossAxisPosition(RenderBox child) { |
| final SliverGridParentData childParentData = child.parentData as SliverGridParentData; |
| return childParentData.crossAxisOffset; |
| } |
| |
| @override |
| void performLayout() { |
| final SliverConstraints constraints = this.constraints; |
| childManager.didStartLayout(); |
| childManager.setDidUnderflow(false); |
| |
| final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin; |
| assert(scrollOffset >= 0.0); |
| final double remainingExtent = constraints.remainingCacheExtent; |
| assert(remainingExtent >= 0.0); |
| final double targetEndScrollOffset = scrollOffset + remainingExtent; |
| |
| final SliverGridLayout layout = _gridDelegate.getLayout(constraints); |
| |
| final int firstIndex = layout.getMinChildIndexForScrollOffset(scrollOffset); |
| final int targetLastIndex = targetEndScrollOffset.isFinite ? |
| layout.getMaxChildIndexForScrollOffset(targetEndScrollOffset) : null; |
| |
| if (firstChild != null) { |
| final int oldFirstIndex = indexOf(firstChild); |
| final int oldLastIndex = indexOf(lastChild); |
| final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount) as int; |
| final int trailingGarbage = targetLastIndex == null |
| ? 0 |
| : ((oldLastIndex - targetLastIndex).clamp(0, childCount) as int); |
| collectGarbage(leadingGarbage, trailingGarbage); |
| } else { |
| collectGarbage(0, 0); |
| } |
| |
| final SliverGridGeometry firstChildGridGeometry = layout.getGeometryForChildIndex(firstIndex); |
| final double leadingScrollOffset = firstChildGridGeometry.scrollOffset; |
| double trailingScrollOffset = firstChildGridGeometry.trailingScrollOffset; |
| |
| if (firstChild == null) { |
| if (!addInitialChild(index: firstIndex, layoutOffset: firstChildGridGeometry.scrollOffset)) { |
| // There are either no children, or we are past the end of all our children. |
| final double max = layout.computeMaxScrollOffset(childManager.childCount); |
| geometry = SliverGeometry( |
| scrollExtent: max, |
| maxPaintExtent: max, |
| ); |
| childManager.didFinishLayout(); |
| return; |
| } |
| } |
| |
| RenderBox trailingChildWithLayout; |
| |
| for (int index = indexOf(firstChild) - 1; index >= firstIndex; --index) { |
| final SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index); |
| final RenderBox child = insertAndLayoutLeadingChild( |
| gridGeometry.getBoxConstraints(constraints), |
| ); |
| final SliverGridParentData childParentData = child.parentData as SliverGridParentData; |
| childParentData.layoutOffset = gridGeometry.scrollOffset; |
| childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; |
| assert(childParentData.index == index); |
| trailingChildWithLayout ??= child; |
| trailingScrollOffset = math.max(trailingScrollOffset, gridGeometry.trailingScrollOffset); |
| } |
| |
| if (trailingChildWithLayout == null) { |
| firstChild.layout(firstChildGridGeometry.getBoxConstraints(constraints)); |
| final SliverGridParentData childParentData = firstChild.parentData as SliverGridParentData; |
| childParentData.layoutOffset = firstChildGridGeometry.scrollOffset; |
| childParentData.crossAxisOffset = firstChildGridGeometry.crossAxisOffset; |
| trailingChildWithLayout = firstChild; |
| } |
| |
| for (int index = indexOf(trailingChildWithLayout) + 1; targetLastIndex == null || index <= targetLastIndex; ++index) { |
| final SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index); |
| final BoxConstraints childConstraints = gridGeometry.getBoxConstraints(constraints); |
| RenderBox child = childAfter(trailingChildWithLayout); |
| if (child == null || indexOf(child) != index) { |
| child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout); |
| if (child == null) { |
| // We have run out of children. |
| break; |
| } |
| } else { |
| child.layout(childConstraints); |
| } |
| trailingChildWithLayout = child; |
| assert(child != null); |
| final SliverGridParentData childParentData = child.parentData as SliverGridParentData; |
| childParentData.layoutOffset = gridGeometry.scrollOffset; |
| childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; |
| assert(childParentData.index == index); |
| trailingScrollOffset = math.max(trailingScrollOffset, gridGeometry.trailingScrollOffset); |
| } |
| |
| final int lastIndex = indexOf(lastChild); |
| |
| assert(childScrollOffset(firstChild) <= scrollOffset); |
| assert(debugAssertChildListIsNonEmptyAndContiguous()); |
| assert(indexOf(firstChild) == firstIndex); |
| assert(targetLastIndex == null || lastIndex <= targetLastIndex); |
| |
| final double estimatedTotalExtent = childManager.estimateMaxScrollOffset( |
| constraints, |
| firstIndex: firstIndex, |
| lastIndex: lastIndex, |
| leadingScrollOffset: leadingScrollOffset, |
| trailingScrollOffset: trailingScrollOffset, |
| ); |
| |
| final double paintExtent = calculatePaintOffset( |
| constraints, |
| from: leadingScrollOffset, |
| to: trailingScrollOffset, |
| ); |
| final double cacheExtent = calculateCacheOffset( |
| constraints, |
| from: leadingScrollOffset, |
| to: trailingScrollOffset, |
| ); |
| |
| geometry = SliverGeometry( |
| scrollExtent: estimatedTotalExtent, |
| paintExtent: paintExtent, |
| maxPaintExtent: estimatedTotalExtent, |
| cacheExtent: cacheExtent, |
| // Conservative to avoid complexity. |
| hasVisualOverflow: true, |
| ); |
| |
| // We may have started the layout while scrolled to the end, which |
| // would not expose a new child. |
| if (estimatedTotalExtent == trailingScrollOffset) |
| childManager.setDidUnderflow(true); |
| childManager.didFinishLayout(); |
| } |
| } |