Chapter 13

  Constraints go down, Size go up

heyhey1028
heyhey1028
2023.02.17に更新

Flutter では Widget がツリー構造を構成し、UI を構築していることを学びました。Widget を配置の仕方は分かったとして気になるのは各 Widget のサイズです。

1つ1つの Widget のサイズはどう決まるのでしょうか?

サイズを決定づける2つの要素

Widget のサイズを決定づける要素は大きく分けて2つあります。

それが「 親から子に渡されるConstraints(制約) 」と「 Widget自身の習性 」 です。

親から子に渡されるConstraints

Widget サイズの決まり方について最も重要な概念がConstraintsです。

Constraintsとは言葉が表すように「(サイズの)制約」を意味します。Flutter では描画処理が行われる際に、親 Widget から子 Widget に対して、サイズ制約を課す BoxConstraints クラスが渡されます。

このBoxConstraintsクラスには子 Widget が取ることが出来る x 軸 y 軸方向の最大最小値が設定されています。子 Widget はこの与えられたサイズ領域の中で自身が取るサイズを計算します。

このサイズ制約では具体的なサイズが指定される場合もあれば、無制限(double.infinity)という制約が指定される場合もあります。

BoxConstraints クラス
  const BoxConstraints({
    this.minWidth = 0.0,
    this.maxWidth = double.infinity,
    this.minHeight = 0.0,
    this.maxHeight = double.infinity,
  }){
    final double minWidth;
    final double maxWidth;
    final double minHeight;
    final double maxHeight;
  }

Widget 自身の習性

サイズを決定づけるもう1つの要素はその Widget 自体が持つ習性です。

Widget はそれぞれ「与えられたサイズ制約の中でどのように振る舞うか」という設定が含まれています。

この習性には大きく分けて以下の3つがあります。

  1. なるべく大きくなろうとする
  2. なるべく小さくなろうとする
  3. 制約に関係なく特定のサイズになろうとする

また 上記以外にも与えられた引数に応じて取る振る舞いを変える Widgetx 軸 y 軸方向で振る舞いが違う Widget など、Widget によって非常に様々です。

使っている Widget がどういった振る舞いをするかは使っていく中で覚えていく必要があります。

サイズの決まり方

端的には以上の2つの要素の組み合わせで、Widget は自身の取るサイズを計算します。

例えば子 Widget に対し、以下のサイズ制約を課す Widget があったとします。

- 最大幅 500
- 最大高さ 500
- 最小幅 10
- 最小高さ 10

これに対し、なるべく大きくなろうとする習性を持つ子 Widget を配置するとその Widget は高さ500、幅500のサイズになります。

なるべく小さくなろうとする Widget を配置すれば、高さ10、幅10。特定のサイズになろうとする Widget であればその特定のサイズを取ります。

⚠️ 無制限なサイズ制約に注意

サイズ制約の項で触れましたが、Widget によっては子に対して無制限のサイズ制約を渡す Widget があります。

これらの Widget では配置する子 Widget によっては UI エラーを引き起こすことがあるので注意が必要です。

このような Widget の子に「なるべく大きくなろう」とする Widget を配置してしまうと無制限に大きくなろうとすることで画面サイズを超えてしまう為、エラーとなり、以下のような黄色と黒の縞々が表示されます。

具体的にはRowColumnListViewといったある方向に子 Widget を複数レイアウトしていくような Widget では、その主軸方向に対しサイズ制約が無制限となる制約を子 Widget に渡します。

これらの Widget は自身の振る舞いとして「なるべく大きくなろうとする」widget でもあります。そのため、よくあるエラーケースとしてはColumnに子 widget としてListViewを配置するようなケースがあります。

Column(
    children: [
        ListView.builder(
            itemCount: 50,
            shrinkWrap: true,
            itemBuilder: (context, index) {
            return ListTile( title: Text(index.toString()));
            },
        ),
    ],
),

このようなケースではExpandedFlexibleといったColumnRowの子 Widget に対して使うことを前提に用意された Widget を使って解決する必要があります。

その詳細については Chapter4 のハンズオンの中で説明します。

余談:Constraints go down, Size go upとは?

この章のタイトルになっているConstraints go down, Size go upというフレーズは Widget ツリー全体のサイズ決定処理の流れを表すフレーズです。(実際にはこの後にParent set positionというフレーズが続きます)

個々の Widget のサイズ決定については前述した通りですが、このサイズ決定の処理は Widget ツリー構築時に使用されている Widget 全てに対して行われます。

Widget ツリー構築時にツリーのルートから前述のBoxConstarintsを末端の Widget に達するまで渡していきます。末端に到達した時点で初めて Widget 自身の振る舞いとBoxConstraintsを照らし合わせ、サイズを決定します。

その後、今度は末端から決まった自身のサイズを親 Widget に伝達していきます。親 Widget は伝達された子 Widget のサイズを元に今度は子 Widget の配置を定めます。この処理をルートの Widget に到達するまで繰り返します。

複雑な処理のため、最初から理解する必要はありませんが、UI を構築する際の重要なロジックなので、Flutter に慣れてきたら学び直してみると良いかもしれません。

より詳しい解説を知りたい方は以下のページを参照してみてください
https://docs.flutter.dev/development/ui/layout/constraints

まとめ

この章では レイアウトを構築する際に重要な Widget のサイズを決定する要素について学んでいきました。

UI が思うようにレイアウト出来ない時はぜひ一度以下の 2 点を自分に問いかけてみてください。

  1. 親から子にどんなサイズ制約が渡されているか?
  2. 与えられたサイズ制約内でどのような振る舞いをする Widget か?

さて次章からはより具体的にコードの方を見ていきましょう