letsar / flutter_staggered_grid_view

A Flutter staggered grid view
MIT License
3.14k stars 510 forks source link

[FR] Dynamic pattern builder #351

Open Xazin opened 3 months ago

Xazin commented 3 months ago

I want to use specifically the SliverQuiltedGridDelegate to have as boxed a layout as possible no matter the amount of items in the grid, as it's not fixed.

Our use-case is when a user inserts irregular amount of end-items to a grid compared to the pattern, in such a case it would be nice to be able to change the pattern based on the length of the current segment.

Consider this pattern and code:

    return GridView.custom(
      shrinkWrap: true,
      gridDelegate: SliverQuiltedGridDelegate(
          crossAxisCount: 4,
          repeatPattern: QuiltedGridRepeatPattern.inverted,
          pattern: const [
            QuiltedGridTile(2, 2),
            QuiltedGridTile(1, 1),
            QuiltedGridTile(1, 1),
            QuiltedGridTile(1, 2),
          ]),
      childrenDelegate: SliverChildBuilderDelegate(
        childCount: widget.images.length,
        (_, index) => Tile(),
      ),
    );

Which produces this result:

image

Now if the last image was missing, it would look like this:

image

Where a more fitting pattern would be something like this for the last block:

image


So if we could use a patternBuilder in some cases when needed, which provides the length of the images in the current segment, that would be very helpful.

I'm willing to look into how we could go about achieving this, I know it might not be super straightforward, and might be good to completely separate the new implementation from SliverQuiltedGridDelegate.

Xazin commented 3 months ago

For anyone that could be slightly interested in how the same could be achieved without the suggested implementation, this is how I've done it, it's not pretty:

StaggeredGrid.count implementation ```dart /// Draws a staggered grid of images, where the pattern is based /// on the amount of images to fill the grid at all times. /// /// They will be alternating depending on the current index of the images. /// /// For example, if there are 4 images in the last segment, this will be drawn: /// ┌─────┐┌─┐┌─┐ /// │ │└─┘└─┘ /// │ │┌────┐ /// └─────┘└────┘ /// /// If there are 3 images in the last segment, this will be drawn: /// ┌─────┐┌────┐ /// │ │└────┘ /// │ │┌────┐ /// └─────┘└────┘ /// /// If there are 2 images in the last segment, this will be drawn: /// ┌─────┐┌─────┐ /// │ ││ │ /// └─────┘└─────┘ /// /// If there is 1 image in the last segment, this will be drawn: /// ┌──────────┐ /// │ │ /// └──────────┘ class StaggeredGridBuilder extends StatefulWidget { const StaggeredGridBuilder({ super.key, required this.images, required this.onImageDoubleTapped, }); final List images; final void Function(int) onImageDoubleTapped; @override State createState() => _StaggeredGridBuilderState(); } class _StaggeredGridBuilderState extends State { /// Split up the list of images into a list of lists of 4 images each, the /// last list may have less than 4 images. /// final List> _splitImages = []; @override void initState() { super.initState(); for (int i = 0; i < widget.images.length; i += 4) { final end = (i + 4 < widget.images.length) ? i + 4 : widget.images.length; _splitImages.add(widget.images.sublist(i, end)); } } @override void didUpdateWidget(covariant StaggeredGridBuilder oldWidget) { if (widget.images.length != oldWidget.images.length) { _splitImages.clear(); for (int i = 0; i < widget.images.length; i += 4) { final end = (i + 4 < widget.images.length) ? i + 4 : widget.images.length; _splitImages.add(widget.images.sublist(i, end)); } } super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { return StaggeredGrid.count( crossAxisCount: 4, children: _splitImages.indexed.map(_buildTilesForImages).flattened.toList(), ); } List _buildTilesForImages((int, List) data) { final index = data.$1; final images = data.$2; final isReversed = index.isOdd; if (images.length == 4) { return [ StaggeredGridTile.count( crossAxisCellCount: isReversed ? 1 : 2, mainAxisCellCount: isReversed ? 1 : 2, child: Tile(), ), StaggeredGridTile.count( crossAxisCellCount: 1, mainAxisCellCount: 1, child: Tile(), ), StaggeredGridTile.count( crossAxisCellCount: isReversed ? 2 : 1, mainAxisCellCount: isReversed ? 2 : 1, child: Tile(), ), StaggeredGridTile.count( crossAxisCellCount: 2, mainAxisCellCount: 1, child: Tile(), ), ]; } else if (images.length == 3) { return [ StaggeredGridTile.count( crossAxisCellCount: 2, mainAxisCellCount: isReversed ? 1 : 2, child: Tile(), ), StaggeredGridTile.count( crossAxisCellCount: 2, mainAxisCellCount: isReversed ? 2 : 1, child: Tile(), ), StaggeredGridTile.count( crossAxisCellCount: 2, mainAxisCellCount: 1, child: Tile(), ), ]; } else if (images.length == 2) { return [ StaggeredGridTile.count( crossAxisCellCount: 2, mainAxisCellCount: 2, child: Tile(), ), StaggeredGridTile.count( crossAxisCellCount: 2, mainAxisCellCount: 2, child: Tile(), ), ]; } else { return [ StaggeredGridTile.count( crossAxisCellCount: 4, mainAxisCellCount: 2, child: Tile(), ), ]; } } } ```