Flet 做了一件听起来有点"离谱"的事------它让你只用 Python,就能同时构建桌面、Web 和移动端应用,连一行 HTML、CSS、JavaScript 都不需要写。它的底层是 Flutter 渲染引擎,界面漂亮、性能扎实。如果你是一个 Python 开发者,却一直被前端的复杂性挡在门外,Flet 就是为你准备的。
一、环境搭建:从零开始
Flet 的安装极其简单,一行命令搞定:
bash
pip install 'flet[all]'
安装完成后,验证一下版本:
bash
flet --version
运行方式有两种:
bash
# 作为桌面应用运行
flet run app.py
# 作为 Web 应用运行(自动打开浏览器)
flet run --web app.py
环境要求: Python 3.10 或以上版本。
二、第一个 Flet 应用:Hello World
每一个 Flet 应用的结构都非常固定,掌握这个模板,后面的一切都是往里填内容。
python
import flet as ft
def main(page: ft.Page):
page.title = "我的第一个 Flet 应用"
page.window.width = 400
page.window.height = 300
page.add(ft.Text("Hello, World! 🎉"))
ft.app(target=main)
这三行逻辑,就是 Flet 的核心骨架:
-
Page是整个应用的根容器,相当于一张白纸 -
ft.Text是最基础的文本控件 -
page.add()把控件"贴"到页面上
三、核心控件:构建界面的积木
Flet 提供了 150+ 个内置控件,按用途分为几大类。
3.1 基础显示控件
python
import flet as ft
def main(page: ft.Page):
page.add(
ft.Text("这是标题", size=30, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE),
ft.Text("这是正文,颜色灰一点", size=14, color=ft.Colors.GREY),
ft.Icon(ft.Icons.FAVORITE, color=ft.Colors.RED, size=40),
ft.Image(src="https://picsum.photos/200/100", width=200, height=100),
)
ft.app(target=main)
3.2 输入控件
python
import flet as ft
def main(page: ft.Page):
def on_submit(e):
page.add(ft.Text(f"你输入了:{name_field.value}"))
name_field.value = ""
page.update()
name_field = ft.TextField(
label="请输入姓名",
hint_text="例如:张三",
width=300,
on_submit=on_submit, # 按回车触发
)
dropdown = ft.Dropdown(
label="选择城市",
width=300,
options=[
ft.dropdown.Option("北京"),
ft.dropdown.Option("上海"),
ft.dropdown.Option("深圳"),
],
)
checkbox = ft.Checkbox(label="我同意用户协议", value=False)
page.add(name_field, dropdown, checkbox,
ft.ElevatedButton("提交", on_click=on_submit))
ft.app(target=main)
3.3 按钮家族
| 控件 | 用途 | 示例 |
|---|---|---|
ElevatedButton |
主要操作,有阴影 | 提交、确认 |
TextButton |
次要操作,扁平 | 取消、返回 |
OutlinedButton |
中等强调,有边框 | 次级确认 |
IconButton |
图标按钮,节省空间 | 工具栏操作 |
FloatingActionButton |
浮动主操作按钮 | 新建、添加 |
四、布局系统:让界面整齐起来
布局是 Flet 里最需要花时间理解的部分。核心就三个容器:Row(横排)、Column(竖排)、Stack(叠放)。
4.1 Row 和 Column
python
import flet as ft
def main(page: ft.Page):
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
# Row:横向排列
row_demo = ft.Row(
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
controls=[
ft.Container(
content=ft.Text("左"),
bgcolor=ft.Colors.BLUE_100,
padding=10,
),
ft.Container(
content=ft.Text("中"),
bgcolor=ft.Colors.GREEN_100,
padding=10,
),
ft.Container(
content=ft.Text("右"),
bgcolor=ft.Colors.RED_100,
padding=10,
),
],
width=400,
)
# Column:纵向排列
col_demo = ft.Column(
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
ft.Text("第一行"),
ft.Text("第二行"),
ft.Text("第三行"),
],
)
page.add(row_demo, ft.Divider(), col_demo)
ft.app(target=main)
4.2 Container:万能包装器
Container 是 Flet 里最灵活的控件,可以加背景色、圆角、阴影、内边距:
python
ft.Container(
content=ft.Text("卡片内容"),
bgcolor=ft.Colors.WHITE,
border_radius=12,
padding=20,
shadow=ft.BoxShadow(
spread_radius=1,
blur_radius=8,
color=ft.Colors.with_opacity(0.2, ft.Colors.BLACK),
),
width=300,
)
4.3 响应式布局
ResponsiveRow 可以根据屏幕宽度自动调整列数,适配桌面和手机:
python
ft.ResponsiveRow(
controls=[
ft.Column(col={"sm": 12, "md": 6, "xl": 4},
controls=[ft.Text("模块一")]),
ft.Column(col={"sm": 12, "md": 6, "xl": 4},
controls=[ft.Text("模块二")]),
ft.Column(col={"sm": 12, "md": 6, "xl": 4},
controls=[ft.Text("模块三")]),
]
)
五、事件处理与状态管理
Flet 的状态更新有一个铁律:修改控件属性后,必须调用 update() 才会刷新界面 。这一点和 React 的 setState 有点像,但更直白。
5.1 计数器:最经典的状态示例
python
import flet as ft
def main(page: ft.Page):
page.title = "计数器"
page.vertical_alignment = ft.MainAxisAlignment.CENTER
count_text = ft.Text("0", size=60, weight=ft.FontWeight.BOLD)
def increment(e):
count_text.value = str(int(count_text.value) + 1)
count_text.update() # 只更新这一个控件,更高效
def decrement(e):
count_text.value = str(int(count_text.value) - 1)
count_text.update()
def reset(e):
count_text.value = "0"
count_text.update()
page.add(
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.IconButton(ft.Icons.REMOVE, on_click=decrement),
count_text,
ft.IconButton(ft.Icons.ADD, on_click=increment),
],
),
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[ft.TextButton("重置", on_click=reset)],
),
)
ft.app(target=main)
5.2 用 Python 类封装组件状态
当应用变复杂,把状态和逻辑封装进类是最佳实践。@ft.control 装饰器让普通类变成可复用的 Flet 控件:
python
import flet as ft
@ft.control
class CounterCard(ft.Container):
"""一个可复用的计数卡片组件"""
title: str = "计数器"
def init(self):
self._count = 0
self._label = ft.Text(
str(self._count), size=40, weight=ft.FontWeight.BOLD
)
self.content = ft.Column(
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
ft.Text(self.title, size=16, color=ft.Colors.GREY),
self._label,
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.IconButton(ft.Icons.REMOVE, on_click=self._dec),
ft.IconButton(ft.Icons.ADD, on_click=self._inc),
],
),
],
)
self.bgcolor = ft.Colors.WHITE
self.border_radius = 12
self.padding = 20
self.shadow = ft.BoxShadow(blur_radius=8,
color=ft.Colors.with_opacity(0.15, ft.Colors.BLACK))
def _inc(self, e):
self._count += 1
self._label.value = str(self._count)
self.update()
def _dec(self, e):
self._count -= 1
self._label.value = str(self._count)
self.update()
def main(page: ft.Page):
page.bgcolor = ft.Colors.GREY_100
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.vertical_alignment = ft.MainAxisAlignment.CENTER
# 复用同一个组件,实例化两次
page.add(
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[CounterCard(title="苹果"), CounterCard(title="橙子")],
)
)
ft.app(target=main)
六、进阶实战:完整 Todo 应用
这是官方推荐的入门项目,麻雀虽小五脏俱全------涵盖了布局、事件、组件复用、列表操作的全部核心知识。
python
from dataclasses import field
from typing import Callable
import flet as ft
@ft.control
class TaskItem(ft.Column):
"""单个任务条目组件"""
task_name: str = ""
on_delete: Callable[["TaskItem"], None] = field(default=lambda t: None)
def init(self):
self._display_task = ft.Checkbox(
value=False, label=self.task_name, expand=True
)
self._edit_field = ft.TextField(expand=True)
# 显示模式
self._display_view = ft.Row(
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
controls=[
self._display_task,
ft.Row(spacing=0, controls=[
ft.IconButton(ft.Icons.EDIT_OUTLINED,
tooltip="编辑", on_click=self._edit_clicked),
ft.IconButton(ft.Icons.DELETE_OUTLINE,
tooltip="删除", on_click=self._delete_clicked),
]),
],
)
# 编辑模式
self._edit_view = ft.Row(
visible=False,
controls=[
self._edit_field,
ft.IconButton(ft.Icons.DONE_OUTLINE,
icon_color=ft.Colors.GREEN,
on_click=self._save_clicked),
],
)
self.controls = [self._display_view, self._edit_view]
def _edit_clicked(self, e):
self._edit_field.value = self._display_task.label
self._display_view.visible = False
self._edit_view.visible = True
self.update()
def _save_clicked(self, e):
self._display_task.label = self._edit_field.value
self._display_view.visible = True
self._edit_view.visible = False
self.update()
def _delete_clicked(self, e):
self.on_delete(self)
@ft.control
class TodoApp(ft.Column):
"""Todo 应用主组件"""
def init(self):
self._new_task = ft.TextField(
hint_text="添加新任务...", expand=True,
on_submit=self._add_task
)
self._tasks_view = ft.Column()
self.width = 600
self.controls = [
ft.Row(controls=[
self._new_task,
ft.FloatingActionButton(
icon=ft.Icons.ADD, on_click=self._add_task
),
]),
self._tasks_view,
]
def _add_task(self, e):
if self._new_task.value.strip():
task = TaskItem(
task_name=self._new_task.value,
on_delete=self._delete_task,
)
self._tasks_view.controls.append(task)
self._new_task.value = ""
self.update()
def _delete_task(self, task: TaskItem):
self._tasks_view.controls.remove(task)
self.update()
def main(page: ft.Page):
page.title = "Todo App"
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.add(TodoApp())
ft.app(target=main)
七、多页面导航与路由
真实应用不可能只有一个页面。Flet 提供了 View 和路由系统来管理多页面跳转。
python
import flet as ft
def main(page: ft.Page):
page.title = "多页面示例"
def route_change(e: ft.RouteChangeEvent):
page.views.clear()
# 首页
page.views.append(
ft.View(
route="/",
controls=[
ft.AppBar(title=ft.Text("首页"), bgcolor=ft.Colors.BLUE),
ft.ElevatedButton(
"前往详情页",
on_click=lambda _: page.go("/detail"),
),
],
)
)
# 详情页
if page.route == "/detail":
page.views.append(
ft.View(
route="/detail",
controls=[
ft.AppBar(
title=ft.Text("详情页"),
bgcolor=ft.Colors.GREEN,
leading=ft.IconButton(
ft.Icons.ARROW_BACK,
on_click=lambda _: page.go("/"),
),
),
ft.Text("这里是详情内容", size=20),
],
)
)
page.update()
def view_pop(e):
page.views.pop()
top_view = page.views[-1]
page.go(top_view.route)
page.on_route_change = route_change
page.on_view_pop = view_pop
page.go("/")
ft.app(target=main)
路由的工作流程如下:
八、异步编程:让界面不卡顿
涉及网络请求、文件读写这类耗时操作,必须用异步,否则界面会冻结。Flet 原生支持 async 事件处理器。
python
import asyncio
import flet as ft
def main(page: ft.Page):
status = ft.Text("准备就绪")
progress = ft.ProgressBar(visible=False, width=300)
async def fetch_data(e):
# 显示加载状态
btn.disabled = True
progress.visible = True
status.value = "加载中..."
page.update()
# 模拟网络请求(实际用 httpx.AsyncClient)
await asyncio.sleep(2)
# 更新结果
progress.visible = False
status.value = "✅ 数据加载完成!"
btn.disabled = False
page.update()
btn = ft.ElevatedButton("获取数据", on_click=fetch_data)
page.add(
ft.Column(
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
controls=[btn, progress, status],
)
)
ft.app(target=main)
实际项目中配合 httpx 请求真实 API:
python
import httpx
import flet as ft
async def load_users(e):
async with httpx.AsyncClient() as client:
resp = await client.get("https://jsonplaceholder.typicode.com/users")
users = resp.json()
for user in users:
list_view.controls.append(ft.ListTile(title=ft.Text(user["name"])))
page.update()
九、主题与样式:让应用更专业
9.1 全局主题配置
python
import flet as ft
def main(page: ft.Page):
page.title = "主题示例"
# 配置全局主题
page.theme = ft.Theme(
color_scheme_seed=ft.Colors.INDIGO, # 主色调种子色
font_family="Roboto",
)
page.dark_theme = ft.Theme(
color_scheme_seed=ft.Colors.INDIGO,
)
page.theme_mode = ft.ThemeMode.LIGHT # LIGHT / DARK / SYSTEM
def toggle_theme(e):
page.theme_mode = (
ft.ThemeMode.DARK
if page.theme_mode == ft.ThemeMode.LIGHT
else ft.ThemeMode.LIGHT
)
page.update()
page.add(
ft.ElevatedButton("切换深色/浅色模式", on_click=toggle_theme),
ft.Text("这段文字会跟随主题变色"),
ft.Card(
content=ft.Container(
content=ft.Text("这是一张卡片"),
padding=20,
)
),
)
ft.app(target=main)
9.2 对话框与通知
python
# SnackBar:轻量提示
page.open(ft.SnackBar(content=ft.Text("操作成功!")))
# AlertDialog:确认弹窗
page.open(
ft.AlertDialog(
title=ft.Text("确认删除"),
content=ft.Text("此操作不可撤销,确定要删除吗?"),
actions=[
ft.TextButton("取消", on_click=lambda e: page.close(dlg)),
ft.ElevatedButton("删除", on_click=confirm_delete),
],
)
)
十、打包发布:一键部署到各平台
写完应用,最后一步是把它交到用户手里。Flet 的 flet build 命令支持全平台打包。
bash
# 打包为 Windows 可执行文件
flet build windows
# 打包为 macOS 应用
flet build macos
# 打包为 Android APK
flet build apk
# 打包为 iOS IPA(需要 macOS + Xcode)
flet build ipa
# 打包为 Web(静态文件,可部署到 GitHub Pages)
flet build web
各平台发布要点:
| 目标平台 | 特殊要求 | 输出产物 |
|---|---|---|
| Windows | 无特殊要求 | .exe 安装包 |
| macOS | 需要 macOS 系统 | .app 包 |
| Android | 需要 Android SDK | .apk / .aab |
| iOS | 需要 macOS + Xcode | .ipa |
| Web | 无特殊要求 | 静态 HTML/JS 文件 |
| Linux | 需要 Linux 系统 | 可执行文件 |
知识地图:Flet 学习路径总览

Flet 的魅力在于它把"写界面"这件事的门槛压到了极低------你只需要会 Python,剩下的交给框架。从一个简单的计数器,到一个可以上架 App Store 的完整应用,走的是同一条路。上手容易,但深度不浅:异步、路由、组件化、响应式布局,每一块都值得深入探索。现在,打开终端,pip install 'flet[all]',然后开始写你的第一个应用吧。
参考资料
- Flet 官方网站 --- 官方主页,介绍核心特性与平台支持
- Flet 官方文档 & Todo 教程 --- 官方入门文档与完整 Todo 实战教程
- Python GUIs - Getting Started With Flet --- 第三方深度入门教程,涵盖控件、布局与事件处理
- Elevify - Flet Python Course --- 系统性课程大纲,涵盖状态管理、路由、异步、部署全流程