[Flet入門] 基本レイアウト（コンテナとフレックス）の使い方

## はじめに

![flet-layout-diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/607a50e3-d9fe-4460-ba47-8cfc371a30d4.png)


前回の記事では、FletのインタラクティブなUI要素（ElevatedButton、Checkbox、Switchなど）について紹介しました。今回は、**これらの要素を効果的に配置するための基本レイアウト**に焦点を当てます。

Fletは、Pythonだけで美しいクロスプラットフォームUIアプリケーションを構築できるフレームワークです。UIを構築する際に重要となるのがレイアウトの考え方です。

今回紹介する基本レイアウト要素は以下の通りです：

1. **Container**: 単一の子要素を持つコンテナ
2. **Row, Column**: 複数の子要素を水平・垂直に配置
3. **フレックスの考え方**: 柔軟なレイアウト
4. **余白とアライメント**: 要素の位置調整

## 環境構築

まだFletをインストールしていない場合は、以下のコマンドでインストールしましょう。

```bash
pip install flet
```

## 1. Container - 基本的なコンテナ

`Container`は、単一の子要素をラップするための基本的なレイアウト要素です。パディング、マージン、背景色、ボーダーなどのスタイリングを適用できます。

### 基本的な使い方

```python
import flet as ft

def main(page: ft.Page):
    # 基本的なコンテナ
    basic_container = ft.Container(
        content=ft.Text("こんにちは、Flet!"),
        width=200,
        height=100,
        bgcolor=ft.Colors.BLUE_100,
        border_radius=10,
        padding=20,
        alignment=ft.alignment.center,
    )
    
    page.add(basic_container)

ft.app(target=main)
```

### コンテナの主要プロパティ

- **content**: コンテナ内に配置する子要素
- **width**, **height**: 幅と高さ
- **bgcolor**: 背景色
- **border_radius**: 角の丸み
- **border**: ボーダー設定
- **padding**: 内側の余白
- **margin**: 外側の余白
- **alignment**: 子要素の配置位置

## 2. Row, Column - 複数要素の配置

`Row`と`Column`は、複数の子要素を水平方向（Row）または垂直方向（Column）に配置するためのレイアウト要素です。

### Rowの基本的な使い方

```python
# 水平方向の配置
row = ft.Row(
    controls=[
        ft.Container(width=50, height=50, bgcolor=ft.Colors.RED),
        ft.Container(width=50, height=50, bgcolor=ft.Colors.GREEN),
        ft.Container(width=50, height=50, bgcolor=ft.Colors.BLUE),
    ],
    spacing=10,  # 要素間の間隔
)
```

### Columnの基本的な使い方

```python
# 垂直方向の配置
column = ft.Column(
    controls=[
        ft.Container(width=50, height=50, bgcolor=ft.Colors.RED),
        ft.Container(width=50, height=50, bgcolor=ft.Colors.GREEN),
        ft.Container(width=50, height=50, bgcolor=ft.Colors.BLUE),
    ],
    spacing=10,  # 要素間の間隔
)
```

## 3. フレックスの考え方

Fletのレイアウトシステムは、Flutterのフレックスボックスに基づいています。これにより、様々な画面サイズに対応する柔軟なレイアウトが可能になります。

### 主軸と交差軸

- **主軸 (Main Axis)**: 
  - Rowでは水平方向
  - Columnでは垂直方向

- **交差軸 (Cross Axis)**:
  - Rowでは垂直方向
  - Columnでは水平方向

### 主軸でのアライメント

Rowを例にした主軸でのアライメントオプションです：

```python
# MainAxisAlignment.START（デフォルト）
# 要素が主軸の始点から配置される
row_start = ft.Row(
    [ft.Container(width=50, height=50, bgcolor=ft.Colors.RED),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.GREEN),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.BLUE)],
    alignment=ft.MainAxisAlignment.START,
    width=400,
    bgcolor=ft.Colors.AMBER_100,
)

# MainAxisAlignment.CENTER
# 要素が主軸の中央に配置される
row_center = ft.Row(
    [ft.Container(width=50, height=50, bgcolor=ft.Colors.RED),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.GREEN),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.BLUE)],
    alignment=ft.MainAxisAlignment.CENTER,
    width=400,
    bgcolor=ft.Colors.AMBER_100,
)

# MainAxisAlignment.END
# 要素が主軸の終点に配置される
row_end = ft.Row(
    [ft.Container(width=50, height=50, bgcolor=ft.Colors.RED),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.GREEN),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.BLUE)],
    alignment=ft.MainAxisAlignment.END,
    width=400,
    bgcolor=ft.Colors.AMBER_100,
)

# MainAxisAlignment.SPACE_BETWEEN
# 最初と最後の要素は端に配置され、残りの要素は等間隔に配置される
row_space_between = ft.Row(
    [ft.Container(width=50, height=50, bgcolor=ft.Colors.RED),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.GREEN),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.BLUE)],
    alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
    width=400,
    bgcolor=ft.Colors.AMBER_100,
)
```

### 交差軸でのアライメント

```python
# CrossAxisAlignment.START
# 要素が交差軸の始点に配置される
row_cross_start = ft.Row(
    [ft.Container(width=50, height=30, bgcolor=ft.Colors.RED),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.GREEN),
     ft.Container(width=50, height=70, bgcolor=ft.Colors.BLUE)],
    vertical_alignment=ft.CrossAxisAlignment.START,
    height=100,
    bgcolor=ft.Colors.AMBER_100,
)

# CrossAxisAlignment.CENTER（デフォルト）
# 要素が交差軸の中央に配置される
row_cross_center = ft.Row(
    [ft.Container(width=50, height=30, bgcolor=ft.Colors.RED),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.GREEN),
     ft.Container(width=50, height=70, bgcolor=ft.Colors.BLUE)],
    vertical_alignment=ft.CrossAxisAlignment.CENTER,
    height=100,
    bgcolor=ft.Colors.AMBER_100,
)

# CrossAxisAlignment.END
# 要素が交差軸の終点に配置される
row_cross_end = ft.Row(
    [ft.Container(width=50, height=30, bgcolor=ft.Colors.RED),
     ft.Container(width=50, height=50, bgcolor=ft.Colors.GREEN),
     ft.Container(width=50, height=70, bgcolor=ft.Colors.BLUE)],
    vertical_alignment=ft.CrossAxisAlignment.END,
    height=100,
    bgcolor=ft.Colors.AMBER_100,
)
```

### 要素のフレックス比率

`expand`プロパティを使用すると、要素が利用可能なスペースをどれだけ占めるかを指定できます。

```python
# フレックス比率の例
flex_row = ft.Row([
    # 1の比率で拡大
    ft.Container(
        content=ft.Text("1"),
        bgcolor=ft.Colors.RED,
        alignment=ft.alignment.center,
        expand=1,
    ),
    # 2の比率で拡大（最初の要素の2倍のスペースを占める）
    ft.Container(
        content=ft.Text("2"),
        bgcolor=ft.Colors.GREEN,
        alignment=ft.alignment.center,
        expand=2,
    ),
    # 1の比率で拡大
    ft.Container(
        content=ft.Text("1"),
        bgcolor=ft.Colors.BLUE,
        alignment=ft.alignment.center,
        expand=1,
    ),
], height=50)
```

### 要素のラップ（折り返し）

`wrap`プロパティを`True`に設定すると、要素が収まらない場合に自動的に折り返されます。

```python
# 要素の折り返し
wrap_row = ft.Row(
    [
        ft.Container(width=100, height=50, bgcolor=ft.Colors.RED),
        ft.Container(width=100, height=50, bgcolor=ft.Colors.GREEN),
        ft.Container(width=100, height=50, bgcolor=ft.Colors.BLUE),
        ft.Container(width=100, height=50, bgcolor=ft.Colors.YELLOW),
        ft.Container(width=100, height=50, bgcolor=ft.Colors.ORANGE),
    ],
    wrap=True,  # 収まらない要素を折り返す
    spacing=10,
    run_spacing=10,  # 折り返し行の間隔
)
```

## 4. 余白とアライメント

### マージンとパディング

Fletでは、`margin`と`padding`を使って要素の周りや内側に余白を設定できます。

```python
# マージンとパディングの例
container = ft.Container(
    content=ft.Text("Hello, Flet!"),
    margin=20,  # 外側の余白
    padding=20,  # 内側の余白
    bgcolor=ft.Colors.AMBER_100,
)
```

### 特定の方向にのみ余白を設定

```python
# 上下にのみマージンを設定
custom_margin = ft.Container(
    content=ft.Text("上下にのみマージン"),
    margin=ft.margin.only(top=20, bottom=20),
    bgcolor=ft.Colors.GREEN_100,
    padding=10,
)

# 左右にのみパディングを設定
custom_padding = ft.Container(
    content=ft.Text("左右にのみパディング"),
    padding=ft.padding.only(left=20, right=20),
    bgcolor=ft.Colors.PURPLE_100,
)
```

### 要素の配置位置

`alignment`プロパティを使用して、コンテナ内での子要素の配置位置を指定できます。

```python
# 中央揃え
center_container = ft.Container(
    content=ft.Text("中央揃え"),
    alignment=ft.alignment.center,
    width=200,
    height=100,
    bgcolor=ft.Colors.BLUE_100,
)

# 左上揃え
top_left_container = ft.Container(
    content=ft.Text("左上揃え"),
    alignment=ft.alignment.top_left,
    width=200,
    height=100,
    bgcolor=ft.Colors.BLUE_100,
)

# 右下揃え
bottom_right_container = ft.Container(
    content=ft.Text("右下揃え"),
    alignment=ft.alignment.bottom_right,
    width=200,
    height=100,
    bgcolor=ft.Colors.BLUE_100,
)
```

## 実践的な例: レスポンシブカード

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/3bbda2f8-0af9-42b6-9830-4d8c07d65809.png)

以下の例では、これまで学んだレイアウト要素を組み合わせて、視認性の高いレスポンシブなカードUIを作成します。

```python
import flet as ft

def main(page: ft.Page):
    page.title = "レスポンシブカードUI"
    page.padding = 20
    page.bgcolor = ft.Colors.BLACK
    page.theme_mode = ft.ThemeMode.DARK
    
    # ウィンドウサイズ変更時のハンドラ
    def on_resize(e):
        # 画面幅に応じてカードの幅を調整
        if page.width < 600:
            # モバイル表示
            info_card.width = page.width - 40
            header_row.alignment = ft.MainAxisAlignment.CENTER
            actions_row.alignment = ft.MainAxisAlignment.CENTER
        else:
            # デスクトップ表示
            info_card.width = min(page.width - 40, 600)
            header_row.alignment = ft.MainAxisAlignment.START
            actions_row.alignment = ft.MainAxisAlignment.END
        
        page.update()
    
    # ページのリサイズイベントを監視
    page.on_resize = on_resize
    
    # ヘッダー行
    header_row = ft.Row([
        ft.Icon(ft.Icons.INFO, color=ft.Colors.WHITE, size=24),
        ft.Text("情報カード", color=ft.Colors.WHITE, weight="bold", size=18),
    ], alignment=ft.MainAxisAlignment.START)
    
    # 確認ボタン
    confirm_button = ft.ElevatedButton(
        "確認",
        style=ft.ButtonStyle(
            color={"": ft.Colors.WHITE},
            bgcolor={"": ft.Colors.BLUE, "hovered": ft.Colors.BLUE_700},
            elevation={"pressed": 0, "": 5},
            animation_duration=300,
        ),
    )
    
    actions_row = ft.Row(
        [confirm_button],
        alignment=ft.MainAxisAlignment.END,
    )
    
    # カードUI - コントラスト改善版
    info_card = ft.Container(
        content=ft.Column([
            # カードヘッダー - グラデーション背景
            ft.Container(
                content=header_row,
                gradient=ft.LinearGradient(
                    begin=ft.alignment.center_left,
                    end=ft.alignment.center_right,
                    colors=[ft.Colors.BLUE_700, ft.Colors.BLUE_900],
                ),
                padding=15,
                border_radius=ft.border_radius.only(top_left=10, top_right=10),
            ),
            
            # カードコンテンツ
            ft.Container(
                content=ft.Column([
                    ft.Text(
                        "Fletの基本レイアウト要素の例です",
                        size=16,
                        weight="bold",
                        color=ft.Colors.BLACK,  # 黒色でコントラスト向上
                    ),
                    ft.Container(height=15),  # スペーサー
                    
                    # リストアイテム - アイコン付き、文字色を濃く
                    ft.Row([
                        ft.Icon(ft.Icons.LAYERS, color=ft.Colors.BLUE_700, size=20),
                        ft.Text("Container: カード全体のコンテナ", 
                              color=ft.Colors.BLACK,
                              size=14,
                              weight="w500"),  # 文字列として指定
                    ], vertical_alignment=ft.CrossAxisAlignment.CENTER, spacing=10),
                    
                    ft.Row([
                        ft.Icon(ft.Icons.GRID_VIEW, color=ft.Colors.BLUE_700, size=20),
                        ft.Text("Row, Column: 要素の配置", 
                              color=ft.Colors.BLACK,
                              size=14,
                              weight="w500"),
                    ], vertical_alignment=ft.CrossAxisAlignment.CENTER, spacing=10),
                    
                    ft.Row([
                        ft.Icon(ft.Icons.SPACE_DASHBOARD, color=ft.Colors.BLUE_700, size=20),
                        ft.Text("マージンとパディング: 適切な余白", 
                              color=ft.Colors.BLACK,
                              size=14,
                              weight="w500"),
                    ], vertical_alignment=ft.CrossAxisAlignment.CENTER, spacing=10),
                    
                    ft.Row([
                        ft.Icon(ft.Icons.ASPECT_RATIO, color=ft.Colors.BLUE_700, size=20),
                        ft.Text("レスポンシブ: 画面サイズに適応", 
                              color=ft.Colors.BLACK,
                              size=14,
                              weight="w500"),
                    ], vertical_alignment=ft.CrossAxisAlignment.CENTER, spacing=10),
                    
                    ft.Container(height=20),  # スペーサー
                    
                    # アクションボタン
                    actions_row,
                ], spacing=15),  # 行間を広げる
                padding=20,
                bgcolor=ft.Colors.WHITE,
            ),
        ]),
        width=500,  # 初期幅（on_resizeでウィンドウサイズに応じて調整）
        border_radius=10,
        shadow=ft.BoxShadow(
            spread_radius=1,
            blur_radius=15,
            color=ft.Colors.BLACK38,
            offset=ft.Offset(0, 5),
        ),
    )
    
    # ページに追加
    page.add(info_card)
    
    # 初期サイズに合わせてレイアウトを調整
    on_resize(None)

ft.app(target=main)
```

この例では、以下のポイントに注目しています：

1. **レスポンシブ対応**：
   - `on_resize`関数で画面幅を検出
   - 小さい画面（600px未満）と大きい画面で異なるレイアウトを適用
   - カードの幅を画面サイズに合わせて調整

2. **視認性の向上**：
   - 文字色を濃くしてコントラストを高める
   - アイコンにも濃い色を使用
   - 行間を広げて読みやすさを向上

3. **魅力的なデザイン**：
   - ヘッダー部分にグラデーション背景
   - 立体感のある影効果
   - アイコンを使って視覚的な魅力を向上

4. **コントラストの確保**：
   - 背景色と文字色の組み合わせで高いコントラストを実現
   - フォントサイズとウェイトを適切に設定

## まとめ

![flet-layout-summary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/a5400337-8424-43d1-8286-c88165586249.png)

Fletの基本レイアウト要素について解説しました。

- **Container**: 単一の子要素をラップし、スタイリングを適用
- **Row, Column**: 複数の子要素を水平・垂直に配置
- **フレックスの考え方**: 主軸と交差軸でのアライメント、比率による拡大、折り返し
- **余白とアライメント**: マージン、パディング、配置位置の調整

これらの要素を組み合わせることで、柔軟で美しいUIレイアウトを作成できます。レスポンシブデザインの実装も、基本的なレイアウト要素の理解があれば難しくありません。視認性とコントラストを考慮したデザインは、すべてのユーザーにとって使いやすいUIを提供します。

## 免責事項
- 本記事の作成にあたり、文章や図解の生成に生成AIを活用しました。最終的な編集と確認は筆者が行っています。