やりたいこと
こういうことがしたい。
ちゃんと文字に起こすと、要件はこんな感じ。
- リストの空表示をしたい
- Pull to Refreshしたいので、RefreshIndicatorで全体をくくって、中をスクロール可能にしたい
- SingleChildScrollViewの中に自作Widgetを中央表示したい
RefreshIndicator
の中身を SingleChildScrollView
で包んでやれば良いのはわかるのだけれども、なかなか一筋縄では行きません。
先に解決策
LayoutBuilderとConstrainedBox、AlwaysScrollableScrollPhysicsを使用します。
RefreshIndicator(
child: LayoutBuilder(
builder: (context, constraints) => SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: Center(child: EmptyView()),
))))
これでSingleChildScrollViewの中身を表示域と同じサイズにできるので、目的のWidgetを上下中央に表示できます。
解決すべき問題点と解決策
表示域の高さ不定問題
通常であれば、Widgetを表示域の中央に持ってきたければ Center
を使用します。
Center(
child: EmptyView()
)
これをスクロールできればいいので、 SingleChildScrollView
で括ってやりましょう。以上!なんて簡単なんだ!!
SingleChildScrollView(
child: Center(
child: EmptyView()
)
)
↓
SingleChildScrollView
の子Widgetは高さの最大値が決まらない(いくらでも縦に伸ばせてしまうため)、 Center
は表示域(この場合はScaffoldのbody部分)いっぱいに広がってくれないようです。
これを解決するにはLayoutBuilderとConstrainedBoxを使います。
SingleChildScrollViewのDocに書いてありますね!!
LayoutBuilder
は、FlutterがWidgetツリーを構築するbuildメソッドが呼ばれたときまで、子Widgetツリーの生成を遅延し、その時点での表示域の制約を取得してくれます。
ここでは、 Center
を表示域の高さまで広げられればよいので、この制約の情報が欲しいわけです。
ConstrainedBox
はサイズに制約をもたせた Container
のようなものです。
ここでは、Center
の親Widgetの高さは表示域の高さと等しいかそれより大きくなってもらう必要があるので、 minHeight
の制約を使用します。
LayoutBuilder(
builder: (context, constraints) => SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: Center(child: EmptyView()),
),
))
↓
ちゃんと中央表示できました!!やったね!!
しかし、触ってみるとわかりますが、このLayoutBuilderの親に RefreshIndicator
を置いてもPull to Refreshできません。なぜだ。
Pull to Refreshできない問題
RefreshIndicator(
child: LayoutBuilder(
builder: (context, constraints) => SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: Center(child: EmptyView()),
))))
今のままでは、 SingleChildScrollView
内要素が SingleChildScrollView
と同じ高さになっているため、スクロールが行われません。
スクロール可能にするならば、内要素が SingleChildScrollView
より長くなっている必要があります。
が、長くしてしまうとせっかく Center
で表示域中央に持ってきたのが、中央からずれてしまいます。
そこで、 SingleChildScrollView
内要素が SingleChildScrollView
より短くてもスクロール可能にする方法を取ります。
physics:
に AlwaysScrollableScrollPhysics を指定する方法です。
AlwaysScrollableScrollPhysics
はスクロール可能なWidgetを、いつでもスクロールさせるものです。
これで RefreshIndicator
をトリガーさせることができました!
まとめ
-
LayoutBuilder
で表示域の制約を取得できる -
ConstrainedBox
で子Widgetのサイズに制約をかけることができる - Scroll系のWidgetをいつでもスクロールさせたいときは
AlwaysScrollableScrollPhysics
を使う - 公式のドキュメントはよく読む