Flutter - Layout 2
本記事は下記リンクのFlutter公式ドキュメントを学習したメモです。
今回は上記リンクのレイアウトの中でList & gridsセクションの内容を紹介する。
リストビュー
基本的なリストの表示。ListViewを使ってListTileを配置する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
const title = '基本的なリスト';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: ListView(
children: const <Widget>[
ListTile(
// ラベルの前に位置するアイコン
leading: Icon(Icons.map),
// リストタイルのラベル
title: Text('Map'),
),
ListTile(
// ラベルの前に位置するアイコン
leading: Icon(Icons.photo_album),
// リストタイルのラベル
title: Text('Album'),
),
ListTile(
// ラベルの前に位置するアイコン
leading: Icon(Icons.call),
// リストタイルのラベル
title: Text('Phone'),
),
],
),
),
);
}
}
横並びリスト
先ほどは基本的に縦並びのリストだったが、横並びもある。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
const title = '基本的なリスト';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
// 隙間とサイズ指定のためContainerを使う
body: Container(
// 内側の上下で隙間を作る
margin: const EdgeInsets.symmetric(vertical: 20),
// 縦のサイズを指定する
height: 200,
child: ListView(
// 横向きにする(本当は横スクロールにする)
scrollDirection: Axis.horizontal,
children: <Widget>[
// 横幅160ピクセルの箱を並べる
Container(
width: 160,
color: Colors.red,
),
Container(
width: 160,
color: Colors.blue,
),
Container(
width: 160,
color: Colors.green,
),
Container(
width: 160,
color: Colors.yellow,
),
Container(
width: 160,
color: Colors.orange,
),
],
),
),
),
);
}
}
グリッド
グリッド表示したい場合はGridViewがある。GridView.count()コンストラクターを使うと行数もしくは列数を指定して初期化できる。
次のサンプルはListを使って100個のインデックスを作ってそのインデックスを持つ100個のアイテムを追加する例だ。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
const title = 'グリッドリスト';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
// 隙間とサイズ指定のためContainerを使う
body: GridView.count(
// 2列のグリッド
crossAxisCount: 2,
// 100個のアイテムを作る
children: List.generate(100, (index) {
// インデックスによって3つの色のどれかにする
Color? bgColor = Colors.blue[100];
if (index % 3 == 0) {
bgColor = Colors.red[100];
} else if (index % 3 == 1) {
bgColor = Colors.green[100];
}
return Container(
color: bgColor,
child: Center(
child: Text(
'アイテム${index + 1}番',
style: const TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
);
}),
),
),
);
}
}
異なるタイプを持つリスト
様々なタイプのアイテムを持つリストを作ってみる。
まずは異なるタイプをWidgetをリストに入れられるためにインターフェースを作る。
ListItemは主タイトルとサブタイトルのWidgetを作成する関数を持つインターフェースだ。
1
2
3
4
5
// ListItem Interface
abstract class ListItem {
Widget buildTitle(BuildContext context);
Widget buildSubtitle(BuildContext context);
}
次に上で作ったインターフェースを使って二つの異なるWidgetを作る。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class HeadingItem implements ListItem {
final String heading;
HeadingItem(this.heading);
@override
Widget buildTitle(BuildContext context) {
return Text(
heading,
style: Theme.of(context).textTheme.headlineSmall,
);
}
@override
Widget buildSubtitle(BuildContext context) => const SizedBox.shrink();
}
class MessageItem implements ListItem {
final String sender;
final String body;
MessageItem(this.sender, this.body);
@override
Widget buildTitle(BuildContext context) => Text(sender);
@override
Widget buildSubtitle(BuildContext context) => Text(body);
}
HeadingItemののサブタイトルはSizedBox().shrink()なのでほぼサイズがないアイテムだ。結局、タイトルのみ表示されるWidgetになる。
MessageItemは送信者とメッセージボディをもらい二つのテキストを生成している。
次にmain関数でデータを1000個作って渡したら、データに合わせてリストに追加するようにしてみよう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import 'package:flutter/material.dart';
void main() {
runApp(
MyApp(
items: List<ListItem>.generate(
1000, // 1000個のデータを作成
// 6個ごとにタイトルにする
(i) => i % 6 == 0
? HeadingItem('タイトル $i')
: MessageItem('送信者 $i', 'メッセージ $i'),
),
),
);
}
class MyApp extends StatelessWidget {
// 表示するデータ
final List<ListItem> items;
const MyApp({super.key, required this.items});
@override
Widget build(BuildContext context) {
const title = 'いろんなタイプを持つリスト';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
// itemsリストからアイテムを作成する
body: ListView.builder(
// リストのサイズ
itemCount: items.length,
// リストの各要素からListTileを作る
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
title: item.buildTitle(context),
subtitle: item.buildSubtitle(context),
);
},
),
),
);
}
}
iOSシミュレーターで実行すると「Using the Impeller rendering backend.」とエラーが発生するが一旦無視しよう。おそらくデータが多すぎたと思う。
柔軟なリスト
タイトルが変化もしれないが、通常の画面では要素を均等に配置して画面が小さい時にはスクロールできるように配置するものを作ってみる。
まずはアイテムとなるWidgetだ。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ItemWidget extends StatelessWidget {
const ItemWidget({
super.key,
required this.text,
});
final String text;
@override
Widget build(BuildContext context) {
// 縦幅200ピクセルのカード
return Card(
child: SizedBox(
height: 200,
child: Center(
child: Text(text),
),
),
);
}
}
リストWidgetを含め、main関数まで
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import 'package:flutter/material.dart';
void main() => runApp(const SpacedItemList());
class SpacedItemList extends StatelessWidget {
const SpacedItemList({super.key});
@override
Widget build(BuildContext context) {
// 4つのアイテムを持つ
const items = 4;
return MaterialApp(
title: 'Spaced Item List',
// アプリで使うテーマ(色)を定義する
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
cardTheme: CardTheme(color: Colors.blue.shade50),
useMaterial3: true,
),
home: Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: List.generate(
items,
(index) => ItemWidget(text: 'アイテム $index'),
),
),
),
);
},
),
),
);
}
}
カードのサイズを変えたり、アイテム数を変えたり試してみたら、画面を超えるとスクロールできるし、画面におさまる場合は均等な間隔で並ぶことがわかる。
長いリスト
ListViewコンストラクターはアイテム数が少ない時に良い。アイテム数が多くなるとListView.builderコンストラクターを使った方が良い。
ListViewは一度にアイテムを作成するが、ListView.builderはスクロールされる時に作成される。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import 'package:flutter/material.dart';
void main() {
runApp(
MyApp(
items: List<String>.generate(10000, (index) => 'アイテム ${index + 1}'),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key, required this.items});
final List<String> items;
@override
Widget build(BuildContext context) {
const title = '長いリスト';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: ListView.builder(
itemCount: items.length,
prototypeItem: ListTile(
leading: const Icon(Icons.account_circle),
title: Text(items.first),
),
itemBuilder: (_, index) {
return ListTile(
leading: const Icon(Icons.account_circle),
title: Text(items[index]),
);
},
),
),
);
}
}
最後に
これでLayoutのList & gridsセクションの内容を一通りみた。
リストの表示でどのようにすれば良いか、わかったが、長いリストの場合、工夫が必要と思う。
コメントする