公開日 2026-06-29

FlutterのContainerとSizedBoxを使いこなす(余白・サイズ・装飾の基本)

Flutter の SizedBox・Padding・Container を最小例で整理し、余白・サイズ・装飾をどこへ書くか判断できるようにする。

目次

  1. 1. ゴールと非対象
  2. 対象読者
  3. この記事で到達する状態
  4. 非対象
  5. 2. 先に 3 つの役割を分ける
  6. 3. SizedBox は間隔と固定サイズに使う
  7. コードのポイント
  8. 4. Padding と margin を分けて考える
  9. コードのポイント
  10. 5. Container は装飾と配置をまとめる箱
  11. コードのポイント
  12. 6. 不要な Container を減らす
  13. コードのポイント
  14. 7. 迷ったときの使い分け
  15. 8. まとめ

Flutterのテーマ設計入門(ThemeData + Theme Extension) の次に、UI を組み始めると迷いやすいのが ContainerSizedBox の使い分けです。止まりやすいのは API の数ではなく、「間隔を空けたいだけなのに Container を置く」「内側の余白と外側の余白が混ざる」といった役割の曖昧さにあります。この記事では、SizedBox Padding Container を余白・サイズ・装飾の観点で整理し、最初の画面を読みやすく組むための基準をまとめます。

1. ゴールと非対象

対象読者

  • Flutter の環境構築と Dart 入門、Column / Row / Stack の基本までは終わった人
  • ContainerSizedBox をどちらも何となく置いている人
  • 余白、サイズ、背景色をどこへ書くかまだ曖昧な人

この記事で到達する状態

  • SizedBox を間隔と固定サイズのために使える
  • Paddingmargin を内側と外側の余白で説明できる
  • Container を装飾や配置が必要な場面に絞って使える
  • 「とりあえず Container」を減らす判断基準を持てる

非対象

  • ConstrainedBox / Expanded / Flexible の詳細
  • レスポンシブ設計の深掘り
  • テーマ設計やアニメーション
  • BoxConstraints の細かな仕様

この記事で押さえたいのは、レイアウトシステム全体ではありません。余白、サイズ、装飾をどの Widget へ置くかを分けることです。ここが整理できると、一覧やフォームへ進んだあとも、どこで余白や装飾を直すか追いやすくなります。

2. 先に 3 つの役割を分ける

最初に、SizedBox Padding Container の役割を分けます。

Widget主な役割まず見るポイントよくある場面
SizedBox固定の余白、固定サイズwidth / height部品同士の間隔、画像やボタンの大きさ
Padding内側の余白EdgeInsetsカードの内側、テキスト周囲の余白
Container箱のサイズ、背景、枠線、角丸、配置padding / margin / alignment / decorationカード、ラベル、通知枠

Container は何でもできますが、毎回そこへ寄せる必要はありません。役割が小さい場面では、専用 Widget のほうが意図を読み取りやすくなります。

以降のコード例はすべて lib/main.dart にそのまま貼って flutter run で確認できます。外部パッケージ不要のため、DartPad でも試せます。

3. SizedBox は間隔と固定サイズに使う

SizedBox は「空白を入れる Widget」と覚えがちですが、それだけだと用途が狭く見えます。実際には、幅や高さを固定したいときに使う小さな箱です。

次の lib/main.dart では、縦の間隔、横の間隔、固定サイズの 3 つをまとめて確認できます。

このファイルは、SizedBox が gap と固定サイズの両方に使えることを 1 画面で確認するための最小例です。カード、行内の余白、ボタンの高さを同じサンプルへまとめ、役割の違いが見えるようにしています。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('SizedBox sample')),
        body: Center(
          child: Container(
            width: 280,
            padding: const EdgeInsets.all(16),
            color: Colors.blueGrey.shade50,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  '入荷予定',
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    Container(
                      width: 56,
                      height: 56,
                      color: Colors.blue.shade100,
                      alignment: Alignment.center,
                      child: const Text('A'),
                    ),
                    const SizedBox(width: 12),
                    const Expanded(
                      child: Text('棚A-12 に 24 件の入荷予定があります'),
                    ),
                  ],
                ),
                const SizedBox(height: 16),
                SizedBox(
                  width: double.infinity,
                  height: 44,
                  child: ElevatedButton(
                    onPressed: () {},
                    child: const Text('詳細を見る'),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

コードのポイント

SizedBox(height: ...)SizedBox(width: ...) で gap を明示する

            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  '入荷予定',
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    Container(
                      width: 56,
                      height: 56,
                      color: Colors.blue.shade100,
                      alignment: Alignment.center,
                      child: const Text('A'),
                    ),
                    const SizedBox(width: 12),

縦方向と横方向の間隔を SizedBox で分けて書くと、「ここは余白だけが役割だ」とそのまま読めます。背景や枠線を持たないため、Container を gap のためだけに置いたときより責務が小さく保てます。

② ボタンのサイズも SizedBox で固定できる

SizedBox(
  width: double.infinity,
  height: 44,
  child: ElevatedButton(
    onPressed: () {},
    child: const Text('詳細を見る'),
  ),
),

SizedBox は空白専用ではなく、子に与える幅や高さを決める小さな箱でもあります。ボタンのサイズをここへ寄せると、余白と装飾を分けたまま操作部品の大きさだけを制御できます。

SizedBoxサンプル:縦横の間隔とボタン固定サイズ

4. Paddingmargin を分けて考える

余白の話で最初に混ざりやすいのが、内側と外側です。

  • Padding: 子を内側へ押し込む余白
  • margin: 箱ごと周囲から離す余白

Padding は専用 Widget として独立しています。一方、marginContainer のプロパティです。名前は別ですが、最初は内側か外側かだけ見れば十分です。

flowchart LR
  Outside[周囲の空き] --> Margin[margin]
  Margin --> Box[Container本体]
  Box --> Padding[padding]
  Padding --> Child[子Widget]

lib/main.dart の最小例です。

このファイルは、marginPadding がどちらも余白に見えても、効く場所が違うことを見比べるための最小例です。箱の外側を空ける例と、箱の内側へ文字を押し込む例を並べています。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Padding vs margin')),
        body: ListView(
          children: [
            Container(
              margin: const EdgeInsets.all(16),
              color: Colors.orange.shade100,
              child: const Text('margin だけを付けた箱'),
            ),
            Container(
              margin: const EdgeInsets.symmetric(horizontal: 16),
              color: Colors.green.shade100,
              child: const Padding(
                padding: EdgeInsets.all(16),
                child: Text('padding を付けると、文字が箱の内側へ下がる'),
              ),
            ),
            Container(
              margin: const EdgeInsets.fromLTRB(16, 16, 16, 0),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.blueGrey),
              ),
              child: const Padding(
                padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
                child: Text('外側の距離と内側の距離を分けて書ける'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

コードのポイント

margin は箱ごと周囲から離す

            Container(
              margin: const EdgeInsets.all(16),
              color: Colors.orange.shade100,
              child: const Text('margin だけを付けた箱'),
            ),

margin はコンテナ自身の外側へ効くため、周囲の部品や画面端との距離を表すときに使います。フォームやカード全体を少し離したい場面では、まずこちらを疑うと整理しやすくなります。

Padding は子を箱の内側へ押し込む

            Container(
              margin: const EdgeInsets.symmetric(horizontal: 16),
              color: Colors.green.shade100,
              child: const Padding(
                padding: EdgeInsets.all(16),
                child: Text('padding を付けると、文字が箱の内側へ下がる'),
              ),
            ),

Padding は子 Widget の周囲に内側余白を作る役割です。外側の距離を作る margin と分けておくと、あとで余白だけを直したいときに触る場所を迷わず済みます。

③ カード UI では gap・内側余白・外側余白を分担させる

            Container(
              margin: const EdgeInsets.fromLTRB(16, 16, 16, 0),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.blueGrey),
              ),
              child: const Padding(
                padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
                child: Text('外側の距離と内側の距離を分けて書ける'),
              ),
            ),

同じ箱の中に marginPadding を並べると、外側と内側の役割差がはっきり見えます。部品同士の距離は SizedBox、カード内部は Padding、カード全体と周囲の距離は margin と分けると、修正箇所を追いやすくなります。

Padding vs margin:内側と外側の余白の違い

5. Container は装飾と配置をまとめる箱

Container が便利なのは、サイズ、余白、背景色、枠線、角丸、子の配置を 1 か所でまとめられるからです。逆に言うと、そうした要素が必要な場面に絞ると、なぜ Container を置いたか追いやすくなります。

次の lib/main.dart では、業務画面の小さなステータスカードを作ります。

このファイルは、Container を「装飾と配置をまとめる箱」として使う典型例です。カード全体の見た目と、小さなステータスラベルの見た目を別々の Container で担当させています。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Container sample')),
        backgroundColor: const Color(0xFFF5F7FA),
        body: Center(
          child: Container(
            width: 300,
            margin: const EdgeInsets.all(16),
            padding: const EdgeInsets.all(16),
            alignment: Alignment.centerLeft,
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(16),
              border: Border.all(color: const Color(0xFFD6DCE5)),
              boxShadow: const [
                BoxShadow(
                  color: Color(0x14000000),
                  blurRadius: 16,
                  offset: Offset(0, 8),
                ),
              ],
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 10,
                    vertical: 4,
                  ),
                  decoration: BoxDecoration(
                    color: Colors.green.shade50,
                    borderRadius: BorderRadius.circular(999),
                  ),
                  child: Text(
                    '同期済み',
                    style: TextStyle(
                      color: Colors.green.shade800,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                const SizedBox(height: 12),
                const Text(
                  '最終送信ジョブ',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 8),
                const Text('伝票番号: SH-2026-0315'),
                const SizedBox(height: 4),
                const Text('処理件数: 24 件'),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

コードのポイント

① 外側の Container はカード全体の見た目をまとめて持つ

          child: Container(
            width: 300,
            margin: const EdgeInsets.all(16),
            padding: const EdgeInsets.all(16),
            alignment: Alignment.centerLeft,
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(16),
              border: Border.all(color: const Color(0xFFD6DCE5)),

この Container にはサイズ、内外余白、配置、背景、角丸、枠線が集まっています。こうした箱全体の見た目をまとめて持たせたいときは、PaddingSizedBox より Container の責務が素直です。

② 内側の Container はステータスラベルの装飾だけを担当する

                Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 10,
                    vertical: 4,
                  ),
                  decoration: BoxDecoration(
                    color: Colors.green.shade50,
                    borderRadius: BorderRadius.circular(999),
                  ),

内側の小さい Container は、ラベルへ背景色と角丸を付けるためだけに存在しています。同じ Container でも、どの見た目をどこで持つかを分けると、あとでカード全体とラベルだけを別々に調整しやすくなります。

Containerサンプル:カードの背景・枠線・影・ステータスラベル

6. 不要な Container を減らす

Container を減らしたい理由は、パフォーマンスより先に可読性です。何をしたいのかが小さな Widget 名で読めるほうが、修正箇所を追いやすくなります。

判断基準は次の表で十分です。

やりたいことまず選ぶ Widget理由
タイトルと本文の間を 12px 空けたいSizedBoxgap だとすぐ分かるため
カードの内側へ 16px の余白を入れたいPadding子を内側へ押し込む目的だから
カード全体へ背景色、角丸、枠線を付けたいContainer箱の見た目をまとめて持たせるため
子を中央へ寄せたいだけCenter または Align配置だけなら専用 Widget のほうが意図が明確だから
背景色を付けたいだけColoredBox色だけが目的なら責務が小さいから

比較用の lib/main.dart です。

このファイルは、Container を何でも箱にする書き方と、役割ごとに Widget を分ける書き方を同じ画面へ並べた比較例です。余白と背景色の責務を分離すると、どこを直すべきかが読み取りやすくなります。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Avoid overusing Container')),
        body: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            const Text(
              'Container を何でも箱に入れた例',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 12),
            Container(
              padding: const EdgeInsets.all(16),
              color: Colors.red.shade50,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text('出荷ステータス'),
                  Container(height: 12),
                  Container(
                    padding: const EdgeInsets.all(8),
                    color: Colors.white,
                    child: const Text('処理待ち 3 件'),
                  ),
                ],
              ),
            ),
            const SizedBox(height: 24),
            const Text(
              '役割ごとに分けた例',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 12),
            ColoredBox(
              color: Colors.green.shade50,
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('出荷ステータス'),
                    const SizedBox(height: 12),
                    const ColoredBox(
                      color: Colors.white,
                      child: Padding(
                        padding: EdgeInsets.all(8),
                        child: Text('処理待ち 3 件'),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

コードのポイント

① 余白のためだけに Container を置くと責務が見えにくくなる

            Container(
              padding: const EdgeInsets.all(16),
              color: Colors.red.shade50,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text('出荷ステータス'),
                  Container(height: 12),
                  Container(

前半の例では、gap のために Container(height: 12) まで登場しており、どこが余白でどこが装飾かを一目で追いにくくなっています。背景や枠線を持たない空白なら、より責務の小さい Widget へ寄せたほうが意図が明確です。

② 役割ごとに ColoredBoxPaddingSizedBox へ分けると読みやすい

ColoredBox(
  color: Colors.green.shade50,
  child: Padding(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('出荷ステータス'),
        const SizedBox(height: 12),

後半は「色を付ける」「内側へ寄せる」「距離を空ける」を別々の Widget 名で表しています。背景色と角丸と位置調整を一度に持たせたい場面なら Container で構いませんが、責務を分けられるなら分けたほうが修正箇所を追いやすくなります。

Container多用例vs役割分担例の比較

7. 迷ったときの使い分け

よくある場面を短く戻すと、判断基準は次のようになります。

場面選ぶもの見る理由
タイトルと本文の間を 12px 空けるSizedBox(height: 12)距離そのものを表しているから
アイコンとテキストの間を 8px 空けるSizedBox(width: 8)横方向の gap だと分かるから
カードの中身を 16px だけ内側へ寄せるPadding内側余白の責務だから
カード全体を画面端から 16px 離すmargin箱ごとの外側余白だから
カードへ背景色、枠線、角丸を付けるContainer箱の見た目をまとめて持つため

迷ったら、次の順で見ると整理しやすくなります。

  1. 今やりたいのは距離か、内側余白か、箱の見た目か
  2. gap だけなら SizedBox で足りないか
  3. 子の周囲だけなら Padding で足りないか
  4. それでも背景、枠線、角丸、配置が必要なら Container を使う

8. まとめ

SizedBox は間隔と固定サイズ、Padding は内側余白、Container は箱の見た目と配置をまとめる Widget です。この 3 つを役割で分けるだけでも、Flutter の UI コードで各部品の責務を追いやすくなります。

最初の 1 画面では、gap のためだけに Container を足しがちです。そこを SizedBoxPadding へ分けるだけでも、どこを直せばよいかが見えやすくなります。次に一覧やフォームへ進むときも、この分け方がそのまま効きます。

シリーズ 11/14

このシリーズ

Flutter導入と基礎

  1. 1. Windows 11で始めるFlutter開発環境:Android Emulatorで動かすまで
  2. 2. Flutter + FVM で開発環境のバージョンを固定する
  3. 3. Flutterで画像・SVG・アイコンを管理する(flutter_gen最小構成)
  4. 4. Flutterで最初に詰まりやすいDartの書き方:final・const・null safety・async/await を最初に整理する
  5. 5. DartのStream入門(非同期データの流れをつかむ)
  6. 6. FlutterのWidgetライフサイクル入門(initState / dispose で詰まらないために)
  7. 7. FlutterでBuildContextとKeyを理解する
  8. 8. Flutterのレイアウト入門(Column / Row / Stack の使い分け)
  9. 9. Flutterのテーマ設計入門(ThemeData + Theme Extension)
  10. 10. FlutterでMediaQueryとLayoutBuilderを使って画面サイズに対応する(スマホ・タブレット両対応)
  11. 11. FlutterのContainerとSizedBoxを使いこなす(余白・サイズ・装飾の基本) 現在の記事
  12. 12. FlutterのListViewとGridViewで一覧画面を作る(基本パターン)
  13. 13. Flutterのダイアログ・スナックバー・ボトムシートを使う(確認・通知UIの基本)
  14. 14. FlutterのTabBarとBottomNavigationBarで複数画面を切り替える