Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FR] Dynamic pattern builder #351

Open
Xazin opened this issue Jul 29, 2024 · 1 comment
Open

[FR] Dynamic pattern builder #351

Xazin opened this issue Jul 29, 2024 · 1 comment

Comments

@Xazin
Copy link

Xazin commented Jul 29, 2024

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
Copy link
Author

Xazin commented Jul 29, 2024

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
/// 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<ImageBlockData> images;
  final void Function(int) onImageDoubleTapped;

  @override
  State<StaggeredGridBuilder> createState() => _StaggeredGridBuilderState();
}

class _StaggeredGridBuilderState extends State<StaggeredGridBuilder> {
  /// 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<List<ImageBlockData>> _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<Widget> _buildTilesForImages((int, List<ImageBlockData>) 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(),
        ),
      ];
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant