🧩

【Flutter】Flexible/Expanded/Spacerは結局どう違うのか

2024/07/18に公開1

株式会社Sally新人エンジニアの @wellPicker です。
日々の業務の中で学んだことを書き残すことで読者の皆様(と将来の自分)の役に立てればと思います。

はじめに

Flutterでは、Widgetを使用してUIを構築します。
複数のWidgetを縦や横に並べたい時にはColumnやRowを使用しますが、この時にただ並べるのではなくそれぞれのWidgetの間隔などレイアウトを調整する必要があることが多いです。
そんな時にFlexible/Expanded/SpacerといったWidgetを使用すると思いますが、この三つがどう違うのかは意外と忘れがちです。
そこで、本記事ではこれらのWidgetの関係性と違い、使い方についてまとめたいと思います。

いいから結論を教えてくれ

というせっかちな人向けに。

  • Flexibleは「大きさの上限を決める」Widget
  • Expandedは「必ず上限の大きさまで広がる」Widget
  • Spacerは「余白を上限の大きさまで広げる」Widget

と考えるとわかりやすいと思います。

Flexibleとは

Flexibleは主にColumn, Rowの中で使用されます。
親Widgetの中で利用可能なスペースに合わせて、Flexibleの子Widgetのサイズを柔軟に変更する目的で使います。

プロパティの解説

Flexibleはchildの他に、flexとfitという二つのプロパティを持ちます。

  • flex:親同じ親の中に二つ以上のFlexibleがある場合、flexの値が「親Widgetに対して子Widgetが占めることが可能な最大のスペースの比率」を表します。
  • fit:FlexFit.tightかFlexFit.looseのいずれかの値を取りますが、後述する理由により普通はFlexFit.looseです。FlexFit.looseの場合、Flexibleは可能な限り子Widgetのサイズに合わせて小さくなろうとします。

flexはあくまで最大の時の占有スペースの比率であり、Flexibleが最大まで広がらなくても良い状況ではこの値はなんの意味も持たないことには注意しなければいけません。

具体的な挙動

Flexibleでラップした子Widgetの具体的な挙動は、

  • 親Widgetからはみ出なくなる
  • 子Widgetが十分に小さい場合、それに合わせて小さくなる
  • 子Widgetが十分に大きい場合でも、親Widgetの一定の割合以上には大きくならない(必要なら子Widgetを折りたたむ)
    ことになります。
Flexible(子要素が十分に小さい場合)
        Row(
          children: [
            Flexible(
              flex: 1,
              child: Container(
                color: Colors.red,
                child: const Text(
                  'flex: 1',
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
            Flexible(
              flex: 2,
              child: Container(
                color: Colors.green,
                child: const Text(
                  'flex: 2',
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
          ],
        ),

Flexible(子要素が十分に大きい場合)
        Row(
          children: [
            Flexible(
              flex: 1,
              child: Container(
                color: Colors.red,
                child: const Text(
                  'The flex of this Flexible Widget is 1',
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
            Flexible(
              flex: 2,
              child: Container(
                color: Colors.green,
                child: const Text(
                  'The flex of this Flexible Widget is 2',
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
          ],
        ),

Expandedとは

Expandedの元の実装を見ると、実はExpandedはFlexibleを継承していることがわかります。その上でfitプロパティの値がFlexFit.tightに設定されたものがExpandedです(Flexibleを使うときにFlexFit.tightを使用しないのもこのためです)。
fitがFlexFit.tightに設定されているため、Expandedは可能な限り大きくなろうとします。

プロパティの解説

したがって、Expandedはchildの他にflexのプロパティを持ちます。中身はFlexibleと同じです。

具体的な挙動

Expandedでラップした子Widgetの具体的な挙動は、

  • 親Widgetからはみ出なくなる
  • 子Widgetの大きさに関わらず、親Widgetの一定の割合まで大きくなる
    ことになります。太字の部分がFlexibleとの唯一の違いです。
    親Widget内にExpandedが一つしかない場合は、可能な限り親Widgetのスペースを埋めるように広がってくれます。
Expandedの場合
        Row(
          children: [
            Expanded(
              flex: 1,
              child: Container(
                color: Colors.red,
                child: const Text(
                  'flex: 1',
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
            Expanded(
              flex: 2,
              child: Container(
                color: Colors.green,
                child: const Text(
                  'flex: 2',
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
          ],
        ),

Spacerとは

FlexibleとExpandedの違いを解説した記事はよくあるのですが、Spacerについても解説した記事はあまり見かけない印象です。
しかし、SpacerもColumnやRowのレイアウト調整に使うことが多いので、一緒に理解した方が良いと思います。
そんなSpacerですが、実はExpandedのchildにSizedBoxを渡しただけのWidgetです。

プロパティの解説

したがって、Spacerはflexプロパティのみを持ちます。当然、中身はFlexibleと同じです。

具体的な挙動

Expandedは「親Widgetの一定の割合まで広がる」Widgetでした。
そのchildにSizedBoxを渡したSpacerはすなわち、「余白を親Widgetの一定の割合まで広げる」ためのWidgetです。
親Widget内にSpacerが一つしかない場合は、可能な限り親Widgetのスペースを埋めるように余白を広げてくれます。

Spacerは主に余白を広げるのに使う
        Row(
          children: [
            Container(
              color: Colors.red,
              child: const Text(
                'flex: 1',
                style: TextStyle(fontSize: 20),
              ),
            ),
            const Spacer(),
            Container(
              color: Colors.green,
              child: const Text(
                'flex: 2',
                style: TextStyle(fontSize: 20),
              ),
            ),
          ],
        ),

まとめ

本記事では、FlutterのFlexible/Expanded/Spacerについて解説しました。
ここまで読んでくださった方ならお分かりかと思いますが、実はExpandedもSpacerも元を辿ればFlexibleなのです。
当然、それぞれが持つflexプロパティも全く同じものなので、これらを同時に使う場合は注意する必要があります
実は自分が今回この記事を書いたのも、ExpandedとSpacerを同時に設定してしまってデザイン崩れを引き起こしたことがきっかけでした。

FlutterにはUIを構築するための便利なWidgetが多数用意されていますが、予期せぬデザイン崩れを防ぐためにはそれらの正しい仕組みを理解することが重要です。
読者の皆様がFlutterのUI構築について理解する一助となれば幸いです。

UZU テックブログ

Discussion