はじめに
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
詳細
Fletの画面遷移(ルーティング)の仕様
↑が公式サイトの図ですが、画面表示の仕組みとしてはまさにこの図の通りです。
まず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
に設定した関数が実行される→その中で画面切り替えする
サンプルソースは↓。
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()
する
サンプルソースは↓
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)
以上です。