2
2

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】Routeを使用して、画面切り替えを実装

Posted at

今回の記事は前回の記事を使用して、画面切り替え機能を実装したのでその備忘録となります。

実装概要としてはサイドバーのアイコンをクリックしたとき、メインボディの内容を切り替えるといった内容です。
※切り替える際にPage.routeの情報が必要なため、各コンポーネントにPageを渡しています。

本記事を作成するにあたり以下を参照しました:

1. 実装物の挙動

・本プログラムを実行すると以下の画面が表示されます:

スクリーンショット 2024-05-24 180521.png

・←ボタンを押下するとサイドバーが閉じます(→ボタンが表示されて、そのボタンを押すとサイドバーが再度表示されます。):

スクリーンショット 2024-05-24 180105.png

スクリーンショット 2024-05-24 180127.png

・Favoriteボタンを押下するとヘッダーのタイトルと本文が切り替わります:

スクリーンショット 2024-05-24 180521 - コピー (2).png

スクリーンショット 2024-05-24 180140.png

※ヘッダー右側のライトモードまたはダークモードを切り替えるボタンは実装済みですが、それ以外は表示のみです。

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.pysidebar.pyはRoute情報を取得するため、Pageを引数に追加。body.pyは本文のテキスト用に引数追加。

となっています。views.pyで画面切り替えを実装し、layout.pyでRouteに依存して表示するコンポーネントを変更するように修正しています。

3. 実装詳細

main.py
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)をインスタンス生成しています。

views.py
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.viewsMyLayout(page, route)を追加しているのでRouteに依存して画面を切り替えるようにできます。
view_popviewsのリストに対して以下のようにViewを追加または削除するように挙動します:

イメージ
views = [..., 'HOME', 'ABOUT'] -- POP --> [..., 'HOME'] -- views[-1] --> 'HOME'
layout.py
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について条件分岐をすることで画面の切り替えができるようにしています。(コンポーネントを条件分岐で切り替えることも可能です。)

body.py
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を追加しています。

header.py
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に関する制御が可能になります。(未実装ですが。)

sidebar.py
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アプリケーションぽくなった気がします。
次回は本文をリッチにしていくなどしていきたいと思います。

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?