【備忘録】Fletでグリッドビューとリストビューの切り替えボタンの実装について

# １．はじめに

本記事はFletで提供されているUI

 * `GridView`
 * `ListView`

を交互に切り替える`Switch`を実装したのでその備忘録です。
（※表示したいリストの数が5000件ぐらいなどの場合は分割して表示、無限リロードのような実装を行う必要があります。単純に読み込みが遅くなるので。）

実行環境は

* Python : 3.13.1
* Flet : 0.25.2

# ２．コンポーネントの説明

```python
class My_View(Column):
    def __init__(self, view_items: list):
        super().__init__()
        self.view_items = view_items
        # 初期表示はグリッドビューとしています
        self.my_switch = Switch(
            label='Grid View',
            label_style=TextStyle(letter_spacing=0.5, size=16,),
            value=True,
            on_change=self.switch_changed,
            height=25,
            active_track_color=Colors.LIGHT_BLUE,
            inactive_track_color=Colors.DEEP_ORANGE,
            thumb_icon=ControlState.SELECTED,
        )
        self.my_grid = GridView(
            expand=True,
            max_extent=200,
            child_aspect_ratio=1.0,
        )
        self.my_list = ListView(
            expand=True,
            spacing=10,
            visible=False,
        )
        self.controls = [
            self.my_switch,
            self.my_grid,
            self.my_list,
        ]

    def before_update(self):
        # コンポーネントの更新が行われる前に、毎回以下のコードが実行される。
        # 無限リロードを実装する場合は、ビューのロードと表示の切り替えはそれぞれ別関数で実装する必要あり。
        self.my_grid.controls.clear()
        self.my_list.controls.clear()
        for item in self.view_items:
            self.__my_container = Container(
                content=Text(item, color=Colors.BLACK, no_wrap=False),
                alignment=alignment.center,
                bgcolor=Colors.AMBER_100,
                border=border.all(1, Colors.AMBER_400),
                border_radius=border_radius.all(10),
                padding=padding.all(10),
            )
            if self.my_switch.value:
                self.my_grid.visible = True
                self.my_list.visible = False
                self.my_grid.controls.append(self.__my_container)
            else:
                self.my_grid.visible = False
                self.my_list.visible = True
                self.my_list.controls.append(self.__my_container)

    def switch_changed(self, e):
        self.my_switch.label = 'Grid View' if e.control.value else 'List View'
        self.update()
```

上記実装のポイント：

* `GridView`、`ListView`自体はともに`controls`に追加している（アイテムが空の状態）
* 表示しないViewについては`visible=False`とする
* Viewに追加している`Container`は共通のものを使用しているが、別個で実装も可能
* `Switch`ボタンをクリックしたときのイベントは`value=True／False`と`label`の切り替え、Viewの更新
* `before_update`に記載されたコードは更新が走るたびに、実行される（現実装ではView内に追加されたアイテムは毎回クリアしてから、SwitchのオンオフでViewにアイテムを追加している。）

# ３．作成物

今回作成したコードをテストデータとともに表示させるコードが以下：

```python
import flet as ft
from flet import (
    alignment,
    border,
    border_radius,
    Column,
    Colors,
    Container,
    ControlState,
    GridView,
    ListView,
    padding,
    Page,
    ScrollMode,
    Switch,
    Text,
    TextStyle,
)

class My_View(Column):
    def __init__(self, view_items: list):
        super().__init__()
        self.view_items = view_items
        # 初期表示はグリッドビューとしています
        self.my_switch = Switch(
            label='Grid View',
            label_style=TextStyle(letter_spacing=0.5, size=16,),
            value=True,
            on_change=self.switch_changed,
            height=25,
            active_track_color=Colors.LIGHT_BLUE,
            inactive_track_color=Colors.DEEP_ORANGE,
            thumb_icon=ControlState.SELECTED,
        )
        self.my_grid = GridView(
            expand=True,
            max_extent=200,
            child_aspect_ratio=1.0,
        )
        self.my_list = ListView(
            expand=True,
            spacing=10,
            visible=False,
        )
        self.controls = [
            self.my_switch,
            self.my_grid,
            self.my_list,
        ]

    def before_update(self):
        # コンポーネントの更新が行われる前に、毎回以下のコードが実行される。
        # 無限リロードを実装する場合は、ビューのロードと表示の切り替えはそれぞれ別関数で実装する必要あり。
        self.my_grid.controls.clear()
        self.my_list.controls.clear()
        for item in self.view_items:
            self.__my_container = Container(
                content=Text(item, color=Colors.BLACK, no_wrap=False),
                alignment=alignment.center,
                bgcolor=Colors.AMBER_100,
                border=border.all(1, Colors.AMBER_400),
                border_radius=border_radius.all(10),
                padding=padding.all(10),
            )
            if self.my_switch.value:
                self.my_grid.visible = True
                self.my_list.visible = False
                self.my_grid.controls.append(self.__my_container)
            else:
                self.my_grid.visible = False
                self.my_list.visible = True
                self.my_list.controls.append(self.__my_container)

    def switch_changed(self, e):
        self.my_switch.label = 'Grid View' if e.control.value else 'List View'
        self.update()


def main(page: Page):
    # アプリケーション上でスクロールができるように設定
    page.scroll = ScrollMode.ADAPTIVE
    # テストデータ
    my_items = [f'item {i}' for i in range(101)]
    # コンポーネントをページに追加
    page.add(My_View(my_items))


ft.app(main)
```

上記コードを実行すると、以下のようなグリッド表示とリスト表示となります。

![スクリーンショット 2025-01-19 161957.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3275023/0336a879-ca03-ca5f-1cc6-0c0d54f26e92.png)

![スクリーンショット 2025-01-19 162020.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3275023/f49f547f-ddc7-e780-1e85-f6cfa1994404.png)


# ４．最後に

少々課題が残っていますが、よくあるグリッドからリスト（またはその逆）への表示切替を実装することができました。（件数が膨大な場合は200件ずつなどの塊ごとに表示させるなど工夫の余地あり。）