PR

Fletのレイアウトが崩れてもエラーが出ない理由|Row/Column・expand・ResponsiveRowで直す方法

Python

前回、Fletのアイコン(ft.Icons)がバージョンのたびに書き方を変えていて、AIに書かせるとAttributeErrorになる、という話を書きました。

🥕 前回の記事 【保存版】Fletアイコン一覧&使い方|バージョンで名前が変わってAIが誤爆する理由 Flet 1.0の破壊的変更とアイコンAPIの変遷、AIが古い書き方を出す理由をまとめました。 →

アイコンの書き方さえ揃えば安心、と思っていたのですが、次に手を動かしてみて驚いたのは「レイアウトが崩れているのに、コンソールにもエラーにも何も出ない」ことでした。AIに「サイドバー付きのダッシュボードを作って」と頼んで出てきたコードは、見た目には自然。ですがブラウザで開くと、3枚並ぶはずのカードのうち1枚が画面からはみ出ていました。

この記事では、Fletのレイアウトがなぜエラーなしで崩れるのかという検証結果と、AIがよくやる崩し方のパターン、expandやResponsiveRowを使った直し方までを、動かした画面とあわせてまとめます。

この記事で分かること

  • FletのRow/Columnの基本(横に並べる箱・縦に並べる箱)
  • Fletはレイアウトが崩れてもエラーもコンソール警告も出さないという検証結果
  • AIが書いたコードでよくある崩れ方(expand忘れ、深いネストでの消失)
  • ResponsiveRowで画面幅に応じて自動的に列数を変える方法
  • 項目が多すぎるときにscroll=ft.ScrollMode.AUTOで横スクロールにする方法
  • 業務ダッシュボードを例にしたBefore/Afterの直し方

スポンサーリンク

FletのRow/Column入門:横に並べる箱・縦に並べる箱

Fletでレイアウトを組む基本は、ft.Row(横に並べる)とft.Column(縦に並べる)の2つです。中に入れた部品を、指定した方向へ順番に並べていきます。

import flet as ft
from layout_common import setup_page


def box(label, color):
    return ft.Container(
        content=ft.Text(label, color=ft.Colors.WHITE, weight=ft.FontWeight.BOLD),
        bgcolor=color,
        width=110,
        height=70,
        alignment=ft.Alignment.CENTER,
        border_radius=10,
    )


def main(page: ft.Page):
    setup_page(page, "Row Column Basic", bgcolor=ft.Colors.WHITE)
    page.padding = 30

    page.add(
        ft.Text("ft.Row(...) : 横に並べる箱", size=14, color=ft.Colors.GREY_700),
        ft.Row([box("A", ft.Colors.INDIGO), box("B", ft.Colors.INDIGO_300), box("C", ft.Colors.INDIGO_100)], spacing=12),
        ft.Container(height=24),
        ft.Text("ft.Column(...) : 縦に並べる箱", size=14, color=ft.Colors.GREY_700),
        ft.Column([box("A", ft.Colors.TEAL), box("B", ft.Colors.TEAL_300), box("C", ft.Colors.TEAL_100)], spacing=12),
    )


ft.run(main, view=ft.AppView.WEB_BROWSER, port=8561)
ft.Rowとft.Columnの基本
▲ 上段がft.Row(横並び)、下段がft.Column(縦並び)

setup_pageは共通の初期設定(タイトル・テーマ・背景色)をまとめた自作の補助関数で、この後のサンプルでも使い回します。中身は次の通りです。

import flet as ft


def setup_page(page: ft.Page, title="Flet Layout Demo", bgcolor=None):
    page.title = title
    page.theme = ft.Theme(color_scheme_seed=ft.Colors.INDIGO)
    page.bgcolor = bgcolor or ft.Colors.GREY_50
本記事のコードでは検証環境固有のフォント設定は省いています。手元の環境で試す場合、この関数のままで日本語は問題なく表示されます。

ここまではCustomTkinterのpack(side="left")のような感覚に近く、迷うところはありません。問題はここからです。


スポンサーリンク

Fletはレイアウトが崩れてもエラーもコンソール警告も出さない

サイドバー(幅220px)と、カード3枚(各幅300px)を横に並べただけの、次のシンプルなコードを動かしてみます。

import flet as ft
from layout_common import setup_page


def card(label, color):
    return ft.Container(
        width=300,
        height=140,
        bgcolor=color,
        border_radius=10,
        padding=14,
        content=ft.Text(label, weight=ft.FontWeight.BOLD),
    )


def main(page: ft.Page):
    setup_page(page, "Overflow Demo", bgcolor=ft.Colors.WHITE)
    page.padding = 0

    sidebar = ft.Container(
        width=220,
        bgcolor=ft.Colors.INDIGO_100,
        padding=20,
        content=ft.Text("サイドバー", weight=ft.FontWeight.BOLD),
    )

    page.add(
        ft.Row(
            [
                sidebar,
                ft.Container(
                    padding=20,
                    content=ft.Column(
                        [
                            ft.Text("売上ダッシュボード", size=20, weight=ft.FontWeight.BOLD),
                            ft.Container(height=12),
                            ft.Row(
                                [
                                    card("カード1: 今月の売上", ft.Colors.RED_100),
                                    card("カード2: 新規顧客数", ft.Colors.GREEN_100),
                                    card("カード3: 対応件数", ft.Colors.BLUE_100),
                                ],
                                spacing=16,
                            ),
                        ]
                    ),
                ),
            ],
        )
    )


ft.run(main, view=ft.AppView.WEB_BROWSER, port=8562)

ブラウザで開くと、3枚目のカードが画面の右側で切れています。

サイドバー+固定幅カードでレイアウトが崩れた画面
▲ サイドバー+カード3枚を並べただけのコード。3枚目のカードが画面からはみ出している

ここで真っ先に疑うのは「エラーメッセージ」だと思います。ところが、ターミナルには何も出力されず、ブラウザの開発者コンソールを開いて確認しても手がかりがありませんでした。

開発者コンソールでの検証結果
▲ 開発者コンソールでの検証結果を図解したもの。"overflow"・"RenderFlex"・"clip"のいずれのキーワードでも該当メッセージは0件だった

Flutter本来のネイティブアプリ開発では、この手のはみ出しは”A RenderFlex overflowed by ○○ pixels”という黄色と黒の縞模様の警告表示でおなじみですが、Fletのweb表示ではこの警告自体が出ません。つまりFletのレイアウト崩れは、コードのエラーを追いかける方法では気づけず、目視でしか発見できないタイプの不具合だと言えます。AIに丸投げしたコードほど、この「一見動いているように見える」落とし穴にはまりやすくなります。


スポンサーリンク

AIがよくやる崩し方

ここからは、AIが生成しがちなコードで実際に踏んだ、2つの典型的な崩れ方を紹介します。

expand忘れでコンテンツ側が余白を残したまま伸びない

サイドバー+メインコンテンツを横に並べる構成で、メインコンテンツ側にexpand=Trueを付け忘れると、コンテンツは中身の幅にしか広がらず、右側に不自然な余白が残ります。

expand未指定とexpand=Trueの比較
▲ 左: expand未指定でコンテンツが幅いっぱいに広がらない例 / 右: expand=Trueで残り幅いっぱいに広がる例

Good側の該当部分は次の通りです。

ft.Container(
    expand=True,
    bgcolor=ft.Colors.INDIGO_50,
    padding=14,
    border=ft.Border.all(1, ft.Colors.INDIGO_200),
    border_radius=8,
    content=ft.Text("メインコンテンツ\n(expand=True)", size=12),
)

expand=Trueを付けるだけの違いですが、これもエラーは出ません。見た目の違和感でしか気づけない点は、先ほどのはみ出しと同じです。

expand=Trueを深くネストすると、高さの基準がないまま要素ごと消える

Columnの中にexpand=TrueのRow、さらにその中にexpand=TrueのColumnとContainer……と入れ子にした状態で、一番外側の高さが確定していないと、内側のexpand要素は高さ0になって完全に消えます。

import flet as ft
from layout_common import setup_page


def main(page: ft.Page):
    setup_page(page, "Nest Test", bgcolor=ft.Colors.WHITE)
    page.padding = 20

    page.add(
        ft.Column(
            [
                ft.Text("深いネストの例", size=18, weight=ft.FontWeight.BOLD),
                ft.Row(
                    [
                        ft.Column(
                            [
                                ft.Text("左"),
                                ft.Container(expand=True, bgcolor=ft.Colors.RED_100),
                            ],
                            expand=True,
                        ),
                        ft.Column(
                            [
                                ft.Text("右"),
                                ft.Container(expand=True, bgcolor=ft.Colors.BLUE_100),
                            ],
                            expand=True,
                        ),
                    ],
                    expand=True,
                ),
            ],
        )
    )


ft.run(main, view=ft.AppView.WEB_BROWSER, port=8564)
深いネストで要素が消えた画面
▲ 「左」「右」のラベルだけが表示され、色付きのコンテナが高さ0になって消えている

直し方は、高さの基準になるコンテナに明示的なheightを与えることです。

ft.Container(
    height=380,  # 高さの基準を明示することで、内側のexpandが効くようになる
    content=ft.Row(
        [...],
        expand=True,
    ),
),
高さの基準を与えて修正した画面
▲ 外側のContainerにheight=380を与えると、内側の色付きエリアが正しく表示される

「ネストが深いと崩れやすい」というのは感覚的な注意点ではなく、こうして手元で再現できる具体的な不具合です。AIはexpand=Trueを機械的に足すことはできても、「どこかに高さの基準が必要」という設計上の前提までは考慮してくれないことが多いようです。


スポンサーリンク

直し方:ResponsiveRowとscrollで崩れにくい設計にする

固定のwidthで組むこと自体が崩れの根っこなので、ここでは「幅を決め打ちしない」2つの選択肢を紹介します。

ResponsiveRowで画面幅に応じて自動的に列数を変える

ft.ResponsiveRowは、各要素のcolプロパティに画面幅ごとの列数を指定しておくと、ブラウザの幅に応じて自動的に折り返してくれるコンポーネントです。

import flet as ft
from layout_common import setup_page

LABELS = [
    ("今月の売上", ft.Colors.RED_100),
    ("新規顧客数", ft.Colors.GREEN_100),
    ("対応件数", ft.Colors.BLUE_100),
    ("在庫アラート", ft.Colors.ORANGE_100),
    ("未読メッセージ", ft.Colors.PURPLE_100),
    ("完了タスク", ft.Colors.TEAL_100),
]


def card(label, color):
    return ft.Container(
        col={"xs": 12, "sm": 6, "md": 4},
        bgcolor=color,
        border_radius=10,
        padding=16,
        height=100,
        content=ft.Text(label, weight=ft.FontWeight.BOLD),
    )


def main(page: ft.Page):
    setup_page(page, "Responsive Demo", bgcolor=ft.Colors.WHITE)
    page.padding = 20
    page.add(
        ft.Text("業務ダッシュボード(ResponsiveRow)", size=18, weight=ft.FontWeight.BOLD),
        ft.Container(height=10),
        ft.ResponsiveRow([card(label, color) for label, color in LABELS], spacing=14, run_spacing=14),
    )


ft.run(main, view=ft.AppView.WEB_BROWSER, port=8566)

col={"xs": 12, "sm": 6, "md": 4}は「画面が最も狭い(xs)ときは12/12=1列、少し広い(sm)ときは6/12=2列、さらに広い(md)ときは4/12=3列」という意味です。ブラウザの幅だけを変えて撮ったのが次の3枚で、幅に応じて3列→2列→1列と並び替わっています。

幅広画面でのResponsiveRow(3列)
▲ 幅広の画面では3列
中間幅画面でのResponsiveRow(2列)
▲ やや狭い画面では2列
狭い画面でのResponsiveRow(1列)
▲ スマホ相当の幅では1列
画面幅を変えると列数が切り替わる様子
▲ 画面幅を変えながら列数が切り替わる様子

項目が多すぎるときはscroll=ft.ScrollMode.AUTOで横スクロールにする

画面に収めきれないほど項目が多い場合、無理に折り返さず、横スクロールに任せるという選択肢もあります。ft.Rowにscroll=ft.ScrollMode.AUTOを付けるだけです。

ft.Row(
    [card(f"項目{i+1}", COLORS[i]) for i in range(5)],
    spacing=14,
    scroll=ft.ScrollMode.AUTO,
)
横スクロールで対応した画面
▲ 画面に収まらない項目は横スクロールで見られる状態になる

崩れを隠すのではなく、「はみ出す設計」にしたい場合に使える方法です。


スポンサーリンク

AIへの丸投げで崩れたレイアウトを直す4ステップ

ここまでの内容を踏まえて、AIが生成したレイアウトの崩れを手元で直していく一連の流れを4ステップで見てみます。

STEP1/4 AIにサイドバー付きダッシュボードを丸投げする
▲ STEP1/4 AIに「サイドバー付きダッシュボード」を丸投げする(コードの解説図)
STEP2/4 実行すると3枚目のカードが画面から消える
▲ STEP2/4 実行すると、3枚目のカードが画面から消える
STEP3/4 原因は幅の決め打ち、expandに直す
▲ STEP3/4 原因は「幅の決め打ち」。expandに直す(コードの解説図)
STEP4/4 画面幅が変わっても崩れずに並び替わる
▲ STEP4/4 画面幅が変わっても、崩れずに並び替わる
丸投げから修正までの流れ
▲ 丸投げ→崩壊→原因特定→修正までの流れ

ポイントは、AIが出したwidth=300のような固定値そのものを疑うことです。widthを指定しているだけのコードは一見自然で、AIも人間も違和感を覚えにくいのですが、それこそが崩れの火種です。プロンプトを工夫する場合は、「幅は固定値ではなくexpandやResponsiveRowで組んで」と明記すると、この手の崩れを事前に防ぎやすくなります。


スポンサーリンク

実践:業務ダッシュボードをBefore/Afterで直す

最後に、ここまでの内容をまとめて、サイドバー付きの業務ダッシュボードをBefore/Afterで直してみます。

Beforeは、幅を固定値で決め打ちしたコードです(冒頭の崩壊デモと同じ構成)。

Before: サイドバー+固定幅カード
▲ Before: サイドバー+固定幅カード。3枚目が画面からはみ出す

Afterは、サイドバー以外の幅をexpand=Trueにし、カード部分をResponsiveRowに組み替えたものです。

import flet as ft
from layout_common import setup_page

CARDS = [
    ("今月の売上", ft.Colors.RED_100),
    ("新規顧客数", ft.Colors.GREEN_100),
    ("対応件数", ft.Colors.BLUE_100),
]


def card(label, color):
    return ft.Container(
        col={"xs": 12, "sm": 6, "md": 4},
        bgcolor=color,
        border_radius=10,
        padding=16,
        height=120,
        content=ft.Text(label, weight=ft.FontWeight.BOLD),
    )


def main(page: ft.Page):
    setup_page(page, "Dashboard Fixed", bgcolor=ft.Colors.WHITE)
    page.padding = 0

    sidebar = ft.Container(
        width=220,
        bgcolor=ft.Colors.INDIGO_100,
        padding=20,
        content=ft.Text("サイドバー", weight=ft.FontWeight.BOLD),
    )

    main_area = ft.Container(
        expand=True,  # 幅の決め打ちをやめて残り幅いっぱいに広げる
        padding=20,
        content=ft.Column(
            [
                ft.Text("売上ダッシュボード", size=20, weight=ft.FontWeight.BOLD),
                ft.Container(height=12),
                ft.ResponsiveRow(  # 固定Rowをやめて画面幅で列数が変わるResponsiveRowに
                    [card(label, color) for label, color in CARDS],
                    spacing=16,
                    run_spacing=16,
                ),
            ]
        ),
    )

    page.add(ft.Row([sidebar, main_area], spacing=0))


ft.run(main, view=ft.AppView.WEB_BROWSER, port=8568)
After(幅広画面)
▲ After(幅広画面): カードが3列で収まり、右側の余白もない
After(狭い画面)
▲ After(狭い画面): サイドバーは幅220pxの固定のまま、カード部分だけが自動で1列に切り替わる

サイドバーの幅だけは業務ツールらしく固定のままにし、それ以外を「決め打ちしない」設計に変えるだけで、画面幅を問わず崩れにくいダッシュボードになります。

スポンサーリンク

サンプルコードのダウンロード

この記事で紹介した9本のサンプルコード(Row/Column基本、overflowデモ、expand比較、ネスト崩壊/修正、ResponsiveRow、scroll、実践ダッシュボード、共通ヘルパー)は、まとめてダウンロードできます。

📦 サンプルコード一式をダウンロード(zip)

スポンサーリンク

よくある質問(FAQ)

QFletでレイアウトが崩れているのに気づく方法はありますか?

A現状ではエラーメッセージやコンソール警告に頼れないため、ブラウザで画面を開いて目視で確認するしかありません。特に固定のwidthを多用しているコードは、画面幅を変えて崩れないか一度試してみると安心です。

Qexpand=Trueはどこに付ければいいですか?

A「残りの幅(または高さ)いっぱいに広げたい要素」に付けます。Rowの中で幅を分け合いたい要素、Columnの中で高さを分け合いたい要素が対象です。ただし、外側に高さや幅の基準がまったくない状態でネストして使うと、要素が消えることがあるので注意してください。

QResponsiveRowのcolはどう決めればいいですか?

Acol={"xs": 12, "sm": 6, "md": 4}のように、12を基準にした分数で考えます。1行に何列並べたいかで割り算し、12 ÷ 列数を各ブレークポイントに当てはめると分かりやすいです(3列なら12÷3=4)。

Qwidthとexpandはどう使い分ければいいですか?

Aサイドバーやアイコンのように「常に同じ幅であってほしい」要素にはwidth、メインコンテンツのように「残りの幅を使い切ってほしい」要素にはexpand=Trueを使うのが基本の考え方です。

QAIにレイアウトのコードを書かせるとき、崩れを防ぐコツはありますか?

A「幅は固定値ではなくexpandやResponsiveRowで組んで」のように、決め打ちのwidthを避ける指示を加えるだけでも、崩れの一部は事前に防げます。ただし出力されたコードは必ずブラウザで目視確認してください。


スポンサーリンク

まとめ

この記事では、Fletのレイアウトがエラーなしで崩れる理由と、AIがよくやる崩し方のパターン、ResponsiveRowやscrollを使った直し方までを、動かした画面とあわせて紹介しました。

  • FletのレイアウトはRow(横並び)とColumn(縦並び)の組み合わせが基本
  • レイアウトが崩れても、Fletはエラーもコンソール警告も出さない。目視でしか気づけない
  • expand忘れ、高さの基準がないままの深いネストが、AIコードでよくある崩れ方
  • ResponsiveRowは画面幅に応じて自動的に列数を変えてくれる
  • 項目が多すぎる場合はscroll=ft.ScrollMode.AUTOで横スクロールにする選択肢もある
  • widthの決め打ちを避け、expand/ResponsiveRowで組むよう意識するだけで、崩れにくいレイアウトになる


ニンジン🥕アイコン

Follow me on X

本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕

@ninjin_py_vba をフォロー

この記事が役に立ったら、シェアしてもらえると嬉しいです🥕

Xでポスト はてブ LINE Pocket
まとめハブ
Python
スポンサーリンク

コメント