Posted at

SingleChildScrollViewの中で上下中央揃えしてPull to Refreshもする


やりたいこと

こういうことがしたい。

ちゃんと文字に起こすと、要件はこんな感じ。


  • リストの空表示をしたい



RefreshIndicator の中身を SingleChildScrollView で包んでやれば良いのはわかるのだけれども、なかなか一筋縄では行きません。


先に解決策

LayoutBuilderConstrainedBoxAlwaysScrollableScrollPhysicsを使用します。

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()
)
)

:thinking:

SingleChildScrollView の子Widgetは高さの最大値が決まらない(いくらでも縦に伸ばせてしまうため)、 Center は表示域(この場合はScaffoldのbody部分)いっぱいに広がってくれないようです。

これを解決するにはLayoutBuilderConstrainedBoxを使います。

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()),
),
))

ちゃんと中央表示できました!!やったね!! :tada:

しかし、触ってみるとわかりますが、この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 をトリガーさせることができました! :congratulations:


まとめ



  • LayoutBuilder で表示域の制約を取得できる


  • ConstrainedBox で子Widgetのサイズに制約をかけることができる

  • Scroll系のWidgetをいつでもスクロールさせたいときは AlwaysScrollableScrollPhysics を使う

  • 公式のドキュメントはよく読む