画面にリストやグリッド、画像など、様々な要素を挿入したい、という状況は結構あると思う。
しかし、リスト自体がスクロール要素を複数合わせると、思うように動いてくれない状況が発生した。
例えば下記のような要件を満たす画面を作成するとする。
- 画像や文言など、規則性の無い要素を縦に並べる
- 画面全体はスクロールできるようにする
コードは下記のようになる
main.dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
String img_src1 = 'https://example.com/img1.jpg';
String img_src2 = 'https://example.com/img2.jpg';
String img_src3 = 'https://example.com/img3.jpg';
String img_src4 = 'https://example.com/img4.jpg';
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Text('example', style: TextStyle(fontSize: 30.0, color: Colors.amber),),
Image.network(img_src1, height: 200,),
Text('example', style: TextStyle(fontSize: 30.0, color: Colors.black45),),
Image.network(img_src2, height: 200,),
Text('example.example.example.example.example.example.', style: TextStyle(fontSize: 30.0, color: Colors.deepOrangeAccent),),
Text('example.example.example.example.example.example.', style: TextStyle(fontSize: 30.0, color: Colors.teal),),
Image.network(img_src3, width: 400,),
Text('example', style: TextStyle(fontSize: 30.0, color: Colors.indigo),),
Image.network(img_src4, width: 400,),
],
),
),
);
}
}
注意点は以下の通り
- 規則性の無い要素を並べるので、
ListView
等ではなく、Column
を使用- 文言だけ or 写真だけ、ということであれば、
ListView
の方が使い勝手はいいと思うが、今回は1内容に複数の要素が入っている
- 文言だけ or 写真だけ、ということであれば、
- 画面からはみ出した分をスクロールできるように、
SingleChildScrollView
で全体を囲む
他のスクロール要素を追加する
この画面に新しく文言をタイル状に表示するエリアを作ってみるために、下記のようなGridView
要素を入れてビルドすると、例外が発生する。
main.dart
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
String img_src1 = 'https://example.com/img1.jpg';
// 〜省略部分〜
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Text('example', style: TextStyle(fontSize: 30.0, color: Colors.amber),),
// 〜省略部分〜
Image.network(img_src4, width: 400,),
// 新しく追加した部分↓
GridView.count(
crossAxisCount: 5,
children: List.generate(100, (index) {
return Center(
child: Text(
'Item $index',
style: Theme.of(context).textTheme.headline,
),
);
}),
)
],
),
),
);
}
}
これは、スクロール要素を入れ子にすることによって、縦の長さが無限になってしまうのを防ぐために、発生する例外とのこと。
CustomScrollViewでリストやグリッドを混在させる
CustomScrollViewとSliverを利用することで、リストやグリッドを好きに配置できるようになる。
main.dart
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
String img_src1 = 'https://example.com/img1.jpg';
// 〜省略部分〜
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate([
Column(
children: <Widget>[
Text('example', style: TextStyle(fontSize: 30.0, color: Colors.amber),),
// 〜省略部分〜
Image.network(img_src4, width: 400,)
]
)
]),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Center(
child: Text(
'Item $index',
style: Theme.of(context).textTheme.headline,
),
);
},
childCount: 100,
)
),
]
),
);
}
}
変更点と留意点は以下の通り
- `SingleChildScrollView`から`CustomScrollView`に変更する
- イメージや文言を格納していた`Column`要素を`SliverList`の中に入れる
- `GridView`部分は`SliverGrid`に変更
無事表示されるようになった。