今回の記事は前回の記事を使用して、画面切り替え機能を実装したのでその備忘録となります。
実装概要としてはサイドバーのアイコンをクリックしたとき、メインボディの内容を切り替えるといった内容です。
※切り替える際にPage.route
の情報が必要なため、各コンポーネントにPage
を渡しています。
本記事を作成するにあたり以下を参照しました:
1. 実装物の挙動
・本プログラムを実行すると以下の画面が表示されます:
・←ボタンを押下するとサイドバーが閉じます(→ボタンが表示されて、そのボタンを押すとサイドバーが再度表示されます。):
↓
・Favoriteボタンを押下するとヘッダーのタイトルと本文が切り替わります:
↓
※ヘッダー右側のライトモードまたはダークモードを切り替えるボタンは実装済みですが、それ以外は表示のみです。
2. コード概要
画面切り替えを行うためView
コンポーネントを使用しました。ファイル構造は以下のようになります(Route用にviews.py
を作成しています。):
root.
| .gitignore
| layout.py
| main.py
| README.md
| requirements.txt
| views.py
|
+---components
| | body.py
| | header.py
| | sidebar.py
新規作成したviews.py
はRoute変更時の挙動を管理するために実装しており、詳しくはこちらを参照ください。
以前の記事からの変更点としては
-
main.py
:layout.MyLayout
からviews.MyView
に変更。 -
views.py
: 新規作成(Route及び画面管理)。 -
layout.py
: コンポーネントView
を追加し、Routeを定義。 - 他コンポーネント :
header.py
とsidebar.py
はRoute情報を取得するため、Page
を引数に追加。body.py
は本文のテキスト用に引数追加。
となっています。views.py
で画面切り替えを実装し、layout.py
でRouteに依存して表示するコンポーネントを変更するように修正しています。
3. 実装詳細
from views import MyView
from flet import (
app,
Page,
)
def main(page: Page):
page.title = "Example Route"
page.padding = 10
MyView(page)
if __name__ == '__main__':
app(target=main)
views.MyView
をインポートし、MyView(page)
をインスタンス生成しています。
from layout import MyLayout
from flet import (
Row,
Page,
)
class MyView(Row):
def __init__(self, page: Page):
super().__init__()
self.page = page
self.page.on_route_change = self.route_change
self.page.on_view_pop = self.view_pop
self.page.go(self.page.route)
def route_change(self, route):
self.page.views.clear()
self.page.views.append(MyLayout(self.page, self.page.route))
self.page.update()
def view_pop(self, view):
self.page.views.pop()
top_view = self.page.views[-1]
self.page.go(top_view.route)
page
に対して、Route変更、Routeが変更されたときのイベントおよびviewを切り替えるイベントを実装しています。
route_change
の中で、Page.views
にMyLayout(page, route)
を追加しているのでRouteに依存して画面を切り替えるようにできます。
view_pop
はviews
のリストに対して以下のようにViewを追加または削除するように挙動します:
views = [..., 'HOME', 'ABOUT'] -- POP --> [..., 'HOME'] -- views[-1] --> 'HOME'
from components.body import ContentBody
from components.header import AppHeader
from components.sidebar import Sidebar
from flet import (
CrossAxisAlignment,
MainAxisAlignment,
Row,
Page,
View,
)
class MyLayout(View):
def __init__(self, page: Page, route='/'):
super().__init__()
self.page = page
self.route = route
if self.route=='/':
self.page_title = 'Home'
elif self.route=='/about':
self.page_title = 'About'
elif self.route=='/contact':
self.page_title = 'Contact'
self.controls = [
AppHeader(self.page, self.page_title.upper()),
Row(
alignment=MainAxisAlignment.START,
vertical_alignment=CrossAxisAlignment.START,
controls=[Sidebar(self.page),ContentBody(self.page_title.upper()+' Page')],
),
]
route
について条件分岐をすることで画面の切り替えができるようにしています。(コンポーネントを条件分岐で切り替えることも可能です。)
from flet import (
Column,
Text,
)
class ContentBody(Column):
def __init__(self, text:str='Body Text'):
super().__init__()
self.text = text
self.spacing = 10
self.controls = [
Text('**********************'),
Text(self.text),
Text('**********************'),
]
だし分け用にtext
を追加しています。
from flet import (
AppBar,
colors,
Container,
ElevatedButton,
Icon,
IconButton,
icons,
MainAxisAlignment,
margin,
PopupMenuButton,
PopupMenuItem,
Row,
Text,
Page,
)
class AppHeader(AppBar):
def __init__(self, page: Page, page_title: str="Example"):
super().__init__()
self.page = page
self.page_title = page_title
self.toggle_dark_light_icon = IconButton(
icon=icons.LIGHT_MODE_OUTLINED,
selected_icon =icons.DARK_MODE_OUTLINED,
tooltip=f"switch light and dark mode",
on_click=self.toggle_icon,
)
self.appbar_items = [
PopupMenuItem(text="Login"),
PopupMenuItem(),
PopupMenuItem(text="SignUp"),
PopupMenuItem(),
PopupMenuItem(text="Settings"),
]
self.leading=Icon(icons.TRIP_ORIGIN_ROUNDED)
self.leading_width=100
self.title=Text(value=self.page_title, size=32, text_align="center")
self.center_title=False
self.toolbar_height=75
self.bgcolor=colors.SURFACE_VARIANT
self.actions=[
Container(
margin=margin.only(left=50, right=25),
content=Row(
alignment=MainAxisAlignment.SPACE_BETWEEN,
controls=[
self.toggle_dark_light_icon,
ElevatedButton(text="SOMETHING"),
PopupMenuButton(
items=self.appbar_items
),
],
),
)
]
def toggle_icon(self, e):
self.page.theme_mode = "light" if self.page.theme_mode == "dark" else "dark"
self.toggle_dark_light_icon.selected = not self.toggle_dark_light_icon.selected
self.page.update()
page.Route
を追加したのでRouteに関する制御が可能になります。(未実装ですが。)
from flet import (
alignment,
border_radius,
colors,
Container,
CrossAxisAlignment,
FloatingActionButton,
Icon,
IconButton,
icons,
NavigationRail,
NavigationRailDestination,
NavigationRailLabelType,
Row,
Text,
Page,
)
class Sidebar(Container):
def __init__(self, page:Page):
super().__init__()
self.page = page
self.nav_rail_visible = True
self.nav_rail_items = [
NavigationRailDestination(
icon=icons.FAVORITE_BORDER,
selected_icon=icons.FAVORITE,
label="Favorite"
),
NavigationRailDestination(
icon_content=Icon(icons.BOOKMARK_BORDER),
selected_icon_content=Icon(icons.BOOKMARK),
label="Bookmark"
),
NavigationRailDestination(
icon=icons.SETTINGS_OUTLINED,
selected_icon_content=Icon(icons.SETTINGS),
label_content=Text("Settings"),
),
]
self.nav_rail = NavigationRail(
height= 300,
selected_index=None,
label_type=NavigationRailLabelType.ALL,
min_width=100,
min_extended_width=400,
leading=FloatingActionButton(icon=icons.CREATE, text="ADD"),
group_alignment=-0.9,
destinations=self.nav_rail_items,
on_change=self.tap_nav_icon,
)
self.toggle_nav_rail_button = IconButton(
icon=icons.ARROW_CIRCLE_LEFT,
icon_color=colors.BLUE_GREY_400,
selected=False,
selected_icon=icons.ARROW_CIRCLE_RIGHT,
on_click=self.toggle_nav_rail,
tooltip="Collapse Nav Bar",
)
self.visible = self.nav_rail_visible
self.content = Row(
controls=[
self.nav_rail,
Container(
bgcolor=colors.BLACK26,
border_radius=border_radius.all(30),
height=480,
alignment=alignment.center_right,
width=2
),
self.toggle_nav_rail_button,
],
vertical_alignment=CrossAxisAlignment.START,
)
def toggle_nav_rail(self, e):
self.nav_rail.visible = not self.nav_rail.visible
self.toggle_nav_rail_button.selected = not self.toggle_nav_rail_button.selected
self.toggle_nav_rail_button.tooltip = "Open Side Bar" if self.toggle_nav_rail_button.selected else "Collapse Side Bar"
self.update()
def tap_nav_icon(self, e):
if e.control.selected_index == 0:
self.page.go('/about')
elif e.control.selected_index == 1:
self.page.go('/contact')
else:
self.page.go('/')
tap_nav_icon
を実装し、画面切り替えを行えるようにしています。(Page
を追加したおかげですね。)
4. まとめ
今回は画面レイアウトを作成後のステップとしてルーティングを実装しました。
前回のコンポーネントの単純な拡張ですが、だいぶWEBアプリケーションぽくなった気がします。
次回は本文をリッチにしていくなどしていきたいと思います。