😎

StaggeredGridViewを使ってみた!MasonryGridView.builder

2024/01/31に公開

はじめに

ListViewやGridViewは最も一般的に使用されるスクロールウィジェットですが、それを使っても実装できないUIがあり困っていました。そんな中StaggeredGridViewを見つけたので触ってみて学習内容を共有したいと思います。

対象者

  • Flutter学習者
  • StaggeredGridViewについて知りたい方

作りたい画面

実際の画面とコード

画像サイズの違いに対応できず空白が目立ってしまう。

image_with_tag_grid.dart
class ImageWithTagGrid extends StatelessWidget {
  const ImageWithTagGrid({super.key});
  
  Widget build(BuildContext context) {
    return GridView.builder(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 0.75,
        ),
        itemCount: sampleImages.length,
        itemBuilder: (BuildContext context, int index) {
          return ImageWithTagItem(
            image: sampleImages[index].image,
            hashTag: sampleImages[index].hashTag,
          );
        });
  }
}

解決策

StaggeredGridViewパッケージを使う!
https://pub.dev/packages/flutter_staggered_grid_view

StaggeredとMasonryの違い

Staggered

このレイアウトは少数のアイテムを対象としています。また特徴として

  • n列に均等に分割される

  • アイテム数が少ない

  • スクロールが不可

  • Tile properties
    Must occupy 1 to n columns(1 ~nまでColumnを占有しないと使用できません)

  • 配置アルゴリズム
    一番上、次に一番左

といったものがあり、以下のようなUIを作成できる。

image_with_tag_grid.dart
StaggeredGrid.count(
  crossAxisCount: 4,
  mainAxisSpacing: 4,
  crossAxisSpacing: 4,
  children: const [
    StaggeredGridTile.count(
      crossAxisCellCount: 2,
      mainAxisCellCount: 2,
      child: Tile(index: 0),
    ),
    StaggeredGridTile.count(
      crossAxisCellCount: 2,
      mainAxisCellCount: 1,
      child: Tile(index: 1),
    ),
    StaggeredGridTile.count(
      crossAxisCellCount: 1,
      mainAxisCellCount: 1,
      child: Tile(index: 2),
    ),
    StaggeredGridTile.count(
      crossAxisCellCount: 1,
      mainAxisCellCount: 1,
      child: Tile(index: 3),
    ),
    StaggeredGridTile.count(
      crossAxisCellCount: 4,
      mainAxisCellCount: 2,
      child: Tile(index: 4),
    ),
  ],
);

Manonry

大きさが比になっているコンテンツの閲覧が容易にすることができます。Containerの高さはウィジェットのサイズに基づいて決定されます。

またSliverGridDelegateに依存しない(パフォーマンス等の観点から)のでbuilderを使用する場合はSliverSimpleGridDelegateWithFixedCrossAxisCountを使用する。

  • Tile properties
    Must occupy 1 column only (1列のColumnnのみを占有する必要がある)
  • 配置アルゴリズム
    一番上、次に一番左から

以下のようなUIを作成できる。

image_with_tag_grid.dart
MasonryGridView.count(
  crossAxisCount: 4,
  mainAxisSpacing: 4,
  crossAxisSpacing: 4,
  itemBuilder: (context, index) {
    return Tile(
      index: index,
      extent: (index % 5 + 1) * 100,
    );
  },
);

実際にやってみた

image_with_tag_grid.dart
class ImageWithTagGrid extends StatelessWidget {
  const ImageWithTagGrid({super.key});
  
  Widget build(BuildContext context) {
    return MasonryGridView.builder(
      gridDelegate: const SliverSimpleGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
      ),
      itemBuilder: (BuildContext context, int index) {
        return ImageWithTagItem(
          image: sampleImages[index].image,
          hashTag: sampleImages[index].hashTag,
        );
      },
      itemCount: sampleImages.length,
    );
  }
}

全体コード

import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:friendy/utils/gen/assets.gen.dart';
import 'package:friendy/utils/gen/color_palette.dart';

class ImageEntity {
  final Image image;
  final String hashTag;

  ImageEntity({required this.image, required this.hashTag});
}

List<ImageEntity> sampleImages = [
  ImageEntity(
    image: Assets.images.samples.sample1.image(),
    hashTag: 'カフェ',
  ),
  ImageEntity(
    image: Assets.images.samples.sample2.image(),
    hashTag: 'おでかけ',
  ),
  ImageEntity(
    image: Assets.images.samples.sample3.image(),
    hashTag: '料理',
  ),
  ImageEntity(
    image: Assets.images.samples.sample4.image(),
    hashTag: 'お酒',
  ),
  ImageEntity(
    image: Assets.images.samples.sample5.image(),
    hashTag: 'スポーツ',
  ),
  ImageEntity(
    image: Assets.images.samples.sample6.image(),
    hashTag: 'アート',
  ),
  ImageEntity(
    image: Assets.images.samples.sample7.image(),
    hashTag: 'ミュージック',
  ),
  ImageEntity(
    image: Assets.images.samples.sample8.image(),
    hashTag: 'ペット',
  ),
];

class ImageWithTagGrid extends StatelessWidget {
  const ImageWithTagGrid({super.key});
  
  Widget build(BuildContext context) {
    return MasonryGridView.builder(
      gridDelegate: const SliverSimpleGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
      ),
      itemBuilder: (BuildContext context, int index) {
        return ImageWithTagItem(
          image: sampleImages[index].image,
          hashTag: sampleImages[index].hashTag,
        );
      },
      itemCount: sampleImages.length,
    );
  }
}

class ImageWithTagItem extends StatelessWidget {
  const ImageWithTagItem({
    required this.image,
    required this.hashTag,
    super.key,
  });

  final Image image;
  final String hashTag;

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        image,
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                "#$hashTag",
                style: const TextStyle(
                  color: textPrimary,
                  fontSize: 12,
                ),
                overflow: TextOverflow.ellipsis,
              ),
              const Icon(Icons.more_horiz),
            ],
          ),
        ),
      ],
    );
  }
}

まとめ

最後まで読んでいただいてありがとうございました。
今回は要件に対応するためにflutter_staggered_grid_viewを使用しました。staggered_grid_viewは今回取り上げたMansonryの他にQuilted, Woven,Staired,Alignedなど様々な要件に合わせたものが用意されているのでぜひ触ってみてください!

Jboy王国メディア

Discussion