Flutterを書き始めた頃によく混乱するのが、
「テーマで設定したのにサイズが変わらない」 「ボタンの幅が勝手に広がる」
という現象です。
これはFlutterがCSSとは違う考え方でレイアウトを行っているためです。
CSSは「どの指定が勝つか」
例えばCSSでは次のようなコードがあります。
button { width: 100px; } .primary-button { width: 200px; }
この場合、
- 詳細度(Specificity)
- 記述順
- !important
などによって最終的なサイズが決まります。
つまりCSSは、
「複数の指定のうち、どれが勝つか」
という世界です。
Flutterは「親が制約を渡す」
Flutterではレイアウトの流れが異なります。
親 ↓ 制約を渡す ↓ 子 ↓ その範囲内でサイズを決める
例えば次のコードを考えます。
FilledButton(
style: FilledButton.styleFrom(
minimumSize: const Size(0, 48),
),
onPressed: () {},
child: const Text('保存'),
)
これは、
「高さは最低48欲しい」
というボタンの希望です。
しかし親が次のような制約を与えると、
SizedBox(
width: 300,
child: FilledButton(
onPressed: () {},
child: const Text('保存'),
),
)
ボタンは300px幅で表示されます。
これは親が、
「300pxで描いてね」
と指示しているためです。
Themeより親Widgetの方が強い
Flutterでは次の順番で考えると理解しやすいです。
Theme ↓ Widget自身の設定 ↓ 親Widgetの制約
例えば、
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
minimumSize: const Size(0, 48),
),
)
はボタンのデフォルト設定です。
さらに個別のボタンで、
FilledButton(
style: FilledButton.styleFrom(
minimumSize: const Size(0, 60),
),
)
と書けば60pxが採用されます。
しかし親が、
SizedBox( width: 300, child: FilledButton(...), )
と指定していれば、ボタンは300px幅になります。
実際に遭遇した例
例えば次のコードです。
bottomNavigationBar: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: FilledButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('カードを追加'),
),
),
),
テーマでは、
minimumSize: const Size(0, 48)
としているのに、ボタンが横いっぱいに広がります。
最初はテーマ設定がおかしいように見えます。
しかし実際には、
bottomNavigationBar
が子Widgetに対して画面幅いっぱいの制約を渡しているためです。
つまり、
「テーマが負けた」
のではなく、
「親の制約の中でボタンがレイアウトされた」
というのが正しい理解になります。
Flutterでレイアウトが崩れたらまず親を見る
Flutterでサイズがおかしいと感じたら、
まず親Widgetを疑う癖をつけると原因が見つけやすくなります。
よく犯人になるのは次のようなWidgetです。
- SizedBox
- Expanded
- Flexible
- Container(width: ...)
- ScaffoldのbottomNavigationBar
- ScaffoldのAppBar
FlutterはCSSのような「スタイルの勝敗」よりも、
「親から渡される制約」
を理解することが重要です。
この考え方に慣れると、Flutterのレイアウトはかなり読みやすくなります。