はじめに
前回の記事では、Fletの基本的なテキスト系UI要素(Text, TextField, TextButton)について紹介しました。今回は、ユーザーとのインタラクションを実現するUI要素に焦点を当てて解説します。
letは、Pythonだけで美しいクロスプラットフォームUIアプリケーションを構築できるフレームワークです。Flutterをバックエンドに使用しているため、Web、デスクトップ、モバイルで一貫したマテリアルデザインのUIを実現できます。
今回紹介するインタラクション系UI要素は以下の通りです:
- ボタン: ElevatedButton, OutlinedButton
- 選択コントロール: Checkbox, Radio
- 値の調整: Switch, Slider
- コントロールのカスタマイズ
環境構築
まだ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
RadioGroup
とRadio
を使用して、排他的な選択肢を作成できます。
# ラジオグループの基本的な使い方
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ではコントロールの状態を管理する際に、以下の点に注意する必要があります:
-
状態の変更後は
page.update()
を呼び出す- UIに変更を反映させるために必要です
- イベントハンドラ内で状態を変更した後に必ず呼び出します
-
コールバック関数の活用
-
on_click
,on_change
などのイベントで状態を更新します - コールバック関数の引数
e
からは操作されたコントロールにアクセスできます
-
-
ラムダ式の使用
- 簡単な処理は
lambda e: setattr(obj, 'prop', value) or page.update()
のように記述できます -
or page.update()
を追加することで、状態更新と画面更新を1行で書けます
- 簡単な処理は
発展的なUI構築のヒント
Fletでより高度なUIを作成するためのヒントをいくつか紹介します:
-
コンポーネント化
- 再利用可能なUIパーツはクラスや関数として実装すると管理しやすくなります
- 例:
def create_settings_panel(page): ...
-
レスポンシブデザイン
-
Row
のwrap=True
属性を使用すると、画面サイズに応じて要素が折り返されます -
ResponsiveRow
を使用すると、グリッドレイアウトが実現できます
-
-
アニメーション
-
animation_duration
属性でアニメーション時間を指定できます - ボタンやその他のコントロールの状態遷移をスムーズにします
-
-
ビジュアル統一
- 一貫したカラースキームとスタイルを使用しましょう
-
ThemeMode
を活用して、ライト/ダークモードの対応も簡単に行えます
まとめ
今回はFletのインタラクション系UI要素について解説しました。
- ボタン: ElevatedButton, OutlinedButtonは様々な用途に応じたスタイリングが可能
- 選択コントロール: CheckboxとRadioは排他的でない選択肢と排他的な選択肢を実現
- 値の調整: SwitchとSliderは異なる形式の値入力を可能に
- カスタマイズ: テーマ変更やホバー効果など、きめ細かな調整が可能
これらの要素を組み合わせることで、直感的で使いやすいインターフェースを構築できます。Fletの強みは、シンプルなPythonコードでクロスプラットフォームのUIを構築できる点にあります。
Flet 0.25.0以降の重要な変更点
Flet 0.25.0からいくつかの重要な変更があります:
-
ft.icons
→ft.Icons
に名前が変更されました -
ft.colors
→ft.Colors
に名前が変更されました
これらの変更は、将来的に(0.28.0で)完全に非推奨となり削除される予定です。最新のFletを使用する場合は、必ずこの新しい命名規則に従ってください。
免責事項
本記事の作成にあたり、文章や図解の生成にClaude Sonnetを、ファクトチェックにGensparkを活用しました。最終的な編集と確認は筆者が行っています。