😎
StaggeredGridViewを使ってみた!MasonryGridView.builder
はじめに
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パッケージを使う!
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など様々な要件に合わせたものが用意されているのでぜひ触ってみてください!
Discussion