0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Flet入門] ElevatedButton・Checkbox・SwitchなどでインタラクティブなUI要素を作る方法

Last updated at Posted at 2025-04-21

はじめに

前回の記事では、Fletの基本的なテキスト系UI要素(Text, TextField, TextButton)について紹介しました。今回は、ユーザーとのインタラクションを実現するUI要素に焦点を当てて解説します。
letは、Pythonだけで美しいクロスプラットフォームUIアプリケーションを構築できるフレームワークです。Flutterをバックエンドに使用しているため、Web、デスクトップ、モバイルで一貫したマテリアルデザインのUIを実現できます。

flet-ui-diagram.png

今回紹介するインタラクション系UI要素は以下の通りです:

  1. ボタン: ElevatedButton, OutlinedButton
  2. 選択コントロール: Checkbox, Radio
  3. 値の調整: Switch, Slider
  4. コントロールのカスタマイズ

image.png

image.png

image.png

環境構築

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

pip install flet

実装例

まずは全体のコードを見てみましょう。このコードには今回紹介するすべてのインタラクション系UI要素が含まれています。

import flet as ft

def main(page: ft.Page):
    # ページの設定
    page.title = "Flet インタラクション系UI要素のサンプル"
    page.theme_mode = ft.ThemeMode.LIGHT
    page.padding = 20
    page.scroll = "auto"
    
    # 結果表示用のテキスト
    result_text = ft.Text("ここに操作結果が表示されます", size=18)
    
    # 1. ボタン (ElevatedButton, OutlinedButton)
    def on_button_click(e):
        result_text.value = f"クリックされたボタン: {e.control.text}"
        page.update()
    
    # ElevatedButtonのサンプル
    elevated_button = ft.ElevatedButton(
        "ElevatedButton",
        icon=ft.Icons.PLAY_ARROW,
        on_click=on_button_click,
    )
    
    # スタイル付きElevatedButton
    styled_elevated_button = ft.ElevatedButton(
        "スタイル付きボタン",
        icon=ft.Icons.THUMB_UP,
        on_click=on_button_click,
        style=ft.ButtonStyle(
            color=ft.Colors.WHITE,
            bgcolor=ft.Colors.BLUE,
            padding=15,
            overlay_color=ft.Colors.BLUE_ACCENT,
            elevation=5,
            shape=ft.RoundedRectangleBorder(radius=10),
        ),
    )
    
    # OutlinedButtonのサンプル
    outlined_button = ft.OutlinedButton(
        "OutlinedButton", 
        icon=ft.Icons.BOOKMARK_BORDER,
        on_click=on_button_click,
    )
    
    # 長押し対応のボタン
    long_press_button = ft.ElevatedButton(
        "長押し対応ボタン",
        on_click=on_button_click,
        on_long_press=lambda e: setattr(result_text, 'value', "ボタンが長押しされました!") or page.update(),
    )
    
    # ボタン無効化
    disabled_button = ft.ElevatedButton("無効化ボタン", disabled=True)
    
    buttons_section = ft.Container(
        content=ft.Column(
            [
                ft.Text("1. ボタン (ElevatedButton, OutlinedButton)", size=20, weight=ft.FontWeight.BOLD),
                ft.Row([elevated_button, styled_elevated_button, outlined_button, long_press_button, disabled_button], spacing=10, wrap=True),
            ],
            spacing=20,
        ),
        padding=20,
        bgcolor=ft.Colors.BLUE_50,
        border_radius=10,
        margin=ft.margin.only(bottom=20),
    )
    
    # 2. チェックボックスとラジオボタン (Checkbox, Radio)
    def on_checkbox_change(e):
        result_text.value = f"チェックボックス '{e.control.label}'{'オン' if e.control.value else 'オフ'} です"
        page.update()
    
    # 基本的なチェックボックス
    basic_checkbox = ft.Checkbox(label="基本的なチェックボックス", on_change=on_checkbox_change)
    
    # 初期値がオンのチェックボックス
    checked_checkbox = ft.Checkbox(
        label="初期値がオンのチェックボックス", 
        value=True,
        on_change=on_checkbox_change
    )
    
    # 無効化されたチェックボックス
    disabled_checkbox = ft.Checkbox(label="無効化されたチェックボックス", disabled=True)
    
    # チェックボックスのラベル位置変更
    label_position_checkbox = ft.Checkbox(
        label="ラベルが左側のチェックボックス", 
        label_position=ft.LabelPosition.LEFT,
        on_change=on_checkbox_change
    )
    
    # ラジオグループの設定
    def on_radio_change(e):
        result_text.value = f"選択されたラジオボタン: {radio_group.value}"
        page.update()
    
    radio_group = ft.RadioGroup(
        value="option2",
        content=ft.Column([
            ft.Radio(value="option1", label="ラジオオプション 1"),
            ft.Radio(value="option2", label="ラジオオプション 2"),
            ft.Radio(value="option3", label="ラジオオプション 3", disabled=True),
        ]),
        on_change=on_radio_change,
    )
    
    # 水平方向のラジオグループ
    horizontal_radio_group = ft.RadioGroup(
        content=ft.Row([
            ft.Radio(value="horizontal1", label="横並び 1"),
            ft.Radio(value="horizontal2", label="横並び 2"),
            ft.Radio(value="horizontal3", label="横並び 3"),
        ]),
        on_change=lambda e: setattr(result_text, 'value', f"水平ラジオ選択: {horizontal_radio_group.value}") or page.update(),
    )
    
    # Textウィジェットにmarginを直接指定できないため、Containerでラップする
    radio_title = ft.Container(
        content=ft.Text("ラジオボタン:", weight=ft.FontWeight.BOLD),
        margin=ft.margin.only(top=20)
    )
    
    horizontal_radio_title = ft.Container(
        content=ft.Text("水平方向のラジオグループ:"),
        margin=ft.margin.only(top=10)
    )
    
    checkbox_radio_section = ft.Container(
        content=ft.Column(
            [
                ft.Text("2. チェックボックスとラジオボタン (Checkbox, Radio)", size=20, weight=ft.FontWeight.BOLD),
                ft.Text("チェックボックス:", weight=ft.FontWeight.BOLD),
                ft.Column([basic_checkbox, checked_checkbox, disabled_checkbox, label_position_checkbox], spacing=10),
                radio_title,
                ft.Text("垂直方向のラジオグループ:"),
                radio_group,
                horizontal_radio_title,
                horizontal_radio_group,
            ],
            spacing=10,
        ),
        padding=20,
        bgcolor=ft.Colors.GREEN_50,
        border_radius=10,
        margin=ft.margin.only(bottom=20),
    )
    
    # 3. スイッチとスライダー (Switch, Slider)
    def on_switch_change(e):
        result_text.value = f"スイッチは {'オン' if e.control.value else 'オフ'} です"
        page.update()
    
    # 基本的なスイッチ
    basic_switch = ft.Switch(label="基本的なスイッチ", on_change=on_switch_change)
    
    # 初期値がオンのスイッチ
    on_switch = ft.Switch(
        label="初期値がオンのスイッチ", 
        value=True,
        on_change=on_switch_change
    )
    
    # カスタムラベル位置のスイッチ
    custom_label_switch = ft.Row(
        [
            ft.Text("カスタムラベル位置: "),
            ft.Switch(on_change=on_switch_change),
        ],
        alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
    )
    
    # スライダーの設定
    def on_slider_change(e):
        result_text.value = f"スライダーの値: {int(e.control.value)}"
        slider_value_text.value = str(int(e.control.value))
        page.update()
    
    slider_value_text = ft.Text("50")
    
    # 基本的なスライダー
    basic_slider = ft.Slider(
        min=0,
        max=100,
        value=50,
        divisions=10,
        on_change=on_slider_change,
    )
    
    # ラベル付きスライダー
    labeled_slider = ft.Slider(
        min=0,
        max=100,
        value=50,
        divisions=10,
        label="{value}%",
        on_change=on_slider_change,
    )
    
    # カスタムスライダー
    custom_slider = ft.Row(
        [
            ft.Text("0"),
            ft.Slider(
                width=300,
                min=0,
                max=100,
                value=50,
                on_change=on_slider_change,
            ),
            ft.Text("100"),
            ft.Text("値:", weight=ft.FontWeight.BOLD),
            slider_value_text,
        ],
    )
    
    # Textウィジェットにmarginを直接指定できないため、Containerでラップする
    slider_title = ft.Container(
        content=ft.Text("スライダー:", weight=ft.FontWeight.BOLD),
        margin=ft.margin.only(top=20)
    )
    
    switch_slider_section = ft.Container(
        content=ft.Column(
            [
                ft.Text("3. スイッチとスライダー (Switch, Slider)", size=20, weight=ft.FontWeight.BOLD),
                ft.Text("スイッチ:", weight=ft.FontWeight.BOLD),
                ft.Column([basic_switch, on_switch, custom_label_switch], spacing=10),
                slider_title,
                ft.Column([
                    ft.Text("基本的なスライダー:"),
                    basic_slider,
                    ft.Text("ラベル付きスライダー:"),
                    labeled_slider,
                    ft.Text("カスタムスライダー:"),
                    custom_slider,
                ], spacing=10),
            ],
            spacing=10,
        ),
        padding=20,
        bgcolor=ft.Colors.PURPLE_50,
        border_radius=10,
        margin=ft.margin.only(bottom=20),
    )
    
    # 4. コントロールのカスタマイズ
    # コントロール有効/無効のトグル
    enabled_state = True
    custom_button = ft.ElevatedButton("カスタムボタン", icon=ft.Icons.STAR)
    custom_checkbox = ft.Checkbox(label="カスタムチェックボックス", value=True)
    custom_slider = ft.Slider(min=0, max=100, value=30)
    
    def toggle_enabled(e):
        nonlocal enabled_state
        enabled_state = not enabled_state
        
        # コントロールの有効/無効状態を切り替え
        custom_button.disabled = not enabled_state
        custom_checkbox.disabled = not enabled_state
        custom_slider.disabled = not enabled_state
        
        # ボタンテキストの更新
        toggle_button.text = "コントロールを無効化" if enabled_state else "コントロールを有効化"
        
        result_text.value = f"コントロールは {'有効' if enabled_state else '無効'} になりました"
        page.update()
    
    toggle_button = ft.ElevatedButton("コントロールを無効化", on_click=toggle_enabled)
    
    # テーマカラーを変更するボタン
    def change_theme_color(e):
        if page.theme_mode == ft.ThemeMode.LIGHT:
            page.theme_mode = ft.ThemeMode.DARK
            theme_button.text = "ライトモードに変更"
            theme_button.icon = ft.Icons.LIGHT_MODE
        else:
            page.theme_mode = ft.ThemeMode.LIGHT
            theme_button.text = "ダークモードに変更"
            theme_button.icon = ft.Icons.DARK_MODE
        
        result_text.value = f"テーマを {page.theme_mode.name} モードに変更しました"
        page.update()
    
    theme_button = ft.ElevatedButton(
        "ダークモードに変更",
        icon=ft.Icons.DARK_MODE,
        on_click=change_theme_color,
        style=ft.ButtonStyle(
            bgcolor={"hovered": ft.Colors.INDIGO_200},
            shape={"hovered": ft.RoundedRectangleBorder(radius=20)},
        ),
    )
    
    # スタイルのホバー効果を持つカスタムボタン
    hover_effect_button = ft.ElevatedButton(
        "ホバーでスタイル変更",
        style=ft.ButtonStyle(
            color={"": ft.Colors.BLACK, "hovered": ft.Colors.WHITE},
            bgcolor={"": ft.Colors.AMBER_100, "hovered": ft.Colors.ORANGE},
            padding=10,
            animation_duration=300,
            shape=ft.RoundedRectangleBorder(radius=10),
        ),
        on_click=lambda e: setattr(result_text, 'value', "ホバー効果付きボタンがクリックされました") or page.update(),
    )
    
    # Textウィジェットにmarginを直接指定できないため、Containerでラップする
    theme_title = ft.Container(
        content=ft.Text("テーマの変更:", weight=ft.FontWeight.BOLD),
        margin=ft.margin.only(top=20)
    )
    
    hover_title = ft.Container(
        content=ft.Text("ホバー効果:", weight=ft.FontWeight.BOLD),
        margin=ft.margin.only(top=20)
    )
    
    customize_section = ft.Container(
        content=ft.Column(
            [
                ft.Text("4. コントロールのカスタマイズ", size=20, weight=ft.FontWeight.BOLD),
                ft.Text("コントロールの有効/無効化:", weight=ft.FontWeight.BOLD),
                ft.Row([toggle_button], spacing=10),
                ft.Row([custom_button, custom_checkbox], spacing=10),
                custom_slider,
                theme_title,
                ft.Row([theme_button], spacing=10),
                hover_title,
                ft.Row([hover_effect_button], spacing=10),
            ],
            spacing=10,
        ),
        padding=20,
        bgcolor=ft.Colors.AMBER_50,
        border_radius=10,
        margin=ft.margin.only(bottom=20),
    )
    
    # ページ全体のコンテンツ
    page_content = ft.Column([
        ft.Text("Fletのインタラクション系UI要素", size=30, weight=ft.FontWeight.BOLD),
        result_text,
        buttons_section,
        checkbox_radio_section,
        switch_slider_section,
        customize_section,
    ])
    
    page.add(page_content)


ft.app(target=main)

各UI要素の詳細説明

1. ボタン (ElevatedButton, OutlinedButton)

前回紹介したTextButtonの他にも、Fletには様々なスタイルのボタンがあります。

ElevatedButton

ElevatedButtonは影付きの浮き上がったスタイルのボタンです。

# 基本的な使い方
elevated_button = ft.ElevatedButton(
    "ElevatedButton",  # ボタンテキスト
    icon=ft.Icons.PLAY_ARROW,  # アイコン (更新: icons → Icons)
    on_click=on_button_click,  # クリック時のコールバック
)

# スタイル付きElevatedButton
styled_elevated_button = ft.ElevatedButton(
    "スタイル付きボタン",
    icon=ft.Icons.THUMB_UP,  # 更新: icons → Icons
    on_click=on_button_click,
    style=ft.ButtonStyle(
        color=ft.Colors.WHITE,  # テキスト色 (更新: colors → Colors)
        bgcolor=ft.Colors.BLUE,  # 背景色 (更新: colors → Colors)
        padding=15,  # パディング
        overlay_color=ft.Colors.BLUE_ACCENT,  # ホバー時の色 (更新: colors → Colors)
        elevation=5,  # 影の強さ
        shape=ft.RoundedRectangleBorder(radius=10),  # 形状
    ),
)

OutlinedButton

OutlinedButtonは枠線のみのボタンです。

# 基本的な使い方
outlined_button = ft.OutlinedButton(
    "OutlinedButton", 
    icon=ft.Icons.BOOKMARK_BORDER,  # 更新: icons → Icons
    on_click=on_button_click,
)

ボタンのイベント処理

ボタンには様々なイベントを設定できます。

# クリック時のイベント
on_click=lambda e: print("ボタンがクリックされました")

# 長押し時のイベント
on_long_press=lambda e: print("ボタンが長押しされました")

# 無効化されたボタン
disabled_button = ft.ElevatedButton("無効化ボタン", disabled=True)

2. チェックボックスとラジオボタン (Checkbox, Radio)

Checkbox

Checkboxは、オン/オフを切り替えるコントロールです。

# 基本的な使い方
basic_checkbox = ft.Checkbox(
    label="基本的なチェックボックス",  # ラベル
    on_change=on_checkbox_change  # 変更時のコールバック
)

# 初期値がオンのチェックボックス
checked_checkbox = ft.Checkbox(
    label="初期値がオンのチェックボックス", 
    value=True,  # 初期値
    on_change=on_checkbox_change
)

# ラベル位置の変更
label_position_checkbox = ft.Checkbox(
    label="ラベルが左側のチェックボックス", 
    label_position=ft.LabelPosition.LEFT,
    on_change=on_checkbox_change
)

RadioGroup と Radio

RadioGroupRadioを使用して、排他的な選択肢を作成できます。

# ラジオグループの基本的な使い方
radio_group = ft.RadioGroup(
    value="option2",  # 初期選択値
    content=ft.Column([  # 縦方向に配置
        ft.Radio(value="option1", label="ラジオオプション 1"),
        ft.Radio(value="option2", label="ラジオオプション 2"),
        ft.Radio(value="option3", label="ラジオオプション 3", disabled=True),
    ]),
    on_change=on_radio_change,  # 変更時のコールバック
)

# 水平方向のラジオグループ
horizontal_radio_group = ft.RadioGroup(
    content=ft.Row([  # 横方向に配置
        ft.Radio(value="horizontal1", label="横並び 1"),
        ft.Radio(value="horizontal2", label="横並び 2"),
        ft.Radio(value="horizontal3", label="横並び 3"),
    ]),
    on_change=on_radio_change,
)

3. スイッチとスライダー (Switch, Slider)

Switch

Switchは、オン/オフを切り替えるトグルコントロールです。

# 基本的な使い方
basic_switch = ft.Switch(
    label="基本的なスイッチ",  # ラベル
    on_change=on_switch_change  # 変更時のコールバック
)

# 初期値がオンのスイッチ
on_switch = ft.Switch(
    label="初期値がオンのスイッチ", 
    value=True,  # 初期値
    on_change=on_switch_change
)

# カスタムラベル位置
# Rowを使って自由にレイアウト
custom_label_switch = ft.Row(
    [
        ft.Text("カスタムラベル位置: "),
        ft.Switch(on_change=on_switch_change),
    ],
    alignment=ft.MainAxisAlignment.SPACE_BETWEEN,  # 両端揃え
)

Slider

Sliderは、範囲内の値を選択するコントロールです。

# 基本的な使い方
basic_slider = ft.Slider(
    min=0,  # 最小値
    max=100,  # 最大値
    value=50,  # 初期値
    divisions=10,  # 目盛りの数
    on_change=on_slider_change,  # 変更時のコールバック
)

# ラベル付きスライダー
labeled_slider = ft.Slider(
    min=0,
    max=100,
    value=50,
    divisions=10,
    label="{value}%",  # スライダー操作時に表示されるラベル
    on_change=on_slider_change,
)

# カスタムスライダー
# 最小値、最大値、現在値を表示
custom_slider = ft.Row(
    [
        ft.Text("0"),  # 最小値表示
        ft.Slider(
            width=300,
            min=0,
            max=100,
            value=50,
            on_change=on_slider_change,
        ),
        ft.Text("100"),  # 最大値表示
        ft.Text("値:", weight=ft.FontWeight.BOLD),
        slider_value_text,  # 現在値表示
    ],
)

4. コントロールのカスタマイズ

コントロールの有効/無効の切り替え

全てのコントロールはdisabledプロパティを変更することで有効/無効を切り替えることができます。

# ボタンクリックで複数のコントロールの有効/無効を切り替える
def toggle_enabled(e):
    nonlocal enabled_state
    enabled_state = not enabled_state
    
    # コントロールの有効/無効状態を切り替え
    custom_button.disabled = not enabled_state
    custom_checkbox.disabled = not enabled_state
    custom_slider.disabled = not enabled_state
    
    # ボタンテキストの更新
    toggle_button.text = "コントロールを無効化" if enabled_state else "コントロールを有効化"
    
    result_text.value = f"コントロールは {'有効' if enabled_state else '無効'} になりました"
    page.update()

テーマの変更

Fletではアプリケーション全体のテーマを変更することができます。

def change_theme_color(e):
    if page.theme_mode == ft.ThemeMode.LIGHT:
        page.theme_mode = ft.ThemeMode.DARK
        theme_button.text = "ライトモードに変更"
        theme_button.icon = ft.Icons.LIGHT_MODE  # 更新: icons → Icons
    else:
        page.theme_mode = ft.ThemeMode.LIGHT
        theme_button.text = "ダークモードに変更"
        theme_button.icon = ft.Icons.DARK_MODE  # 更新: icons → Icons
    
    result_text.value = f"テーマを {page.theme_mode.name} モードに変更しました"
    page.update()

ホバー効果の追加

ButtonStyleを使用して、ホバー時の効果を設定できます。

hover_effect_button = ft.ElevatedButton(
    "ホバーでスタイル変更",
    style=ft.ButtonStyle(
        color={"": ft.Colors.BLACK, "hovered": ft.Colors.WHITE},  # 通常時と、ホバー時の色 (更新: colors → Colors)
        bgcolor={"": ft.Colors.AMBER_100, "hovered": ft.Colors.ORANGE},  # 通常時と、ホバー時の背景色 (更新: colors → Colors)
        padding=10,
        animation_duration=300,  # アニメーション時間(ミリ秒)
        shape=ft.RoundedRectangleBorder(radius=10),
    ),
    on_click=lambda e: setattr(result_text, 'value', "ホバー効果付きボタンがクリックされました") or page.update(),
)

Fletの状態管理

Fletではコントロールの状態を管理する際に、以下の点に注意する必要があります:

  1. 状態の変更後はpage.update()を呼び出す

    • UIに変更を反映させるために必要です
    • イベントハンドラ内で状態を変更した後に必ず呼び出します
  2. コールバック関数の活用

    • on_click, on_changeなどのイベントで状態を更新します
    • コールバック関数の引数eからは操作されたコントロールにアクセスできます
  3. ラムダ式の使用

    • 簡単な処理はlambda e: setattr(obj, 'prop', value) or page.update()のように記述できます
    • or page.update()を追加することで、状態更新と画面更新を1行で書けます

発展的なUI構築のヒント

Fletでより高度なUIを作成するためのヒントをいくつか紹介します:

  1. コンポーネント化

    • 再利用可能なUIパーツはクラスや関数として実装すると管理しやすくなります
    • 例: def create_settings_panel(page): ...
  2. レスポンシブデザイン

    • Rowwrap=True属性を使用すると、画面サイズに応じて要素が折り返されます
    • ResponsiveRowを使用すると、グリッドレイアウトが実現できます
  3. アニメーション

    • animation_duration属性でアニメーション時間を指定できます
    • ボタンやその他のコントロールの状態遷移をスムーズにします
  4. ビジュアル統一

    • 一貫したカラースキームとスタイルを使用しましょう
    • ThemeModeを活用して、ライト/ダークモードの対応も簡単に行えます

まとめ

image.png

今回はFletのインタラクション系UI要素について解説しました。

  • ボタン: ElevatedButton, OutlinedButtonは様々な用途に応じたスタイリングが可能
  • 選択コントロール: CheckboxとRadioは排他的でない選択肢と排他的な選択肢を実現
  • 値の調整: SwitchとSliderは異なる形式の値入力を可能に
  • カスタマイズ: テーマ変更やホバー効果など、きめ細かな調整が可能

これらの要素を組み合わせることで、直感的で使いやすいインターフェースを構築できます。Fletの強みは、シンプルなPythonコードでクロスプラットフォームのUIを構築できる点にあります。

Flet 0.25.0以降の重要な変更点

Flet 0.25.0からいくつかの重要な変更があります:

  1. ft.iconsft.Icons に名前が変更されました
  2. ft.colorsft.Colors に名前が変更されました

これらの変更は、将来的に(0.28.0で)完全に非推奨となり削除される予定です。最新のFletを使用する場合は、必ずこの新しい命名規則に従ってください。

免責事項

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?