🐙
Flutterでカード形式のグリッドレイアウトを作成する方法
Flutterでカード形式のグリッドレイアウトを作成する方法
Flutterアプリでデータをカード形式で表示したいとき、どのようにレイアウトを組めば良いでしょうか。
今回は、カード形式のグリッドレイアウトについて3つの方法を紹介します。
🚨 問題:データをきれいにカードで表示したい
Flutterアプリでデータを表示する際、以下のような課題があります。
- リスト形式だと見た目が単調
- データの種類ごとに視覚的に分けたい
- レスポンシブなレイアウトにしたい
- カードのサイズを統一したい
📱 方法1:基本的なカード表示
最もシンプルな方法です。Card
ウィジェットを使って基本的なカードを作成します。
class BasicCardDemo extends StatelessWidget {
const BasicCardDemo({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('基本カード表示')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// 基本的なカード
Card(
// カードの影を設定
elevation: 4,
// カードの角丸を設定
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
// カード内の余白を設定
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// カードのタイトル
const Text(
'カードタイトル',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
// カードの内容
const Text(
'これは基本的なカードの内容です。',
style: TextStyle(fontSize: 14),
),
],
),
),
),
const SizedBox(height: 16),
// 複数のカードを縦に並べる
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'もう一つのカード',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
const Text(
'2つ目のカードの内容です。',
style: TextStyle(fontSize: 14),
),
],
),
),
),
],
),
),
);
}
}
メリット
- 実装が簡単
- 基本的なカード表示ができる
- 理解しやすい
デメリット
- グリッド表示ができない
- レスポンシブ対応ができない
- カードの数が多いと見づらい
🔄 方法2:GridViewでグリッド表示
GridView
を使ってカードをグリッド形式で表示する方法です。
class GridCardDemo extends StatelessWidget {
const GridCardDemo({super.key});
Widget build(BuildContext context) {
// サンプルデータ
final List<String> cardTitles = [
'カード1',
'カード2',
'カード3',
'カード4',
'カード5',
'カード6',
];
return Scaffold(
appBar: AppBar(title: const Text('グリッドカード表示')),
body: Padding(
padding: const EdgeInsets.all(16),
child: GridView.builder(
// グリッドの設定
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
// 1行あたりのカード数
crossAxisCount: 2,
// カードの縦横比
childAspectRatio: 1.2,
// カード間の縦の間隔
mainAxisSpacing: 16,
// カード間の横の間隔
crossAxisSpacing: 16,
),
// カードの総数
itemCount: cardTitles.length,
// 各カードの作成
itemBuilder: (context, index) {
return Card(
// カードの影を設定
elevation: 4,
// カードの角丸を設定
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
// カード内の余白を設定
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// カードのアイコン
const Icon(
Icons.star,
size: 32,
color: Colors.amber,
),
const SizedBox(height: 8),
// カードのタイトル
Text(
cardTitles[index],
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
// カードの説明
const Text(
'カードの説明',
style: TextStyle(fontSize: 12),
textAlign: TextAlign.center,
),
],
),
),
);
},
),
),
);
}
}
メリット
- グリッド形式で表示できる
- レスポンシブ対応ができる
- カードのサイズが統一される
デメリット
- 実装が少し複雑
- スクロール可能なリストではない
- カードの数が多いと画面からはみ出る
🎯 方法3:ScrollViewでスクロール可能なグリッド
SingleChildScrollView
とGridView
を組み合わせて、スクロール可能なグリッドを作成する方法です。
実装のポイント
- ScrollViewでスクロール可能にする
SingleChildScrollView(
child: GridView.builder(
// スクロール可能にするための設定
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
// グリッドの設定
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.2,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
),
// カードの作成
itemBuilder: (context, index) => Card(...),
),
)
- カスタムカードウィジェットを作成
class CustomCard extends StatelessWidget {
final String title;
final String description;
final IconData icon;
const CustomCard({
super.key,
required this.title,
required this.description,
required this.icon,
});
Widget build(BuildContext context) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 32, color: Colors.blue),
const SizedBox(height: 8),
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(description, style: const TextStyle(fontSize: 12)),
],
),
),
);
}
}
- データモデルを使用
class CardData {
final String title;
final String description;
final IconData icon;
CardData({
required this.title,
required this.description,
required this.icon,
});
}
メリット
- スクロール可能
- カードの数が多い場合でも対応
- カスタマイズ性が高い
デメリット
- 実装が複雑
- パフォーマンスへの影響がある
- 管理が大変
✅ 3つの方法を比較した完全版コード
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'カードグリッドレイアウトデモ',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const CardLayoutComparisonDemo(),
);
}
}
class CardLayoutComparisonDemo extends StatelessWidget {
const CardLayoutComparisonDemo({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('カードレイアウト比較'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 方法1: 基本的なカード表示
_buildSectionTitle('方法1: 基本的なカード表示'),
_buildBasicCards(),
const SizedBox(height: 24),
// 方法2: GridViewでグリッド表示
_buildSectionTitle('方法2: GridViewでグリッド表示'),
_buildGridCards(),
const SizedBox(height: 24),
// 方法3: スクロール可能なグリッド
_buildSectionTitle('方法3: スクロール可能なグリッド'),
_buildScrollableGridCards(),
const SizedBox(height: 24),
// 比較説明
_buildComparisonInfo(),
],
),
),
);
}
// セクションタイトルを表示するウィジェット
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
);
}
// 方法1: 基本的なカード表示
Widget _buildBasicCards() {
return Column(
children: [
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: const Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'基本カード1',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'これは基本的なカードの内容です。',
style: TextStyle(fontSize: 14),
),
],
),
),
),
const SizedBox(height: 16),
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: const Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'基本カード2',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'2つ目のカードの内容です。',
style: TextStyle(fontSize: 14),
),
],
),
),
),
],
);
}
// 方法2: GridViewでグリッド表示
Widget _buildGridCards() {
final List<String> cardTitles = ['カード1', 'カード2', 'カード3', 'カード4'];
return SizedBox(
height: 200, // 固定の高さを設定
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.2,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
),
itemCount: cardTitles.length,
itemBuilder: (context, index) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.star,
size: 32,
color: Colors.amber,
),
const SizedBox(height: 8),
Text(
cardTitles[index],
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
const Text(
'グリッドカード',
style: TextStyle(fontSize: 12),
textAlign: TextAlign.center,
),
],
),
),
);
},
),
);
}
// 方法3: スクロール可能なグリッド
Widget _buildScrollableGridCards() {
final List<CardData> cardDataList = [
CardData(
title: 'スクロール1',
description: 'スクロール可能なカード',
icon: Icons.favorite,
),
CardData(
title: 'スクロール2',
description: '2つ目のカード',
icon: Icons.thumb_up,
),
CardData(
title: 'スクロール3',
description: '3つ目のカード',
icon: Icons.star,
),
CardData(
title: 'スクロール4',
description: '4つ目のカード',
icon: Icons.bookmark,
),
CardData(
title: 'スクロール5',
description: '5つ目のカード',
icon: Icons.share,
),
CardData(
title: 'スクロール6',
description: '6つ目のカード',
icon: Icons.download,
),
];
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.2,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
),
itemCount: cardDataList.length,
itemBuilder: (context, index) {
final cardData = cardDataList[index];
return CustomCard(
title: cardData.title,
description: cardData.description,
icon: cardData.icon,
);
},
);
}
// 比較情報を表示するウィジェット
Widget _buildComparisonInfo() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'📊 比較表',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text('• 方法1: 基本カード - 実装簡単、グリッド不可'),
Text('• 方法2: グリッド表示 - レスポンシブ、スクロール不可'),
Text('• 方法3: スクロールグリッド - 高機能、実装複雑'),
],
),
);
}
}
// カスタムカードウィジェット
class CustomCard extends StatelessWidget {
final String title;
final String description;
final IconData icon;
const CustomCard({
super.key,
required this.title,
required this.description,
required this.icon,
});
Widget build(BuildContext context) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 32, color: Colors.blue),
const SizedBox(height: 8),
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
description,
style: const TextStyle(fontSize: 12),
textAlign: TextAlign.center,
),
],
),
),
);
}
}
// カードデータモデル
class CardData {
final String title;
final String description;
final IconData icon;
CardData({
required this.title,
required this.description,
required this.icon,
});
}
🎯 使い分けのポイント
方法1(基本カード)を使う場合
- カードの数が少ない場合
- グリッド表示が不要な場合
- 実装を簡単に済ませたい場合
方法2(グリッド表示)を使う場合
- カードをグリッド形式で表示したい場合
- カードの数が限定的な場合
- レスポンシブ対応が必要な場合
方法3(スクロールグリッド)を使う場合
- カードの数が多い場合
- スクロール機能が必要な場合
- カスタマイズ性を重視する場合
🧭 おわりに
Flutterでカード形式のグリッドレイアウトを作成する際は、用途に応じて適切な方法を選択することが重要です。
今回紹介した3つの方法を参考に、自分のアプリに最適なカードレイアウトを実装してみてください!
Discussion