アプリ開発

FlutterでColumnの中にListViewを入れるとerrorになる件

こんにちは。マークです。

今回はFlutterでColumnとListViewを組み合わせて使う場合に、よく出るエラーの解決策をご紹介します。

ColumnとListViewを併用する場面

タイトル+アイテムの数だけリストを並べる

みたいな時。

以下の写真のような場合ですね^^;

実際は、アイテムが10個あります。

Column + ListViewでよく出るエラー

コードは以下の通り

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Columnの中にListView'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Column(
          children: [
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 20),
              child: Text(
                'タイトル',
                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              ),
            ),
            ListView(
              children: [
                for (var i = 0; i < 10; i++)
                  ListTile(
                    title: Text('$i番目のアイテム'),
                  )
              ],
            )
          ],
        ));
  }
}

この場合のエラーログをコピペしてきました。

flutter: Vertical viewport was given unbounded height.
flutter: Viewports expand in the scrolling direction to fill their container. In this case, a vertical
flutter: viewport was given an unlimited amount of vertical space in which to expand. This situation
flutter: typically happens when a scrollable widget is nested inside another scrollable widget.
flutter: If this widget is always nested in a scrollable widget there is no need to use a viewport because
flutter: there will always be enough vertical space for the children. In this case, consider using a Column
flutter: instead. Otherwise, consider using the “shrinkWrap” property (or a ShrinkWrappingViewport) to size
flutter: the height of the viewport to the sum of the heights of its children.

簡単に訳すと、

WIdgetの垂直方向のサイズが無限になってしまう

ということです。

いくつかの解決策がそのまま書いてあります。

解決策1:そもそもListViewを使う必要がないのでは?

上記で示したコードの場合は、Columnだけで完結させることができます。

Column(
          children: [
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 20),
              child: Text(
                'タイトル',
                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              ),
            ),
            for (var i = 0; i < 10; i++)
              ListTile(
                title: Text('$i番目のアイテム'),
              )
          ],
        ));
  }

これでもうまくいきます。

(ちなみに、この例の場合はColumnをSingleChildScrollViewで囲まないとOverflowします^^;)

本当に、ListViewを使う必要があるのかを見直してみましょう!

 

解決策2:ListViewのshrinkWrapを使う

以下のようにListViewのプロパティのshrinkWrapを使うことで、ListViewが高さを持ちます。

ListView(
                //ここ
                shrinkWrap: true,
                children: [
                  for (var i = 0; i < 10; i++)
                    ListTile(
                      title: Text('$i番目のアイテム'),
                    )
                ],
              )

エラーログにも強調して書いてありますね^^

解決策3:heightを持つWidgetのchildにListViewを入れる

そもそもエラーの原因は、Widgetの垂直方向のサイズが無限になってしまうことでした。

なので、ContainerやSizedBoxで括ってheightを定義してやればOKです。

SizedBox(
                height: 200,
                child: ListView(
                  //ここ
                  children: [
                    for (var i = 0; i < 10; i++)
                      ListTile(
                        title: Text('$i番目のアイテム'),
                      )
                  ],
                ),
              )

一応実装できるコード例と挙動が分かるように、以下のコードペンの例を触ってみてください^^

See the Pen
Columnの中にListView2
by maaku saitou (@makumaaku-the-selector)
on CodePen.

SingleChildScrollView + Expandedもエラーが起きがち

上の例で、shrinkWrapを用いた場合には、ListViewはアイテムを全て表示できる最小のサイズになります。

しかし例では、アイテムが10個あったのでボトムがオーバーフローしたと思います。

そこで、SingleChildScrollViewを用いて対処しました。

オーバーフローの対処として、SingleChildScrollViewを導入するのはありがちなことですが、

SingleChildScrollView+Expandedもよくエラーを引き起こしがち

なので注意が必要です。

以下のようなコードはエラーになります。

(今回はLIstView.builderを例に用います)

Scaffold(
        body: SingleChildScrollView(
          child: Column(
            children: [
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 20),
                child: Text(
                  'タイトル',
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                ),
              ),
              Expanded(
                child: ListView.builder(
                  shrinkWrap: true,
                  itemCount: 10,
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text('$index番目'),
                    );
                  },
                ),
              )
            ],
          ),
        ));

エラーログは以下の通り

The following assertion was thrown during performLayout():
RenderFlex children have non-zero flex but incoming height constraints are unbounded.
When a column is in a parent that does not provide a finite height constraint, for example if it is
in a vertical scrollable, it will try to shrink-wrap its children along the vertical axis. Setting a
flex on a child (e.g. using Expanded) indicates that the child is to expand to fill the remaining
space in the vertical direction.
These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child
cannot simultaneously expand to fit its parent.
Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible
children (using Flexible rather than Expanded). This will allow the flexible children to size
themselves to less than the infinite remaining space they would otherwise be forced to take, and
then will cause the RenderFlex to shrink-wrap the children rather than expanding to fit the maximum
constraints provided by the parent.

一言で訳すと、

Expandedは残りのスペースいっぱいに広がるものですが、「残りのスペースって何?」という話になるということです。

SingleChildScrollViewのchildにLIstView.builderが入っていることで、残りのスペースが無限になっていてエラーになっています。

これがわかれば、エラーの対処法はなんとなく予測がつくかもしれないですね。

解決策1:SingleChildScrollViewをはずす

以下のように、ListViewの親のSingleChileScrollViewをはずすとうまくいきます。

Column(
          children: [
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 20),
              child: Text(
                'タイトル',
                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              ),
            ),
            Expanded(
              child: ListView.builder(
                itemCount: 10,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text('$index番目'),
                  );
                },
              ),
            )
          ],
        )

これなら、タイトルを除いた残りのスペースいっぱいにListViewが広がります。

解決策2:ExpandedをやめてFlexibleを使用

エラーログには、以下のような解決策が示されています。

Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible children (using Flexible rather than Expanded).

ColumnのmainAxisSizeをMainAxisSize.minにして、Expandedでなく、Flexibleを使い、fitにはFlexFit.loose を入れろと。

それを踏まえて以下のようなコードで修正は可能です。

SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 20),
                child: Text(
                  'タイトル',
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                ),
              ),
              Flexible(
                fit: FlexFit.loose,
                child: ListView.builder(
                  shrinkWrap: true,
                  itemCount: 10,
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text('$index番目'),
                    );
                  },
                ),
              )
            ],
          ),
        )

2つの解決策を示しましたが、1の方が楽ですよね。

できれば1で解決したいものです^^;

一応挙動が知りたい方のためにコードペンも用意しました。

x0.5にすると一番見やすいかもですね。

 

See the Pen
Column+ListView
by maaku saitou (@makumaaku-the-selector)
on CodePen.

まとめ

いかがでしたか?

Columnの中にListViewを入れる場合のエラーは、開発していく上で1度は通る道かと思います。

僕も初期の頃は何度か遭遇しました^^;

このエラーはログがめちゃくちゃ丁寧に出るので、しっかりと英語を読めば解決できるものです。

大事なことは、

サイズを持たせる

ことです。

ぜひ、参考にしてください!

僕の書いてる全記事はこちらから見れます↓

サイトマップマクログのサイトマップです。こちらから全記事のリストがご覧いただけます。...