Back to tech

[Flutter/Dart] ExpandedとSingleChildScrollView(ScrollView)の相性は最高

3 min read
Table of Contents

お久しぶりです。みやかわです。

最近 Flutter で個人開発しています。

タイトルを固定したScrollView(スクロールビュー)のViewを実装していたときに、エラーがでてしまい実装ができない問題が発生しました。

色々悩んだ末になんとか実装することができたので解決策をメモ程度に書いていきます。

直面した問題

エラー内容

以下のような タイトルを固定したScrollViewを作りたいを作りたいとします。

これを作る場合は以下のように Colum() Widget 内に Text() Widget

 SingleChildScrollView() Widget を入れると思います。

class Home extends StatelessWidget {
  const Home({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('ScrollViewにContainer追加'),
        ),
        body: Column( // ここから
          children: [
            const Text(
              'タイトル',
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 20,
              ),
            ),
            SingleChildScrollView(
              child: Column(
                children: [
                  TestChild(color: Colors.red.shade400, label: 'test1'),
                  TestChild(color: Colors.green.shade400, label: 'test2'),
                  TestChild(color: Colors.blue.shade400, label: 'test3'),
                  TestChild(color: Colors.orange.shade400, label: 'test4'),
                ],
              ),
            ),
          ],
        )); // ここまで
  }
}

これを実行すると以下のようなエラーがでてしまいます。

erforming hot reload...                                               
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY
╞═════════════════════════════════════════════════════════
The following assertion was thrown during layout:
A RenderFlex overflowed by 151 pixels on the bottom.

The relevant error-causing widget was:
  Column Column:file:///path/to/lib/main.dart:30:15

テキトーに翻訳すると SingleChildScrollView() が画面から溢れて表示できないと言われています。

Container() Widgetでは回避できない

SingleChildScrollView() の height を決めるために、Container() Widget でくくってあげて、 height に double.infinity を追加します。

とりあえずこれでSingleChildScrollView() の高さを決めることができました。

class Home extends StatelessWidget {
  const Home({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('ScrollViewにContainer追加'),
        ),
        body: Column(
          children: [
            const Text(
              'タイトル',
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 20,
              ),
            ),
            Container(
                height: double.infinity, // Countainer で高さを設定を追加
                child: SingleChildScrollView(
                  child: Column(
                    children: [
                      TestChild(color: Colors.red.shade400, label: 'test1'),
                      TestChild(color: Colors.green.shade400, label: 'test2'),
                      TestChild(color: Colors.blue.shade400, label: 'test3'),
                      TestChild(color: Colors.orange.shade400, label: 'test4'),
                    ],
                  ),
                )),
          ],
        ));
  }
}

しかし、これを実行すると以下のようになり、エラーとなります。

Another exception was thrown: RenderBox was not laid out:
RenderConstrainedBox#10c4f relayoutBoundary=up2 NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATE
Performing hot reload...                                               
Another exception was thrown: RenderBox was not laid out:
RenderRepaintBoundary#bb725 NEEDS-LAYOUT NEEDS-PAINT
Performing hot reload...                                                
Reloaded 1 of 567 libraries in 440ms.

I/chatty  (17213): uid=10082(com.example.my_test) 1.ui identical 1 line
E/flutter (17213): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Cannot hit test a render box with no size.
E/flutter (17213): The hitTest() method was called on this RenderBox: RenderConstrainedBox#10c4f relayoutBoundary=up2:
E/flutter (17213):   needs compositing
E/flutter (17213):   creator: ConstrainedBoxContainerColumn_BodyBuilderMediaQueryLayoutId-[<_ScaffoldSlot.body>] ← CustomMultiChildLayoutAnimatedBuilderDefaultTextStyleAnimatedDefaultTextStyle_InkFeatures-[GlobalKey#f9b76 ink renderer] ← NotificationListener ← ⋯
E/flutter (17213):   parentData: offset=Offset(0.0, 0.0); flex=null; fit=null (can use size)
E/flutter (17213):   constraints: BoxConstraints(0.0<=w<=392.7, 0.0<=h<=Infinity)
E/flutter (17213):   size: MISSING
E/flutter (17213):   additionalConstraints: BoxConstraints(0.0<=w<=Infinity, h=Infinity)
E/flutter (17213): Although this node is not marked as needing layout, its size is not set.
E/flutter (17213): A RenderBox object must have an explicit size before it can be hit-tested. Make sure that the RenderBox in question sets its size during layout.
E/flutter (17213): #0      RenderBox.hitTest. (package:flutter/src/rendering/box.dart:2394:9)
E/flutter (17213): #1      RenderBox.hitTest (package:flutter/src/rendering/box.dart:2409:6)
~
~
~

とりあえず、よくわからんエラーが出ます。

解決策

ExpandedでSingleChildScrollViewをくくってあげる

今回の問題を解決するには Expanded WidgetSingleChildScrollView をくくってあげると解決します。

Expanded is 何

この項目については個人的なメモになります。

問題を解決できた人は読み飛ばしても大丈夫です。

Expanded とはざっくりいうと、使用可能なスペースに対して自動的にいっぱいまで使えるようにするWidget。

オプションである flex  に比率をいれることで、自動的に比率通りに分けてくれる。

公式サイト

api.flutter.dev
api.flutter.dev

View を作るときに、どこにWidgetを配置するかを決めるときに Expanded を使うと便利だと感じる。