Skip to Content
Flet🎨 Theming

Theming trong Flet

Học cách tùy chỉnh giao diện ứng dụng với theme và styling. ⏱️ 15 phút

🎯 Mục tiêu

Nắm vững:

  • Theme mode (Light/Dark)
  • Custom ColorScheme
  • Typography và Font
  • Control-level styling

🌗 Theme Mode với Declarative UI

Flet hỗ trợ 3 theme modes. Với Declarative UI, ta quản lý qua state:

import flet as ft @ft.observable class ThemeState: is_dark: bool = False def toggle(self): self.is_dark = not self.is_dark state = ThemeState() @ft.component def ThemeToggle(): return ft.Switch( label="Dark Mode", value=state.is_dark, on_change=lambda _: state.toggle(), ) @ft.component def ThemeApp(): return ft.Column([ ThemeToggle(), ft.Text("Hello Flet!", size=24), ]) def main(page: ft.Page): # Thiết lập theme dựa trên state page.theme_mode = ft.ThemeMode.DARK if state.is_dark else ft.ThemeMode.LIGHT page.add(ThemeApp()) ft.run(main)

🎨 Custom Color Scheme

import flet as ft # Định nghĩa themes LIGHT_THEME = ft.Theme( color_scheme=ft.ColorScheme( primary=ft.Colors.PURPLE, secondary=ft.Colors.ORANGE, surface=ft.Colors.WHITE, background=ft.Colors.GREY_100, error=ft.Colors.RED, ), ) DARK_THEME = ft.Theme( color_scheme=ft.ColorScheme( primary=ft.Colors.PURPLE_200, secondary=ft.Colors.ORANGE_200, surface=ft.Colors.GREY_900, background=ft.Colors.BLACK, ), ) @ft.observable class ThemeState: is_dark: bool = False def toggle(self): self.is_dark = not self.is_dark state = ThemeState() @ft.component def ButtonsPreview(): return ft.Row([ ft.FilledButton("Filled"), ft.ElevatedButton("Elevated"), ft.OutlinedButton("Outlined"), ], wrap=True) @ft.component def ThemeDemo(): return ft.Column([ ft.Switch( label="Dark Mode", value=state.is_dark, on_change=lambda _: state.toggle(), ), ft.Divider(), ft.Text("Buttons Preview", weight=ft.FontWeight.BOLD), ButtonsPreview(), ], spacing=20) def main(page: ft.Page): page.title = "Theme Demo" page.theme = LIGHT_THEME page.dark_theme = DARK_THEME page.theme_mode = ft.ThemeMode.DARK if state.is_dark else ft.ThemeMode.LIGHT page.padding = 30 page.add(ThemeDemo()) ft.run(main)

🔤 Typography

Custom fonts và text styles:

import flet as ft @ft.component def TypographyDemo(): return ft.Column([ ft.Text("Default font", size=16), ft.Text("Bold Text", weight=ft.FontWeight.BOLD, size=20), ft.Text("Italic Text", italic=True, size=18), ft.Text("Colored Text", color=ft.Colors.BLUE, size=18), ], spacing=10) def main(page: ft.Page): # Thêm custom font (đặt file .ttf trong thư mục assets) page.fonts = { "Roboto": "fonts/Roboto-Regular.ttf", "RobotoBold": "fonts/Roboto-Bold.ttf", } # Hoặc Google Fonts (tự động tải) page.fonts = { "Kanit": "Kanit", "Open Sans": "Open Sans", } page.theme = ft.Theme(font_family="Kanit") page.add(TypographyDemo()) ft.run(main)

🎛️ Control-level Styling

Styled Button Component

import flet as ft @ft.component def StyledButton(text: str, color=ft.Colors.GREEN): return ft.ElevatedButton( text, style=ft.ButtonStyle( color=ft.Colors.WHITE, bgcolor=color, padding=20, shape=ft.RoundedRectangleBorder(radius=10), elevation=5, ), ) @ft.component def ButtonsRow(): return ft.Row([ StyledButton("Success", ft.Colors.GREEN), StyledButton("Warning", ft.Colors.ORANGE), StyledButton("Danger", ft.Colors.RED), ], spacing=10) def main(page: ft.Page): page.add(ButtonsRow()) ft.run(main)

Styled Container Component

import flet as ft @ft.component def StyledCard(title: str, content: str): return ft.Container( content=ft.Column([ ft.Text(title, weight=ft.FontWeight.BOLD, size=18), ft.Text(content, color=ft.Colors.GREY_600), ], spacing=5), width=250, padding=20, bgcolor=ft.Colors.BLUE_50, border=ft.border.all(2, ft.Colors.BLUE), border_radius=15, shadow=ft.BoxShadow( blur_radius=10, spread_radius=2, color=ft.Colors.BLACK26, ), ) @ft.component def GradientCard(title: str): return ft.Container( content=ft.Text(title, color=ft.Colors.WHITE, size=20), width=200, height=100, alignment=ft.alignment.center, border_radius=15, gradient=ft.LinearGradient( begin=ft.alignment.top_left, end=ft.alignment.bottom_right, colors=[ft.Colors.BLUE_400, ft.Colors.PURPLE_400], ), ) @ft.component def CardsDemo(): return ft.Column([ StyledCard("Card Title", "This is card content"), GradientCard("Gradient Card"), ], spacing=20) def main(page: ft.Page): page.padding = 30 page.add(CardsDemo()) ft.run(main)

🎯 Complete Theme Switcher

import flet as ft @ft.observable class ThemeState: is_dark: bool = False primary_color: str = "indigo" colors_map = { "indigo": (ft.Colors.INDIGO, ft.Colors.INDIGO_200), "green": (ft.Colors.GREEN, ft.Colors.GREEN_200), "purple": (ft.Colors.PURPLE, ft.Colors.PURPLE_200), "orange": (ft.Colors.ORANGE, ft.Colors.ORANGE_200), } 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): light, dark = self.colors_map.get(self.primary_color, (ft.Colors.INDIGO, ft.Colors.INDIGO_200)) return dark if self.is_dark else light state = ThemeState() @ft.component def ColorPicker(): colors = ["indigo", "green", "purple", "orange"] return ft.Row([ ft.Container( width=40, height=40, bgcolor=state.colors_map[c][0], border_radius=20, border=ft.border.all(3, ft.Colors.WHITE) if state.primary_color == c else None, on_click=lambda _, color=c: state.set_color(color), ) for c in colors ], spacing=10) @ft.component def ComponentsPreview(): return ft.Column([ ft.Text("Preview", size=20, weight=ft.FontWeight.BOLD), ft.Divider(), ft.Row([ ft.FilledButton("Filled"), ft.ElevatedButton("Elevated"), ft.OutlinedButton("Outlined"), ], wrap=True), ft.TextField(label="Sample Input"), ft.Slider(value=60), ft.ProgressBar(value=0.7), ft.Card( content=ft.Container( content=ft.ListTile( leading=ft.Icon(ft.Icons.PERSON), title=ft.Text("John Doe"), subtitle=ft.Text("[email protected]"), ), padding=10, ), ), ], spacing=15) @ft.component def ThemeSettings(): return ft.Card( content=ft.Container( content=ft.Column([ ft.Text("Theme Settings", size=20, weight=ft.FontWeight.BOLD), ft.Divider(), ft.Row([ ft.Icon(ft.Icons.DARK_MODE if state.is_dark else ft.Icons.LIGHT_MODE), ft.Text("Dark Mode"), ft.Container(expand=True), ft.Switch( value=state.is_dark, on_change=lambda _: state.toggle_mode(), ), ]), ft.Text("Primary Color", weight=ft.FontWeight.W_500), ColorPicker(), ]), padding=20, ), ) @ft.component def 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 # Dynamic theme based on state 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.add(ThemeSwitcherApp()) ft.run(main)

📋 ColorScheme Properties

PropertyDescription
primaryMàu chính (buttons, links)
secondaryMàu phụ
surfaceMàu surfaces (cards)
backgroundMàu nền
errorMàu lỗi
on_primaryText trên primary
on_surfaceText trên surface

⏭️ Tiếp theo

Tuyệt! Tiếp tục với File Operations để làm việc với files.

Last updated on