LoginSignup
9
5

More than 1 year has passed since last update.

Fletを試す(3) - 画面遷移(ルーティング)の調査

Posted at

はじめに

PythonでFlutterアプリが作れるフレームワーク「Flet」の検証をしました。
簡単なツールは1画面で済むときもありますが、ある程度のアプリを作るとなると2画面以上になると思います。
そうなると画面遷移が必要になるので、画面遷移(ルーティング)の実装方法を調査しました。

Flet公式サイトにおけるルーティングの説明はこちら。
https://flet.dev/docs/guides/python/navigation-and-routing

参考)過去記事はこちら。

前提事項

  • 「Flet」に関する詳細は省略
  • インストール方法、実行方法、デプロイ方法なども省略
  • 試した環境
    • Windows10
    • Python 3.11.1
    • Flet 0.3.2

概要

  • Fletの画面遷移(ルーティング)の仕様を確認
  • 実装としては2パターンあると思ったので、2パターンのサンプルを作成
  • パターン1:画面間を単純に移動する方式
  • パターン2:画面に階層を持たせて進んだり戻ったりする方式(上に画面を被せて行くイメージ)

今回のソースの最終形をFly.ioにアップしたのがこちら。
https://flet-report03.fly.dev

Animation.gif

詳細

Fletの画面遷移(ルーティング)の仕様

image.png

↑が公式サイトの図ですが、画面表示の仕組みとしてはまさにこの図の通りです。
まずPageがあって、その中にViewが複数入っているイメージ。
Viewは最初がRootで、Viewを追加すると上に被さっていくイメージで、一番上のViewが見えている状態になります。
スタックのイメージだと思うのですが、popで一番上のViewが外れてその下の画面が表示されます。

パターン1:画面間を単純に移動する方式を実装

1つ目は、敢えてシンプルに1つのViewを表示するだけで画面を切り替える方法を実装しました。

サンプルでは、view1とview2を作成する関数を用意し、ボタンクリックでviewを切り替えるようにしてあります。
view切替は、page.views.clear()でクリアしてpage.views.append()でviewを追加するだけ。
これにより、元の画面が消えて新たな画面が表示されます。

流れとしては以下です。
page.go()を呼ぶ→page.on_route_changeに設定した関数が実行される→その中で画面切り替えする

動作イメージは↓。
Animation.gif

サンプルソースは↓。

import flet as ft


def main(page: ft.Page):

    def create_view1():
        return ft.View("/view1", [
            ft.AppBar(title=ft.Text("view1"),
                      bgcolor=ft.colors.BLUE),
            ft.TextField(value="view1"),
            ft.ElevatedButton(
                "Go to view2", on_click=lambda _: page.go("/view2")),
        ])

    def create_view2():
        return ft.View("/view2", [
            ft.AppBar(title=ft.Text("view2"),
                      bgcolor=ft.colors.RED),
            ft.TextField(value="view2"),
            ft.ElevatedButton(
                "Go to view1", on_click=lambda _: page.go("/view1")),
        ])

    def route_change(handler):
        troute = ft.TemplateRoute(handler.route)
        page.views.clear()
        if troute.match("/view1"):
            page.views.append(create_view1())
        elif troute.match("/view2"):
            page.views.append(create_view2())
        page.update()

    # ルート変更時のロジック設定
    page.on_route_change = route_change

    # Page レイアウト
    page.title = "Navigation and routing"
    # 初期表示
    page.go("/view1")


if __name__ == "__main__":
    ft.app(target=main)

パターン2:画面に階層を持たせて進んだり戻ったりする方式(上に画面を被せて行くイメージ)

2つ目は、Fletの仕様にあるViewを上に被せていくイメージの画面遷移を実装をしました。

こちらはpage.views.clear()しないでpage.views.append()することで、新しいviewが表示されます。
画面にAppBarを配置しておくと自動的に左端に戻るのアイコンが出てきます。
戻るクリック時の処理はpage.on_view_popに関数を割り当てて書いておく必要があります。

戻るときの流れは以下。
戻るクリック→page.on_view_popに設定した関数が実行される→その中でpage.views.pop()する

動作イメージは↓。
Animation.gif

サンプルソースは↓

import flet as ft


def main(page: ft.Page):

    def create_view1():
        ・・・上と同じなので省略・・・

    def create_view2():
        ・・・上と同じなので省略・・・

    def route_change(handler):
        troute = ft.TemplateRoute(handler.route)
        if troute.match("/view1"):
            page.views.append(create_view1())
        elif troute.match("/view2"):
            page.views.append(create_view2())
        page.update()

    # ルート変更時のロジック設定
    page.on_route_change = route_change

    def view_pop(handler):
        page.views.pop()  # 1つ前に戻る
        page.go("/back")
        # page.update()
        # update() だと route が変更されない。
        # そうなると1つ戻ってまた進むことができなくなるので go("/back") で回避。不具合?

    # 戻る時のロジック設定
    page.on_view_pop = view_pop

    # Page レイアウト
    page.title = "Navigation and routing"
    # 初期表示
    page.views.clear()
    page.go("/view1")


if __name__ == "__main__":
    ft.app(target=main)

メモ
page.go("/back") については、コメント記載の通り、暫定的な対処です。
もっと良い方法があるかもしれませんし、もしかしたら書き方に問題があるだけかもしれません。

ソースの最終形

2パターンを一緒にまとめた形にしたソースがこちら。
動作イメージは概要に貼ってある通りです。

サンプルソースはこちら。
import flet as ft


def main(page: ft.Page):

    def create_view(route: str, title: str, next_route1: str, next_route2: str, color: ft.colors):
        return ft.View(route, [
            ft.AppBar(title=ft.Text(title), bgcolor=color),
            ft.TextField(value=title),
            ft.ElevatedButton(
                f"{next_route1} へ移動", on_click=lambda _: page.go(next_route1)),
            ft.ElevatedButton(
                f"{next_route2} を被せる", on_click=lambda _: page.go(next_route2)),
            ft.ElevatedButton(
                f"Root に移動", on_click=lambda _: page.go("/")),
        ])

    def route_change(handler):
        troute = ft.TemplateRoute(handler.route)
        if troute.match("/"):
            page.views.clear()
            page.views.append(
                create_view("/", "Root", "/view1", "/cover/view1", ft.colors.BLUE))
        elif troute.match("/view1"):
            page.views.clear()
            page.views.append(
                create_view("/view1", "View1", "/view2", "/cover/view2", ft.colors.RED))
        elif troute.match("/view2"):
            page.views.clear()
            page.views.append(
                create_view("/view2", "View2", "/view1", "/cover/view1", ft.colors.RED))
        elif troute.match("/cover/view1"):
            page.views.append(
                create_view("/cover/view1", "View1", "/view2", "/cover/view2", ft.colors.GREEN))
        elif troute.match("/cover/view2"):
            page.views.append(
                create_view("/cover/view2", "View2", "/view1", "/cover/view1", ft.colors.YELLOW_800))
        page.update()

    # ルート変更時のロジック設定
    page.on_route_change = route_change

    def view_pop(handler):
        page.views.pop()  # 1つ前に戻る
        page.go("/back")
        # page.update()
        # update() だと route が変更されない。
        # そうなると1つ戻ってまた進むことができなくなるので go("/back") で回避。不具合?

    # 戻る時のロジック設定
    page.on_view_pop = view_pop

    # Page レイアウト
    page.title = "Navigation and routing"
    # 初期表示
    page.go("/")


if __name__ == "__main__":
    ft.app(target=main)

以上です。

9
5
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
9
5