【Flutter】CustomScrollView & Sliversで複数要素を一緒にスクロールできるようにする
ページ先頭にヘッダー画像などをつけて、スクロールしたらフェイドアウトしていくようなUIを作る。
ListViewだとリスト部分しかスクロールできないし、スクロールできるヘッダーみたいなものを入れたい!というときに便利だと思う。
使うのはCustomScrollViewとslivers。
slivers属性には仲間がたくさんいて、その中でも↑のような動きは3つのクラスを使っている。
- SliverAppBar : 動きのあるAppBar
- SliverList : スクロールできるリスト(要素によって高さ(大きさ)を指定できる)
- SliverFixedExtentList : スクロールできるリスト(全ての要素で高さが一様)
今回の例ではSliverListを使う意味がなかったけれど(汗)、例えば高さの異なるボタンとテキストを並べて配置したい時などはSliverListを使うのが良さそう。
高さが一様でいい場合は、なるべくSliverFixedExtentListを使お。
sliverを包んでいるのはCustomScrollView
class SliverSamplePage extends StatelessWidget {
const SliverSamplePage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: CustomScrollView(
slivers: [], //子にSliverたちを並べていく
),
);
}
sliversの中にはウィジェット(Sliver○○クラス)を入れていく。この中に入れたものたちは、連動してスクロールされる。
SliverAppBar
ヘッダー画像とアイコン画像、タイトル(例では太文字の"TORIsan")までの部分をSliverAppBarで作れる。
例で使用しているプロパティは以下。
leading
通常のAppBarのleadingと同じ役割。戻るボタンなどを設定する。
サンプル↓
leading: Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
primary: Colors.white.withOpacity(0.6),
shape: const CircleBorder(),
padding: EdgeInsets.zero,
),
child: const Icon(
Icons.arrow_back_ios_new,
color: Colors.black54,
size: 16,
),
),
),
actions
通常のAppBarのactionsと同じ役割。
サンプル(押しても特に何も起こらない)↓
actions: [
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
primary: Colors.white.withOpacity(0.6),
shape: const CircleBorder(),
padding: const EdgeInsets.all(8),
),
child: const Icon(
Icons.app_blocking,
color: Colors.black54,
size: 16,
),
),
),
],
expandedHeight
SliverAppBarの高さを指定。
backgroundColor
SliverAppBarの背景色を指定。
pinned
スクロールした時にAppBarを残したい時はtrue(冒頭の動画のような動きになる)、そうでなければfalseを指定。
elevation
スクロールした時に上部に残るAppBarのelevation。
flexibleSpace
SliverAppBar領域に表示するものを設定する。title("TORIsan"), background(今回でいえばヘッダー画像とアイコン)。
ヘッダー画像とアイコン画像を例のように重ねたいときはStackを使えば実現可能。
サンプル↓
background: Stack(children: [
SizedBox(
height: 200,
width: size.width, // sizeをMediaQuery.of(context).sizeなどで定義しておく
),
Positioned(
top: 0,
child: SizedBox(
height: 150.0,
width: size.width,
child: Image(
image: NetworkImage(imageUrl),
fit: BoxFit.cover,
),
),
),
Positioned(
top: 110,
left: 20,
child: CircleAvatar(
radius: 40,
backgroundImage: NetworkImage(iconUrl),
),
),
]),
SliverAppBar全体コードはこんな感じ↓。
SliverAppBar(
leading: //省略,
actions: [/*省略*/],
expandedHeight: 100 + kToolbarHeight,
backgroundColor: Colors.white,
pinned: true,
elevation: 2,
flexibleSpace: FlexibleSpaceBar(
title: /*省略*/,
titlePadding: const EdgeInsets.all(8),
collapseMode: CollapseMode.pin,
centerTitle: true,
background: /*省略*/,
),
),
SliverList
sliversの子に設定できる、スクロールできるクラス。
SliverListでは、delegateというプロパティの設定が必要。
delegateには『SliverChildListDelegate』と『SliverChildBuilderDelegate』を設定できる。
delegateってなに?についてはこちらがわかりやすい。
補足:delegateは直訳すると、「委譲」です。おまかせするという意味。
Viewを作るそのときに全てのアイテムをビルドするのではなく、アイテムが表示されそうになった>タイミングでそのアイテムをビルドする。そのアイテムのビルドはdelegageパラメータに指定し>た関数に「おまかせ」します、という意味です。
https://bukiyo-papa.com/sliverlist-slivergrid/
ここではリストの数が明確に決まっているので、SliverChildListDelegateを使う。
(後にSliverChildBuilderDelegateも使用。)
SliverList(
delegate: SliverChildListDelegate([
const Center(
child: Text(
'region: Japan',
style: TextStyle(color: Colors.grey, fontSize: 16.0),
),
),
const Center(
child: Text(
'color: Brown',
style: TextStyle(
color: Colors.grey,
fontSize: 16.0,
),
),
),
const SizedBox(height: 10.0),
])),
SliverFixedExtentList
sliversの子に設定できる、スクロールできるクラス。
itemExtentにリストの高さを設定し、SliverListと同じようにdelegateも設定する。
SliverListとは違い、外部からとってきたリストデータ("data")をもとにウィジェットを作るパターンを想定し、SliverChildBuilderDelegateを使ってみる。
SliverFixedExtentList(
itemExtent: 100,
delegate:
SliverChildBuilderDelegate((BuildContext context, int index) {
return CardWidget(index: index);
}, childCount: data.length),
),
CardWidget↓
class CardWidget extends StatelessWidget {
CardWidget({Key? key, required this.index}) : super(key: key);
int index;
Widget build(BuildContext context) {
return Card(
child: Center(
child: Text(
data[index],
style: const TextStyle(color: Colors.grey, fontSize: 24.0),
),
),
color: Colors.white.withOpacity(0.8),
);
}
}
SliverGridもあるよ
例で使っているのは、ListViewっぽい見た目のやつだけど、SliverGridというGridViewのような見た目で画面全体をスクロールできるようにするクラスもある。
Discussion