[Flet入門] レイアウトの実装 GridView、Stack、Cardの活用法

## はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/25761ea5-e734-4de3-892b-eddf21b70ec8.png)

Fletは、Pythonだけでクロスプラットフォームのアプリケーションを構築できる強力なツールです。今回は、レイアウト要素に焦点を当て、特に視認性の問題を解決するテクニックをご紹介します：

## Fletのレイアウト要素一覧
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/248b929a-a6c3-4bd8-9915-cb884d33edea.png)

| レイアウト要素 | 核となる特徴 | 視認性向上のポイント |
|--------------|------------|------------------|
| **GridView** | 格子状に要素を整然と配置する | • 適切な間隔（spacing）の設定<br>• 背景色と文字色のコントラスト |
| **Stack** | 複数要素を同じ空間に重ねて表示 | • 半透明オーバーレイ（#66000000）<br>• テキスト背景の追加（#99000000） |
| **Card** | 影とボーダーで浮き上がった表現 | • 適切なパディング<br>• 高さとコンテンツの自動調整 |
| **組み合わせ** | 階層構造による複合的な表現 | • 下部のみ暗くするグラデーション<br>• テキスト要素の視覚的分離 |

Fletを使えば、純粋なPythonコードだけでこれらの高度なレイアウトを実現できます。フロントエンド技術の知識がなくても、美しいアプリケーションが構築できるのが魅力です。

## サンプルコードの実行例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/2bc8ffe0-ab3a-4cbc-807c-8eef41470f6f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/2770f0d9-e40d-491e-af6c-af7d84ef77c8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/bece5115-0106-4277-9489-d616f6f31487.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/8269ff0c-e6e7-4d5d-be23-af39ad1d4f74.png)


## 環境構築

```bash
pip install flet
```

**バージョンの注意点**:
- バージョン0.25.0以降: `ft.colors` → `ft.Colors`, `ft.icons` → `ft.Icons` 
- 各バージョンによって利用できるプロパティやAPIが異なる場合があります
- サンプルコードはflet 0.25.0以降を想定しています

## 1. GridView（グリッドレイアウト）

GridViewは項目を格子状に配置するレイアウトです。写真ギャラリーやダッシュボードに最適です。

### 基本的な使い方

```python
ft.GridView(
    runs_count=4,           # 行または列の数
    max_extent=150,         # 各アイテムの最大幅
    child_aspect_ratio=1.0, # 子要素のアスペクト比（幅/高さ）
    spacing=15,             # 水平方向の間隔
    run_spacing=15,         # 垂直方向の間隔
    controls=[              # グリッドに配置する要素のリスト
        ft.Container(
            content=ft.Text(
                f"項目 {i}",
                color=ft.Colors.BLUE_900,  # テキスト色を濃い青に
                weight=ft.FontWeight.BOLD,  # 太字に
                size=16,  # サイズを大きめに
            ),
            alignment=ft.alignment.center,
            bgcolor=ft.Colors.BLUE_100,
            border_radius=10,
            height=100,  # 高さを固定
        ) for i in range(1, 9)
    ],
)
```

### GridViewの主なプロパティ

| プロパティ名 | 説明 |
|------------|------|
| **runs_count** | 行数（水平スクロール時）または列数（垂直スクロール時） |
| **max_extent** | 各アイテムの最大幅/高さ |
| **spacing** | グリッドアイテム間の主軸方向の間隔 |
| **run_spacing** | グリッドアイテム間の交差軸方向の間隔 |
| **child_aspect_ratio** | 子要素のアスペクト比（幅/高さ） |
| **padding** | グリッド全体のパディング |
| **expand** | 利用可能なスペースを埋めるかどうか（スクロールに影響） |

### 適応型グリッドの例

ウィンドウサイズに応じて列数が自動調整されるグリッドの例です：

```python
ft.GridView(
    runs_count=3,               # 列数を指定
    max_extent=200,             # 各アイテムの最大幅
    child_aspect_ratio=1.5,     # 子要素のアスペクト比（幅/高さ）
    spacing=15,
    run_spacing=15,
    controls=[
        ft.Container(
            content=ft.Column([
                ft.Icon(name=ft.Icons.FAVORITE, color=ft.Colors.RED),
                ft.Text(
                    f"カード {i}", 
                    color=ft.Colors.BROWN_900,  # 暗い茶色テキスト
                    weight=ft.FontWeight.BOLD,
                    size=16,
                ),
            ], alignment=ft.MainAxisAlignment.CENTER),
            alignment=ft.alignment.center,
            bgcolor=ft.Colors.AMBER_100,
            border_radius=10,
            border=ft.border.all(1, ft.Colors.AMBER_400),
        ) for i in range(1, 7)
    ],
)
```

## 2. Stack（重ね合わせレイアウト）

Stackは複数の要素を同じ空間に重ね合わせるレイアウトです。テキストを画像の上に配置したり、複雑なUIを構築したりするのに便利です。

### 基本的な使い方

```python
ft.Stack([
    # 背景
    ft.Container(
        bgcolor=ft.Colors.BLUE_100,
        border_radius=10,
        width=300,
        height=200,
    ),
    # 中央のテキスト
    ft.Container(
        content=ft.Text(
            "中央のテキスト", 
            size=16,
            color=ft.Colors.BLUE_900,
            weight=ft.FontWeight.BOLD,
        ),
        alignment=ft.alignment.center,
        # テキスト背景を追加して視認性向上
        bgcolor="#DDFFFFFF",  # 半透明の白
        padding=8,
        border_radius=4,
    ),
    # 右下のアイコン
    ft.Container(
        content=ft.Icon(ft.Icons.STAR, color=ft.Colors.AMBER),
        alignment=ft.alignment.bottom_right,
        padding=10,
    ),
])
```

### 画像とテキストを重ねる例（視認性向上版）

よくあるヒーローセクションのようなUIを作成する際、テキストの視認性が課題になります。以下は視認性を向上させた実装例です：

```python
ft.Stack([
    # 背景画像
    ft.Image(
        src="https://picsum.photos/350/220?random=3",
        width=350,
        height=220,
        fit=ft.ImageFit.COVER,
        border_radius=ft.border_radius.all(10),
    ),
    # 半透明のオーバーレイ（適度な透明度で画像も見えるように）
    ft.Container(
        bgcolor="#66000000",  # ARGB形式：66は透明度40%
        border_radius=10,
        width=350,
        height=220,
    ),
    # テキスト (視認性向上のため背景と境界線付き)
    ft.Container(
        content=ft.Text(
            "画像上のテキスト",
            color=ft.Colors.WHITE,
            size=22,
            weight=ft.FontWeight.BOLD,
        ),
        padding=15,
        bgcolor="#99000000",  # ARGB形式：99は透明度60%
        border=ft.border.all(2, ft.Colors.WHITE),  # 白い境界線を追加
        border_radius=5,
        alignment=ft.alignment.center,
    ),
])
```

### 視認性向上のための工夫

| テクニック | 詳細 | Fletでの実装方法 |
|----------|------|----------------|
| **半透明オーバーレイ** | 画像全体に薄い暗いオーバーレイを適用（40%程度） | `bgcolor="#66000000"` |
| **テキスト背景** | テキスト自体にも半透明の背景を適用（60%程度） | `bgcolor="#99000000"` |
| **境界線** | 白い境界線でテキストコンテナを囲む | `border=ft.border.all(2, ft.Colors.WHITE)` |
| **適切なサイズ** | テキストサイズを十分に大きくする（20-22px程度） | `size=22, weight=ft.FontWeight.BOLD` |
| **パディング** | テキスト周りに十分な余白を確保 | `padding=15` |

## 3. Card（カードUI）

Cardはマテリアルデザインのカードコンポーネントで、関連するコンテンツをグループ化します。影やボーダーが付いた浮き上がったUIを簡単に作成できます。

### 基本的なカード

```python
ft.Card(
    content=ft.Container(
        content=ft.Column([
            ft.Text("カードタイトル", weight=ft.FontWeight.BOLD),
            ft.Text("カードの説明文をここに入れます。"),
        ]),
        padding=15,
    ),
    width=300,
)
```

### 画像付きカード

```python
ft.Card(
    content=ft.Column([
        ft.Image(
            src="https://picsum.photos/300/150?random=4",
            width=300,
            height=150,
            fit=ft.ImageFit.COVER,
        ),
        ft.Container(
            content=ft.Column([
                ft.Text("画像付きカード", weight=ft.FontWeight.BOLD, size=16),
                ft.Text("美しい画像とテキストを組み合わせたカードです。"),
                ft.Row([
                    ft.TextButton("詳細"),
                    ft.TextButton("共有"),
                ], alignment=ft.MainAxisAlignment.END),
            ]),
            padding=15,
        ),
    ]),
    width=300,
    elevation=5,  # 影の強さ
)
```

### Cardの主なプロパティ

| プロパティ名 | 説明 |
|------------|------|
| **content** | カードの内容 |
| **elevation** | カードの影の強さ |
| **color** | カードの背景色 |
| **margin** | カードの外側の余白 |
| **surface_tint_color** | カードの表面の色合い |

## 4. レイアウトの組み合わせテクニック

これらのレイアウト要素を組み合わせることで、さらに洗練されたUIを作成できます。

### GridView + Card + Stack の組み合わせ（視認性向上版）

以下は、GridViewの各セルにCardを配置し、さらにその中にStackを使って画像とテキストを重ねる高度な例です。テキストの視認性が向上するように実装しています：

```python
ft.GridView(
    runs_count=2,  # 2列のグリッド
    max_extent=250,  # 各アイテムの幅を大きめに
    child_aspect_ratio=0.8,
    spacing=20,  # 間隔を広めに
    run_spacing=20,
    controls=[
        ft.Card(
            content=ft.Container(
                content=ft.Stack([
                    # 背景画像
                    ft.Image(
                        src=f"https://picsum.photos/250/300?random={i}",
                        width=250,
                        height=300,
                        fit=ft.ImageFit.COVER,
                    ),
                    # 上半分は透明、下半分だけ暗く
                    ft.Container(
                        bgcolor="#00000000",  # 完全透明
                        width=250,
                        height=150,
                    ),
                    ft.Container(
                        bgcolor="#99000000",  # 60%不透明
                        width=250,
                        height=150,
                        margin=ft.margin.only(top=150),  # 下半分に配置
                    ),
                    # テキスト要素
                    ft.Container(
                        content=ft.Column([
                            # タイトル用コンテナ
                            ft.Container(  
                                content=ft.Text(
                                    f"カード {i}",
                                    color=ft.Colors.WHITE,
                                    size=18,
                                    weight=ft.FontWeight.BOLD,
                                ),
                                bgcolor="#BF4336",  # 赤系の背景色（75%不透明）
                                padding=8,
                                border_radius=4,
                            ),
                            # 説明文用コンテナ
                            ft.Container(  
                                content=ft.Text(
                                    "サンプルテキスト",
                                    color=ft.Colors.WHITE,
                                    size=16,
                                ),
                                bgcolor="#BF0288D1",  # 青系の背景色（75%不透明）
                                padding=8,
                                border_radius=4,
                                margin=ft.margin.only(top=8),
                            ),
                            # アクションボタン
                            ft.Container(
                                content=ft.ElevatedButton(
                                    "詳細を見る",
                                    style=ft.ButtonStyle(
                                        color=ft.Colors.WHITE,
                                        bgcolor="#4CAF50",  # 緑系の背景色
                                    ),
                                ),
                                margin=ft.margin.only(top=15),
                            ),
                        ]),
                        padding=15,
                        alignment=ft.alignment.bottom_left,
                    ),
                ]),
                width=250,
                height=300,
            ),
            elevation=5,
        ) for i in range(1, 5)
    ],
    expand=False,  # 高さを固定しない（スクロールのため）
)
```

### レイアウト組み合わせでの視認性向上ポイント


| テクニック | 詳細 | 効果 |
|----------|------|------|
| **グラデーション効果の代替** | 下半分だけ暗くし、上部は透明に設定 | 上部は画像がはっきり見え、下部はテキストが読みやすくなる |
| **コントラストの高い背景色** | テキスト背景に目立つ色を使用し、適度な透明度を設定 | 背景画像との調和を保ちながらテキストを強調できる |
| **境界とパディング** | 要素間に適切な余白を設け、テキストコンテナに角丸を適用 | 視覚的に要素を分離し、読みやすさが向上する |
| **サイズの最適化** | グリッドアイテム、テキスト、ボタンのサイズを十分に大きく設定 | 視認性が高まり、タッチ操作も容易になる |

## サンプルコード

以下のコードで、この記事で紹介したすべてのレイアウト要素を1つのアプリケーションで試すことができます。特に視認性を重視した実装になっています：

```python
import flet as ft

def main(page: ft.Page):
    page.title = "Flet 高度なレイアウト（視認性向上版）"
    page.bgcolor = ft.Colors.BLACK
    page.padding = 20
    
    # ページ全体をスクロール可能に設定
    page.scroll = "auto"
    
    # ----- 1. GridView -----
    page.add(
        ft.Text("1. GridView (グリッドレイアウト)", size=20, weight=ft.FontWeight.BOLD, color=ft.Colors.WHITE),
        ft.Text("固定数グリッド:", color=ft.Colors.WHITE),
        
        ft.GridView(
            runs_count=4,
            max_extent=150,
            child_aspect_ratio=1.0,
            spacing=15,
            run_spacing=15,
            controls=[
                ft.Container(
                    content=ft.Text(
                        f"項目 {i}",
                        color=ft.Colors.BLUE_900,
                        weight=ft.FontWeight.BOLD,
                        size=16,
                    ),
                    alignment=ft.alignment.center,
                    bgcolor=ft.Colors.BLUE_100,
                    border_radius=10,
                    height=100,
                ) for i in range(1, 9)
            ],
            expand=False,
        ),
        
        ft.Container(padding=25),
        ft.Text("適応型グリッド:", color=ft.Colors.WHITE),
        
        ft.GridView(
            runs_count=3,
            max_extent=200,
            child_aspect_ratio=1.5,
            spacing=15,
            run_spacing=15,
            controls=[
                ft.Container(
                    content=ft.Column([
                        ft.Icon(name=ft.Icons.FAVORITE, color=ft.Colors.RED),
                        ft.Text(
                            f"カード {i}", 
                            color=ft.Colors.BROWN_900,
                            weight=ft.FontWeight.BOLD,
                            size=16,
                        ),
                    ], alignment=ft.MainAxisAlignment.CENTER),
                    alignment=ft.alignment.center,
                    bgcolor=ft.Colors.AMBER_100,
                    border_radius=10,
                    border=ft.border.all(1, ft.Colors.AMBER_400),
                ) for i in range(1, 7)
            ],
            expand=False,
        ),
    )
    
    # ----- 2. Stack -----
    page.add(
        ft.Container(padding=30),
        ft.Text("2. Stack (重ね合わせレイアウト)", size=20, weight=ft.FontWeight.BOLD, color=ft.Colors.WHITE),
        ft.Text("基本的なStack:", color=ft.Colors.WHITE),
        
        ft.Container(
            width=300,
            height=200,
            content=ft.Stack([
                # 背景
                ft.Container(
                    bgcolor=ft.Colors.BLUE_100,
                    border_radius=10,
                    width=300,
                    height=200,
                ),
                # 中央のテキスト
                ft.Container(
                    content=ft.Text(
                        "中央のテキスト", 
                        size=16,
                        color=ft.Colors.BLUE_900,
                        weight=ft.FontWeight.BOLD,
                    ),
                    alignment=ft.alignment.center,
                    bgcolor="#DDFFFFFF",
                    padding=8,
                    border_radius=4,
                ),
                # 右下のアイコン
                ft.Container(
                    content=ft.Icon(ft.Icons.STAR, color=ft.Colors.AMBER),
                    alignment=ft.alignment.bottom_right,
                    padding=10,
                ),
            ]),
        ),
        
        ft.Container(padding=15),
        ft.Text("画像と重ねるStack (改善版):", color=ft.Colors.WHITE),
        
        ft.Container(
            width=350,
            height=220,
            content=ft.Stack([
                # 背景画像
                ft.Image(
                    src="https://picsum.photos/350/220?random=3",
                    width=350,
                    height=220,
                    fit=ft.ImageFit.COVER,
                    border_radius=ft.border_radius.all(10),
                ),
                # 半透明のオーバーレイ
                ft.Container(
                    bgcolor="#66000000",
                    border_radius=10,
                    width=350,
                    height=220,
                ),
                # テキスト
                ft.Container(
                    content=ft.Text(
                        "画像上のテキスト",
                        color=ft.Colors.WHITE,
                        size=22,
                        weight=ft.FontWeight.BOLD,
                    ),
                    padding=15,
                    bgcolor="#99000000",
                    border=ft.border.all(2, ft.Colors.WHITE),
                    border_radius=5,
                    alignment=ft.alignment.center,
                ),
            ]),
        ),
    )
    
    # ----- 3. Card -----
    page.add(
        ft.Container(padding=30),
        ft.Text("3. Card (カードUI)", size=20, weight=ft.FontWeight.BOLD, color=ft.Colors.WHITE),
        ft.Text("基本的なCard:", color=ft.Colors.WHITE),
        
        ft.Card(
            content=ft.Container(
                content=ft.Column([
                    ft.Text("カードタイトル", weight=ft.FontWeight.BOLD),
                    ft.Text("カードの説明文をここに入れます。"),
                ]),
                padding=15,
            ),
            width=300,
        ),
        
        ft.Container(padding=15),
        ft.Text("画像付きCard:", color=ft.Colors.WHITE),
        
        ft.Card(
            content=ft.Column([
                ft.Image(
                    src="https://picsum.photos/300/150?random=4",
                    width=300,
                    height=150,
                    fit=ft.ImageFit.COVER,
                ),
                ft.Container(
                    content=ft.Column([
                        ft.Text("画像付きカード", weight=ft.FontWeight.BOLD, size=16),
                        ft.Text("美しい画像とテキストを組み合わせたカードです。"),
                        ft.Row([
                            ft.TextButton("詳細"),
                            ft.TextButton("共有"),
                        ], alignment=ft.MainAxisAlignment.END),
                    ]),
                    padding=15,
                ),
            ]),
            width=300,
            elevation=5,
        ),
    )
    
    # ----- 4. レイアウトの組み合わせ -----
    page.add(
        ft.Container(padding=30),
        ft.Text("4. レイアウトの組み合わせテクニック (視認性向上版)", size=20, weight=ft.FontWeight.BOLD, color=ft.Colors.WHITE),
        ft.Text("GridView + Card + Stack:", color=ft.Colors.WHITE),
        
        ft.Container(
            content=ft.GridView(
                runs_count=2,
                max_extent=250,
                child_aspect_ratio=0.8,
                spacing=20,
                run_spacing=20,
                controls=[
                    ft.Card(
                        content=ft.Container(
                            content=ft.Stack([
                                # 背景画像
                                ft.Image(
                                    src=f"https://picsum.photos/250/300?random={i}",
                                    width=250,
                                    height=300,
                                    fit=ft.ImageFit.COVER,
                                ),
                                # 下半分だけ暗くする
                                ft.Container(
                                    bgcolor="#00000000",
                                    width=250,
                                    height=150,
                                ),
                                ft.Container(
                                    bgcolor="#99000000",
                                    width=250,
                                    height=150,
                                    margin=ft.margin.only(top=150),
                                ),
                                # テキスト
                                ft.Container(
                                    content=ft.Column([
                                        # タイトル用コンテナ
                                        ft.Container(  
                                            content=ft.Text(
                                                f"カード {i}",
                                                color=ft.Colors.WHITE,
                                                size=18,
                                                weight=ft.FontWeight.BOLD,
                                            ),
                                            bgcolor="#BF4336",
                                            padding=8,
                                            border_radius=4,
                                        ),
                                        # 説明文用コンテナ
                                        ft.Container(  
                                            content=ft.Text(
                                                "サンプルテキスト",
                                                color=ft.Colors.WHITE,
                                                size=16,
                                            ),
                                            bgcolor="#BF0288D1",
                                            padding=8,
                                            border_radius=4,
                                            margin=ft.margin.only(top=8),
                                        ),
                                        # アクションボタン
                                        ft.Container(
                                            content=ft.ElevatedButton(
                                                "詳細を見る",
                                                style=ft.ButtonStyle(
                                                    color=ft.Colors.WHITE,
                                                    bgcolor="#4CAF50",
                                                ),
                                            ),
                                            margin=ft.margin.only(top=15),
                                        ),
                                    ]),
                                    padding=15,
                                    alignment=ft.alignment.bottom_left,
                                ),
                            ]),
                            padding=0,
                            width=250,
                            height=300,
                        ),
                        elevation=5,
                    ) for i in range(1, 5)
                ],
                expand=False,
            ),
        ),
    )

ft.app(target=main)
```

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/430e4b66-e54e-446a-a9e1-4fbe9226850e.png)

Fletを使用すれば、Pythonだけで視認性の高い美しいUIを持つアプリケーションを構築できます。本記事で紹介したGridView、Stack、Cardの活用方法と視認性向上テクニックを習得することで、プロフェッショナルな見た目のアプリケーションが簡単に作成できるようになります。

特に画像上のテキスト表示など、視認性が問題になりやすい部分でも、適切なオーバーレイやコントラストの高い背景色、境界線などを活用することで、読みやすいUIが実現できます。

ぜひこれらのテクニックを活用して、あなたのFletアプリケーションをさらに魅力的なものにしてください！

## 免責事項
本記事の作成にあたり、文章や図解の生成にClaude Sonnet, Napkin AIを、ファクトチェックにGensparkを活用しました。最終的な編集と確認は筆者が行っています。