Navigation & Routing trong Flet
Học cách điều hướng giữa các màn hình trong ứng dụng Flet. ⏱️ 20 phút
🎯 Mục tiêu
Nắm vững:
- Routes và Navigation
- Views (màn hình)
- AppBar và NavigationBar
- Passing data giữa các màn hình
📍 Routes cơ bản
Flet sử dụng routes để điều hướng, tương tự web URLs:
import flet as ft
@ft.observable
class RouterState:
route: str = "/"
state = RouterState()
@ft.component
def HomeView():
return ft.View(
"/",
[
ft.AppBar(title=ft.Text("Home")),
ft.Text("Welcome to Home!", size=24),
ft.ElevatedButton(
"Go to Settings",
on_click=lambda _: setattr(state, 'route', '/settings'),
),
],
)
@ft.component
def SettingsView():
return ft.View(
"/settings",
[
ft.AppBar(title=ft.Text("Settings")),
ft.Text("Settings Page", size=24),
ft.ElevatedButton(
"Back to Home",
on_click=lambda _: setattr(state, 'route', '/'),
),
],
)
@ft.component
def Router():
if state.route == "/settings":
return SettingsView()
return HomeView()
def main(page: ft.Page):
page.title = "Navigation Demo"
page.add(Router())
ft.run(main)🏗️ Tổ chức code với Components
Tách view thành các components riêng:
import flet as ft
@ft.observable
class AppState:
current_route: str = "/"
def go(self, route: str):
self.current_route = route
state = AppState()
@ft.component
def HomeView():
return ft.View(
"/",
[
ft.AppBar(title=ft.Text("Home"), bgcolor=ft.Colors.BLUE),
ft.Container(
content=ft.Column([
ft.Text("🏠 Home Page", size=28, weight=ft.FontWeight.BOLD),
ft.ElevatedButton("Go to Profile", on_click=lambda _: state.go("/profile")),
ft.ElevatedButton("Go to Settings", on_click=lambda _: state.go("/settings")),
], spacing=20),
padding=30,
),
],
)
@ft.component
def ProfileView():
return ft.View(
"/profile",
[
ft.AppBar(
title=ft.Text("Profile"),
bgcolor=ft.Colors.GREEN,
leading=ft.IconButton(ft.Icons.ARROW_BACK, on_click=lambda _: state.go("/")),
),
ft.Container(
content=ft.Column([
ft.Text("👤 Profile Page", size=28, weight=ft.FontWeight.BOLD),
ft.Text("User: John Doe"),
], spacing=20),
padding=30,
),
],
)
@ft.component
def SettingsView():
return ft.View(
"/settings",
[
ft.AppBar(
title=ft.Text("Settings"),
bgcolor=ft.Colors.ORANGE,
leading=ft.IconButton(ft.Icons.ARROW_BACK, on_click=lambda _: state.go("/")),
),
ft.Container(
content=ft.Column([
ft.Text("⚙️ Settings Page", size=28, weight=ft.FontWeight.BOLD),
ft.Switch(label="Dark Mode"),
ft.Switch(label="Notifications"),
], spacing=20),
padding=30,
),
],
)
@ft.component
def AppRouter():
routes = {
"/": HomeView,
"/profile": ProfileView,
"/settings": SettingsView,
}
ViewComponent = routes.get(state.current_route, HomeView)
return ViewComponent()
def main(page: ft.Page):
page.title = "Navigation Demo"
page.add(AppRouter())
ft.run(main)🔗 Route với Parameters
Truyền dữ liệu qua state:
import flet as ft
@ft.observable
class ItemsState:
items: list = [
{"id": 1, "name": "Item 1", "description": "Description 1"},
{"id": 2, "name": "Item 2", "description": "Description 2"},
{"id": 3, "name": "Item 3", "description": "Description 3"},
]
current_route: str = "/"
selected_item_id: int = None
def go_to_detail(self, item_id: int):
self.selected_item_id = item_id
self.current_route = "/detail"
def go_back(self):
self.selected_item_id = None
self.current_route = "/"
def get_selected_item(self):
return next((i for i in self.items if i["id"] == self.selected_item_id), None)
state = ItemsState()
@ft.component
def ItemsList():
return ft.View(
"/",
[
ft.AppBar(title=ft.Text("Items")),
ft.Column([
ft.ListTile(
title=ft.Text(item["name"]),
subtitle=ft.Text(item["description"]),
on_click=lambda _, id=item["id"]: state.go_to_detail(id),
trailing=ft.Icon(ft.Icons.ARROW_FORWARD_IOS),
)
for item in state.items
]),
],
)
@ft.component
def ItemDetail():
item = state.get_selected_item()
if not item:
return ft.View("/detail", [ft.Text("Item not found")])
return ft.View(
"/detail",
[
ft.AppBar(
title=ft.Text("Item Detail"),
leading=ft.IconButton(ft.Icons.ARROW_BACK, on_click=lambda _: state.go_back()),
),
ft.Container(
content=ft.Column([
ft.Text(f"ID: {item['id']}", size=16),
ft.Text(item["name"], size=28, weight=ft.FontWeight.BOLD),
ft.Text(item["description"], size=18),
], spacing=10),
padding=30,
),
],
)
@ft.component
def App():
if state.current_route == "/detail":
return ItemDetail()
return ItemsList()
def main(page: ft.Page):
page.title = "Route Params"
page.add(App())
ft.run(main)🧭 NavigationBar (Bottom Navigation)
import flet as ft
@ft.observable
class NavState:
selected_index: int = 0
def set_index(self, index: int):
self.selected_index = index
state = NavState()
@ft.component
def ContentArea():
contents = ["🏠 Home Content", "🔍 Search Content", "👤 Profile Content"]
return ft.Container(
content=ft.Text(contents[state.selected_index], size=24),
expand=True,
alignment=ft.alignment.center,
)
@ft.component
def BottomNavApp():
return ft.Column([
ContentArea(),
ft.NavigationBar(
selected_index=state.selected_index,
destinations=[
ft.NavigationBarDestination(icon=ft.Icons.HOME, label="Home"),
ft.NavigationBarDestination(icon=ft.Icons.SEARCH, label="Search"),
ft.NavigationBarDestination(icon=ft.Icons.PERSON, label="Profile"),
],
on_change=lambda e: state.set_index(e.control.selected_index),
),
], expand=True)
def main(page: ft.Page):
page.title = "Bottom Navigation"
page.add(BottomNavApp())
ft.run(main)🚂 NavigationRail (Side Navigation)
import flet as ft
@ft.observable
class RailState:
selected_index: int = 0
state = RailState()
@ft.component
def ContentPanel():
labels = ["Home", "Settings", "About"]
return ft.Container(
content=ft.Text(labels[state.selected_index], size=24),
expand=True,
alignment=ft.alignment.center,
)
@ft.component
def SideNavApp():
return ft.Row([
ft.NavigationRail(
selected_index=state.selected_index,
label_type=ft.NavigationRailLabelType.ALL,
destinations=[
ft.NavigationRailDestination(icon=ft.Icons.HOME, label="Home"),
ft.NavigationRailDestination(icon=ft.Icons.SETTINGS, label="Settings"),
ft.NavigationRailDestination(icon=ft.Icons.INFO, label="About"),
],
on_change=lambda e: setattr(state, 'selected_index', e.control.selected_index),
),
ft.VerticalDivider(width=1),
ContentPanel(),
], expand=True)
def main(page: ft.Page):
page.title = "Navigation Rail"
page.add(SideNavApp())
ft.run(main)📱 Tabs Navigation
import flet as ft
@ft.observable
class TabsState:
selected_index: int = 0
state = TabsState()
@ft.component
def TabContent(text: str, icon):
return ft.Container(
content=ft.Column([
ft.Icon(icon, size=48, color=ft.Colors.PRIMARY),
ft.Text(text, size=20),
], horizontal_alignment=ft.CrossAxisAlignment.CENTER, spacing=10),
padding=40,
alignment=ft.alignment.center,
)
@ft.component
def TabsApp():
tabs_data = [
("Home", ft.Icons.HOME, "Welcome to Home Tab"),
("Settings", ft.Icons.SETTINGS, "Configure your settings"),
("About", ft.Icons.INFO, "About this app"),
]
return ft.Tabs(
selected_index=state.selected_index,
on_change=lambda e: setattr(state, 'selected_index', e.control.selected_index),
tabs=[
ft.Tab(
text=name,
icon=icon,
content=TabContent(content, icon),
)
for name, icon, content in tabs_data
],
expand=True,
)
def main(page: ft.Page):
page.title = "Tabs Demo"
page.add(TabsApp())
ft.run(main)📋 Tổng kết Navigation Types
| Type | Use Case |
|---|---|
| Custom Router + State | Multi-page apps, deep linking |
| NavigationBar | Mobile bottom nav |
| NavigationRail | Desktop side nav |
| Tabs | Content sections |
⏭️ Tiếp theo
Tuyệt! Tiếp tục với Theming để học tùy chỉnh giao diện.
Last updated on