Flutter: BoxConstraints を理解する

Flutter: BoxConstraints を理解する

August 17, 2024

Flutter の BoxConstraints について。

期待通りにはいかない表示 #

次のコードでは、画面に何が表示されるでしょうか?

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: SizedBox(
      width: 100,
      height: 100,
      child: ColoredBox(
        color: Colors.orange,
      ),
    ),
  ));
}

「一辺 100px」のオレンジ色の四角形」が表示されると思いきや、実際には「画面いっぱいに(つまり画面分の高さと幅の)」オレンジ色が表示されます。

ここで Center ウィジェットで囲ってみるとしましょう。すると期待通りの 100px 四方のオレンジ色が表示されます。

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: Center(
      child: SizedBox(
        width: 100,
        height: 100,
        child: ColoredBox(
          color: Colors.orange,
        ),
      ),
    ),
  ));
}

これにはウィジェットの大きさの制約である BoxConstraints が関係しています。

BoxConstraints を確認してみる #

LayoutBuilder というウィジェットを利用するとそのウィジェットが親ウィジェットから課されている BoxConstraints を取得することができます。

BoxConstraints は次の4つの値からなるシンプルなものです。

👉 minWidth, maxWidth, minHeight, maxHeight

Center で囲わなかった場合

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: LayoutBuilder(builder: (
      BuildContext context,
      BoxConstraints constraints,
    ) {
      print("### 親からの制約 ###");
      print("minWidth:  ${constraints.minWidth}");
      print("maxWidth:  ${constraints.maxWidth}");
      print("minHeight: ${constraints.minHeight}");
      print("maxHeight: ${constraints.maxHeight}");

      return SizedBox(
        width: 100,
        height: 100,
        child: LayoutBuilder(builder: (
          BuildContext context,
          BoxConstraints constraints,
        ) {
          print("### 子自身の制約 ###");
          print("minWidth:  ${constraints.minWidth}");
          print("maxWidth:  ${constraints.maxWidth}");
          print("minHeight: ${constraints.minHeight}");
          print("maxHeight: ${constraints.maxHeight}");

          return const ColoredBox(
            color: Colors.orange,
          );
        }),
      );
    }),
  ));
}
### 親からの制約 ###
minWidth:  685
maxWidth:  685
minHeight: 788
maxHeight: 788
### 子自身の制約 ###
minWidth:  685
maxWidth:  685
minHeight: 788
maxHeight: 788

Center で囲った場合

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: Center(
      child: LayoutBuilder(builder: (
        BuildContext context,
        BoxConstraints constraints,
      ) {
        print("### 親からの制約 ###");
        print("minWidth:  ${constraints.minWidth}");
        print("maxWidth:  ${constraints.maxWidth}");
        print("minHeight: ${constraints.minHeight}");
        print("maxHeight: ${constraints.maxHeight}");

        return SizedBox(
          width: 100,
          height: 100,
          child: LayoutBuilder(builder: (
            BuildContext context,
            BoxConstraints constraints,
          ) {
            print("### 子自身の制約 ###");
            print("minWidth:  ${constraints.minWidth}");
            print("maxWidth:  ${constraints.maxWidth}");
            print("minHeight: ${constraints.minHeight}");
            print("maxHeight: ${constraints.maxHeight}");

            return const ColoredBox(
              color: Colors.orange,
            );
          }),
        );
      }),
    ),
  ));
}
### 親からの制約 ###
minWidth:  0
maxWidth:  685
minHeight: 0
maxHeight: 788
### 子自身の制約 ###
minWidth:  100
maxWidth:  100
minHeight: 100
maxHeight: 100

子は親の制約のなかで表現する #

Flutter ではウィジェットはツリー構造をとります。このとき、ツリーの最上位のウィジェットを除いて全てのウィジェットは親を持ちます。

そしてあるウィジェットがどのサイズで表示されるかは親からの制約によって決まります。

改めて先ほどの BoxConstraints を確認してみます。

Center で囲わなかった場合 Center で囲った場合
親からの制約 minWidth 685 0
maxWidth 685 685
minHeight 788 0
maxHeight 788 788
子自身の制約 minWidth 685 100
maxWidth 685 100
minHeight 788 100
maxHeight 788 100

「Center で囲わなかった場合」は親から minWidth = maxWidth = 685minHeight = maxHeight = 788 の制約が課されています。つまり、子は固定長で「幅 685px / 高さ 788px」のサイズをとることしか許されていません。そのため、いくら子が「幅 100px / 高さ 100px」で表示しようとしても、親の制約が優先されることで画面いっぱいの表示になります。

「Center で囲った場合」は親からの minWidth および minHeight の制約が 0px となっています。そのため、子はその制約のなかで表示が許され、自身が希望する「幅 100px / 高さ 100px」が描画できるというわけです。

このように Center ウィジェットは親から課されている制約を緩める機能があります。Center のほか、Align、Flex、Column、Row も制約を緩める機能があり、具体的には、minWidth および minHeight の制約を 0 にします。

そして親からの制約を完全に無効化するものとして、UnconstrainedBox ウィジェットがあります。これは4つすべての制約を無効にするため、子ウィジェットは自由なサイズの描画が可能です。ただし描画内容によっては、子ウィジェットが本来取ることのできる領域からはみ出す可能性もあるので注意して使う必要があります。

逆に制約を追加するためのウィジェットが ConstrainedBox ウィジェットです。

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: Center(
      child: LayoutBuilder(builder: (
        BuildContext context,
        BoxConstraints constraints,
      ) {
        print("### Center からの制約 ###");
        print(constraints);

        return ConstrainedBox(
          constraints: BoxConstraints.tight(const Size(200, 200)),
          child: LayoutBuilder(builder: (
            BuildContext context,
            BoxConstraints constraints,
          ) {
            print("### ConstrainedBox からの制約 ###");
            print(constraints);

            return SizedBox(
              width: 100,
              height: 100,
              child: LayoutBuilder(builder: (
                BuildContext context,
                BoxConstraints constraints,
              ) {
                print("### 子自身の制約 ###");
                print(constraints);

                return const ColoredBox(
                  color: Colors.orange,
                );
              }),
            );
          }),
        );
      }),
    ),
  ));
}

以下の実行結果は次の通りです。ConstrainedBox により固定長で「幅 200px / 高さ 200px」の制約が課されたことにより、いくら子が「幅 100px / 高さ 100px」を希望しようとも許可されず、最終的に画面には「幅 200px / 高さ 200px」のオレンジの正方形が表示されます。

### Center からの制約 ###
BoxConstraints(0.0<=w<=685.0, 0.0<=h<=788.0)
### ConstrainedBox からの制約 ###
BoxConstraints(w=200.0, h=200.0)
### 子自身の制約 ###
BoxConstraints(w=200.0, h=200.0)

その他参考 #