🎨 Theme Switcher
Xây dựng ứng dụng demo chuyển đổi Light/Dark theme. ⏱️ 20 phút
🎯 Mục tiêu
Phần tiêu đề “🎯 Mục tiêu”Tạo ứng dụng với:
- Toggle Light/Dark mode
- Custom color scheme
- Preview các UI components với theme
🚀 Bắt đầu
Phần tiêu đề “🚀 Bắt đầu”Bước 1: Theme Mode cơ bản
Phần tiêu đề “Bước 1: Theme Mode cơ bản”import flet as ft
def main(page: ft.Page): page.title = "Theme Switcher" page.theme_mode = ft.ThemeMode.LIGHT
def toggle_theme(_): if page.theme_mode == ft.ThemeMode.LIGHT: page.theme_mode = ft.ThemeMode.DARK else: page.theme_mode = ft.ThemeMode.LIGHT page.update()
page.add( ft.Switch(label="Dark Mode", on_change=toggle_theme), ft.Text("Hello World!"), )
ft.run(main)Bước 2: Thêm các UI Components
Phần tiêu đề “Bước 2: Thêm các UI Components”import flet as ft
def main(page: ft.Page): page.title = "Theme Switcher" page.theme_mode = ft.ThemeMode.LIGHT page.padding = 20
def toggle_theme(e): page.theme_mode = ft.ThemeMode.DARK if e.control.value else ft.ThemeMode.LIGHT page.update()
page.add( ft.Row([ ft.Text("Theme Switcher", size=24, weight=ft.FontWeight.BOLD), ft.Container(expand=True), ft.Switch(label="Dark Mode", on_change=toggle_theme), ]), ft.Divider(), ft.Text("Buttons", weight=ft.FontWeight.BOLD), ft.Row([ ft.ElevatedButton("Elevated"), ft.FilledButton("Filled"), ft.OutlinedButton("Outlined"), ft.TextButton("Text"), ], wrap=True), ft.Text("Inputs", weight=ft.FontWeight.BOLD), ft.TextField(label="Text Field"), ft.Checkbox(label="Checkbox"), ft.Switch(label="Switch"), ft.Slider(value=50), )
ft.run(main)Bước 3: Custom Color Scheme
Phần tiêu đề “Bước 3: Custom Color Scheme”import flet as ft
# Custom themesLIGHT_THEME = ft.Theme( color_scheme=ft.ColorScheme( primary=ft.Colors.BLUE, secondary=ft.Colors.ORANGE, surface=ft.Colors.WHITE, background=ft.Colors.GREY_100, ),)
DARK_THEME = ft.Theme( color_scheme=ft.ColorScheme( primary=ft.Colors.CYAN, secondary=ft.Colors.AMBER, surface=ft.Colors.GREY_900, background=ft.Colors.BLACK, ),)
def main(page: ft.Page): page.title = "Theme Switcher" page.theme = LIGHT_THEME page.dark_theme = DARK_THEME page.theme_mode = ft.ThemeMode.LIGHT page.padding = 20
def toggle_theme(e): page.theme_mode = ft.ThemeMode.DARK if e.control.value else ft.ThemeMode.LIGHT page.update()
page.add( ft.Switch(label="Dark Mode", on_change=toggle_theme), )
ft.run(main)Bước 4: Code hoàn chỉnh với Preview
Phần tiêu đề “Bước 4: Code hoàn chỉnh với Preview”import flet as ft
@ft.observableclass ThemeState: is_dark: bool = False primary_color: str = "blue"
colors = { "blue": ft.Colors.BLUE, "green": ft.Colors.GREEN, "purple": ft.Colors.PURPLE, "orange": ft.Colors.ORANGE, "red": ft.Colors.RED, }
def toggle_mode(self): self.is_dark = not self.is_dark
def set_color(self, color: str): self.primary_color = color
def get_primary(self): return self.colors.get(self.primary_color, ft.Colors.BLUE)
state = ThemeState()
@ft.componentdef ColorPicker(): colors = ["blue", "green", "purple", "orange", "red"] color_map = { "blue": ft.Colors.BLUE, "green": ft.Colors.GREEN, "purple": ft.Colors.PURPLE, "orange": ft.Colors.ORANGE, "red": ft.Colors.RED, }
return ft.Row([ ft.Container( width=40, height=40, bgcolor=color_map[c], border_radius=20, border=ft.border.all(3, ft.Colors.WHITE) if state.primary_color == c else None, shadow=ft.BoxShadow(blur_radius=5, color=ft.Colors.BLACK26) if state.primary_color == c else None, on_click=lambda _, color=c: state.set_color(color), ) for c in colors ], spacing=10)
@ft.componentdef ComponentsPreview(): return ft.Column([ ft.Text("Preview Components", size=20, weight=ft.FontWeight.BOLD), ft.Divider(height=20),
# Buttons ft.Text("Buttons", weight=ft.FontWeight.W_500), ft.Row([ ft.ElevatedButton("Elevated"), ft.FilledButton("Filled"), ft.OutlinedButton("Outlined"), ], wrap=True),
# Form Elements ft.Text("Form Elements", weight=ft.FontWeight.W_500), ft.TextField(label="Username", prefix_icon=ft.Icons.PERSON), ft.TextField(label="Password", password=True, prefix_icon=ft.Icons.LOCK),
ft.Row([ ft.Checkbox(label="Remember me", value=True), ft.Switch(label="Notifications"), ]),
# Slider ft.Text("Slider", weight=ft.FontWeight.W_500), ft.Slider(value=60, min=0, max=100),
# Progress ft.Text("Progress", weight=ft.FontWeight.W_500), ft.ProgressBar(value=0.7),
# Card ft.Text("Card", weight=ft.FontWeight.W_500), ft.Card( content=ft.Container( content=ft.Column([ ft.ListTile( leading=ft.Icon(ft.Icons.PERSON), title=ft.Text("John Doe"), ), ft.Row([ ft.TextButton("EDIT"), ft.TextButton("DELETE"), ], alignment=ft.MainAxisAlignment.END), ]), padding=10, ), ), ], spacing=15, scroll=ft.ScrollMode.AUTO)
@ft.componentdef ThemeSettings(): return ft.Card( content=ft.Container( content=ft.Column([ ft.Text("Theme Settings", size=20, weight=ft.FontWeight.BOLD), ft.Divider(),
# Mode toggle ft.Row([ ft.Icon(ft.Icons.LIGHT_MODE if not state.is_dark else ft.Icons.DARK_MODE), ft.Text("Dark Mode"), ft.Container(expand=True), ft.Switch( value=state.is_dark, on_change=lambda _: state.toggle_mode(), ), ]),
# Color picker ft.Text("Primary Color", weight=ft.FontWeight.W_500), ColorPicker(), ]), padding=20, ), )
@ft.componentdef ThemeSwitcherApp(): return ft.Row([ ft.Container( content=ThemeSettings(), width=300, ), ft.VerticalDivider(), ft.Container( content=ComponentsPreview(), expand=True, padding=20, ), ], expand=True)
def main(page: ft.Page): page.title = "Theme Switcher" page.padding = 20
# Apply dynamic theme def update_theme(): page.theme = ft.Theme( color_scheme=ft.ColorScheme(primary=state.get_primary()) ) page.dark_theme = ft.Theme( color_scheme=ft.ColorScheme(primary=state.get_primary()) ) page.theme_mode = ft.ThemeMode.DARK if state.is_dark else ft.ThemeMode.LIGHT page.update()
# Watch for state changes page.add(ThemeSwitcherApp())
# Simple approach - rebuild on theme change def on_state_change(): update_theme()
update_theme()
ft.run(main)✅ Code đơn giản hơn (Imperative)
Phần tiêu đề “✅ Code đơn giản hơn (Imperative)”import flet as ft
def main(page: ft.Page): page.title = "Theme Switcher" page.padding = 30
# Theme colors colors = [ft.Colors.BLUE, ft.Colors.GREEN, ft.Colors.PURPLE, ft.Colors.ORANGE]
def toggle_theme(e): page.theme_mode = ft.ThemeMode.DARK if e.control.value else ft.ThemeMode.LIGHT page.update()
def change_color(color): def handler(_): page.theme = ft.Theme(color_scheme=ft.ColorScheme(primary=color)) page.dark_theme = ft.Theme(color_scheme=ft.ColorScheme(primary=color)) page.update() return handler
page.add( ft.Text("🎨 Theme Switcher", size=28, weight=ft.FontWeight.BOLD), ft.Divider(),
# Dark mode toggle ft.Switch(label="Dark Mode", on_change=toggle_theme),
# Color picker ft.Text("Primary Color:", weight=ft.FontWeight.W_500), ft.Row([ ft.Container( width=50, height=50, bgcolor=c, border_radius=25, on_click=change_color(c), ) for c in colors ], spacing=10),
ft.Divider(),
# Preview ft.Text("Preview:", weight=ft.FontWeight.W_500), ft.ElevatedButton("Elevated Button"), ft.FilledButton("Filled Button"), ft.TextField(label="Sample Input"), ft.Slider(value=50), ft.ProgressBar(value=0.6), )
ft.run(main)🎯 Thử thách mở rộng
Phần tiêu đề “🎯 Thử thách mở rộng”- Save preference - Lưu theme vào client storage
- System theme - Theo theme của hệ điều hành
- Custom fonts - Đổi font family
- More colors - Color picker đầy đủ hơn
📝 Kiến thức ôn tập
Phần tiêu đề “📝 Kiến thức ôn tập”page.theme_mode- Light/Dark modepage.themevàpage.dark_theme- Custom themesft.ColorScheme- Bảng màu- Dynamic UI updates
⏭️ Tiếp theo
Phần tiêu đề “⏭️ Tiếp theo”Tuyệt! Tiếp tục với Navigation & Routing để học điều hướng.