| // Copyright 2013 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 'package:flutter/rendering.dart'; |
| |
| import 'base_grid_layout.dart'; |
| |
| // The model that tracks the current max size of the Sliver in the mainAxis and |
| // tracks if there is still space on the crossAxis. |
| class _RunMetrics { |
| _RunMetrics({ |
| required this.maxSliver, |
| required this.currentSizeUsed, |
| required this.scrollOffset, |
| }); |
| |
| /// The biggest sliver size for the current run. |
| double maxSliver; |
| |
| /// The current size that has been used in the current run. |
| double currentSizeUsed; |
| |
| /// The scroll offset in the current run. |
| double scrollOffset; |
| } |
| |
| /// A [DynamicSliverGridLayout] that uses dynamically sized tiles. |
| /// |
| /// Rather that providing a grid with a [DynamicSliverGridLayout] directly, instead |
| /// provide the grid a [SliverGridDelegate], which can compute a |
| /// [DynamicSliverGridLayout] given the current [SliverConstraints]. |
| /// |
| /// This layout is used by [SliverGridDelegateWithWrapping]. |
| /// |
| /// See also: |
| /// |
| /// * [SliverGridDelegateWithWrapping], which uses this layout. |
| /// * [DynamicSliverGridLayout], which represents an arbitrary dynamic tile layout. |
| /// * [DynamicSliverGridGeometry], 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. |
| /// * [RenderDynamicSliverGrid], which uses this class during its |
| /// [RenderDynamicSliverGrid.performLayout] method. |
| class SliverGridWrappingTileLayout extends DynamicSliverGridLayout { |
| /// Creates a layout that uses dynamic sized and spaced tiles. |
| /// |
| /// All of the arguments must not be null and must not be negative. |
| SliverGridWrappingTileLayout({ |
| required this.mainAxisSpacing, |
| required this.crossAxisSpacing, |
| required this.childMainAxisExtent, |
| required this.childCrossAxisExtent, |
| required this.crossAxisExtent, |
| required this.scrollDirection, |
| }) : assert(mainAxisSpacing != null && mainAxisSpacing >= 0), |
| assert(crossAxisSpacing != null && crossAxisSpacing >= 0), |
| assert(childMainAxisExtent != null && childMainAxisExtent >= 0), |
| assert(childCrossAxisExtent != null && childCrossAxisExtent >= 0), |
| assert(crossAxisExtent != null && crossAxisExtent >= 0), |
| assert(scrollDirection != null && |
| (scrollDirection == Axis.horizontal || |
| scrollDirection == Axis.vertical)); |
| |
| /// The direction in which the layout should be built. |
| final Axis scrollDirection; |
| |
| /// The extent of the child in the non-scrolling axis. |
| final double crossAxisExtent; |
| |
| /// 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 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; |
| |
| /// The model that is used internally to keep track of how much space is left |
| /// and how much has been used. |
| final List<_RunMetrics> _model = <_RunMetrics>[ |
| _RunMetrics(maxSliver: 0.0, currentSizeUsed: 0.0, scrollOffset: 0.0) |
| ]; |
| |
| // This method provides the initial constraints for the child to layout, |
| // and then it is updated with the final size later in |
| // updateGeometryForChildIndex. |
| @override |
| DynamicSliverGridGeometry getGeometryForChildIndex(int index) { |
| return DynamicSliverGridGeometry( |
| scrollOffset: 0, |
| crossAxisOffset: 0, |
| mainAxisExtent: childMainAxisExtent, |
| crossAxisExtent: childCrossAxisExtent, |
| ); |
| } |
| |
| @override |
| DynamicSliverGridGeometry updateGeometryForChildIndex( |
| int index, |
| Size childSize, |
| ) { |
| final double scrollOffset = _model.last.scrollOffset; |
| final double currentSizeUsed = _model.last.currentSizeUsed; |
| late final double addedSize; |
| |
| switch (scrollDirection) { |
| case Axis.vertical: |
| addedSize = currentSizeUsed + childSize.width + crossAxisSpacing; |
| break; |
| case Axis.horizontal: |
| addedSize = currentSizeUsed + childSize.height + mainAxisSpacing; |
| break; |
| } |
| |
| if (addedSize > crossAxisExtent && _model.last.currentSizeUsed > 0.0) { |
| switch (scrollDirection) { |
| case Axis.vertical: |
| _model.add( |
| _RunMetrics( |
| maxSliver: childSize.height + mainAxisSpacing, |
| currentSizeUsed: childSize.width + crossAxisSpacing, |
| scrollOffset: |
| scrollOffset + _model.last.maxSliver + mainAxisSpacing, |
| ), |
| ); |
| break; |
| case Axis.horizontal: |
| _model.add( |
| _RunMetrics( |
| maxSliver: childSize.width + crossAxisSpacing, |
| currentSizeUsed: childSize.height + mainAxisSpacing, |
| scrollOffset: |
| scrollOffset + _model.last.maxSliver + crossAxisSpacing, |
| ), |
| ); |
| break; |
| } |
| |
| return DynamicSliverGridGeometry( |
| scrollOffset: _model.last.scrollOffset, |
| crossAxisOffset: 0.0, |
| mainAxisExtent: childSize.height + mainAxisSpacing, |
| crossAxisExtent: childSize.width + crossAxisSpacing, |
| ); |
| } else { |
| _model.last.currentSizeUsed = addedSize; |
| } |
| |
| switch (scrollDirection) { |
| case Axis.vertical: |
| if (childSize.height + mainAxisSpacing > _model.last.maxSliver) { |
| _model.last.maxSliver = childSize.height + mainAxisSpacing; |
| } |
| break; |
| case Axis.horizontal: |
| if (childSize.width + crossAxisSpacing > _model.last.maxSliver) { |
| _model.last.maxSliver = childSize.width + crossAxisSpacing; |
| } |
| break; |
| } |
| |
| return DynamicSliverGridGeometry( |
| scrollOffset: scrollOffset, |
| crossAxisOffset: currentSizeUsed, |
| mainAxisExtent: childSize.height, |
| crossAxisExtent: childSize.width, |
| ); |
| } |
| |
| @override |
| bool reachedTargetScrollOffset(double targetOffset) { |
| return _model.last.scrollOffset > targetOffset; |
| } |
| } |
| |
| /// A [SliverGridDelegate] for creating grids that wrap variably sized tiles. |
| /// |
| /// For example, if the grid is vertical, this delegate will create a layout |
| /// where the children are laid out until they fill the horizontal axis and then |
| /// they continue in the next row. If the grid is horizontal, this delegate will |
| /// do the same but it will fill the vertical axis and will pass to another |
| /// column until it finishes. |
| /// |
| /// This delegate creates grids with different sized tiles. Tiles |
| /// can have fixed dimensions if [childCrossAxisExtent] or |
| /// [childMainAxisExtent] are provided. |
| /// |
| /// See also: |
| /// * [DynamicGridView.wrap], a constructor to use with this [SliverGridDelegate], |
| /// like `GridView.extent`. |
| /// * [DynamicGridView], which can use this delegate to control the layout of its |
| /// tiles. |
| /// * [RenderDynamicSliverGrid], which can use this delegate to control the |
| /// layout of its tiles. |
| class SliverGridDelegateWithWrapping extends SliverGridDelegate { |
| /// Create a delegate that wraps variably sized tiles. |
| /// |
| /// The children widgets are provided with loose constraints, and if any of the |
| /// extent parameters are set, the children are given tight constraints. |
| /// The way that children are made to have loose constraints is by assigning |
| /// the value of [double.infinity] to [childMainAxisExtent] and |
| /// [childCrossAxisExtent]. |
| /// To have same sized tiles with the wrapping, specify the [childCrossAxisExtent] |
| /// and the [childMainAxisExtent] to be the same size. Or only one of them to |
| /// be of a certain size in one of the axis. |
| const SliverGridDelegateWithWrapping({ |
| this.mainAxisSpacing = 0.0, |
| this.crossAxisSpacing = 0.0, |
| this.childCrossAxisExtent = double.infinity, |
| this.childMainAxisExtent = double.infinity, |
| }) : assert(mainAxisSpacing != null && mainAxisSpacing >= 0), |
| assert(crossAxisSpacing != null && crossAxisSpacing >= 0); |
| |
| /// The number of pixels from the leading edge of one tile to the trailing |
| /// edge of the same tile in the main axis. |
| /// |
| /// Defaults to [double.infinity] to provide the child with loose constraints. |
| 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. |
| /// |
| /// Defaults to [double.infinity] to provide the child with loose constraints. |
| final double childCrossAxisExtent; |
| |
| /// The number of logical pixels between each child along the main axis. |
| /// |
| /// Defaults to 0.0 |
| final double mainAxisSpacing; |
| |
| /// The number of logical pixels between each child along the cross axis. |
| /// |
| /// Defaults to 0.0 |
| final double crossAxisSpacing; |
| |
| bool _debugAssertIsValid() { |
| assert(mainAxisSpacing >= 0.0); |
| assert(crossAxisSpacing >= 0.0); |
| return true; |
| } |
| |
| @override |
| SliverGridLayout getLayout(SliverConstraints constraints) { |
| assert(_debugAssertIsValid()); |
| return SliverGridWrappingTileLayout( |
| childMainAxisExtent: childMainAxisExtent, |
| childCrossAxisExtent: childCrossAxisExtent, |
| mainAxisSpacing: mainAxisSpacing, |
| crossAxisSpacing: crossAxisSpacing, |
| scrollDirection: axisDirectionToAxis(constraints.axisDirection), |
| crossAxisExtent: constraints.crossAxisExtent, |
| ); |
| } |
| |
| @override |
| bool shouldRelayout(SliverGridDelegateWithWrapping oldDelegate) { |
| return oldDelegate.mainAxisSpacing != mainAxisSpacing || |
| oldDelegate.crossAxisSpacing != crossAxisSpacing; |
| } |
| } |