Flutter で ListView を使った画面を実装していると、こんな場面に出くわすことがある。
「要素が少ないとき、プルリフレッシュが効かない」
RefreshIndicator を使っているのに、リストの要素が少なくてスクロールが発生しない場合、引っ張っても何も起きない。ユーザーが更新しようとしても反応がなく、UX として不親切だ。本記事では、このよくある課題をシンプルなウィジェットで解決する方法を紹介する。
import 'package:flutter/material.dart';
class SparedHeightRefreshIndicator extends StatelessWidget {
const SparedHeightRefreshIndicator({
required this.child,
this.onRefresh,
super.key,
});
final Widget child;
final Future<void> Function()? onRefresh;
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: onRefresh ?? Future.value,
// onRefresh が null の場合はリフレッシュを無効にする
notificationPredicate: (_) => onRefresh != null,
child: LayoutBuilder(
builder: (context, constraints) {
return ConstrainedBox(
// 子ウィジェットの最小高さを画面いっぱいに広げる
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: child,
);
},
),
);
}
}
RefreshIndicator はスクロール可能なウィジェット(ListView など)の一番上でさらに引っ張ったときに発火する仕組みだ。
しかし、リストの要素が少ない場合、コンテンツの合計高さが画面の高さに満たないため、そもそもスクロールが発生しない。スクロールが発生しないということは、引っ張る動作を検知できず、リフレッシュが一切効かなくなってしまう。これはバグではなく Flutter の仕様だが、ユーザー視点では「更新できない」という体験になってしまうため、対処が必要だ。
解決のアイデアはシンプルだ。コンテンツの高さが足りないなら、最小高さを画面いっぱいまで広げてしまえばいい。 そうすることで、コンテンツが少ない場合でもスクロール可能な領域が確保され、プルリフレッシュが正常に動作する。
LayoutBuilder を使うと、親ウィジェットが提供する制約(constraints)を取得できる。constraints.maxHeight は画面の高さ(または親の最大高さ)を表す。
ConstrainedBox に BoxConstraints(minHeight: constraints.maxHeight) を渡すことで、子ウィジェットの高さを制約できる。maxHeight を下回らないよう保証されるため、コンテンツが少なくてもスクロール領域が確保される。コンテンツが多い場合は自然にそれ以上の高さになるため、通常のスクロール動作も損なわれない。
notificationPredicate: (_) => onRefresh != null を指定することで、onRefresh が渡されていない場合はリフレッシュの発火自体を無効にしている。これにより、リフレッシュ不要な画面でこのウィジェットを使い回すことも可能だ。
SparedHeightRefreshIndicator(
onRefresh: () async {
await fetchData();
},
child: ListView(
children: items.map((item) => ListTile(title: Text(item))).toList(),
),
)
既存の RefreshIndicator と ListView の組み合わせを、このウィジェットに置き換えるだけで対応できる。
RefreshIndicator はスクロールが発生しないとプルリフレッシュが効かないLayoutBuilder + ConstrainedBox でコンテンツの最小高さを画面いっぱいに広げることで解決できるonRefresh の有無で制御できるため、汎用ウィジェットとして使い回しやすい小さなウィジェットだが、地味に詰まりやすいポイントをスッキリ解決してくれる。同じ課題にぶつかった際はぜひ参考にしてほしい。