Flet 完全教程:用纯 Python 构建跨平台应用

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 的核心骨架:

flowchart LR A["ft.app(target=main)"] --> B["创建 Page 对象"] B --> C["执行 main(page)"] C --> D["page.add() 添加控件"] D --> E["渲染到屏幕"]
  • 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)

路由的工作流程如下:

flowchart TD A["page.go('/detail')"] --> B["触发 on_route_change"] B --> C["清空 page.views"] C --> D["重建所有需要的 View"] D --> E["page.update() 刷新"] E --> F["用户看到新页面"] F --> G["按返回键"] G --> H["触发 on_view_pop"] H --> I["弹出当前 View"] I --> F

八、异步编程:让界面不卡顿

涉及网络请求、文件读写这类耗时操作,必须用异步,否则界面会冻结。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]',然后开始写你的第一个应用吧。


参考资料

相关推荐
Cobyte2 小时前
24.Vue Vapor 组件事件 emit 的实现
前端·javascript·vue.js
Flynt2 小时前
TypeScript 5.8 让我少踩了一个大坑,顺手再聊聊 --erasableSyntaxOnly
前端·javascript·typescript
用户2930750976692 小时前
# 从零开始,打造 AI 产品的第一个关键体验——流式输出
前端·vue.js
小杍随笔2 小时前
【 .npmrc 终极配置指南:统一管理 npm/pnpm 缓存与全局目录,告别磁盘混乱】
前端·缓存·npm
默_笙2 小时前
👍 手写一个 MCP 文件读取服务:原来 AI 能"读"我的文件,全靠这个协议
前端·javascript
量化吞吐机2 小时前
2026年AI量化工具评估,先分开学习开发和验证
人工智能·python
饮茶三千2 小时前
基于 WangEditor 的富文本编辑器定制实践
前端·vue.js·设计模式
kyle~2 小时前
前端 --- Toast 轻量级弱反馈提示组件
前端
玉宇夕落2 小时前
Vue3 + 流式输出学习
前端