Flet是一个基于谷歌开发Flutter的Python跨平台开发框架,允许用你喜欢的语言构建交互式多用户Web,桌面和移动应用程序,而无需拥有前端开发的经验。使用Flet,您只需在Python中编写一个整体式有状态应用程序。
Flet UI 由Flutter控件构建,应用程序看起来相当专业。控件被组织到层次结构或树中,其中每个控件都有一个父控件(Page 除外)和容器控件(如 Column),下拉列表可以包含子控件。
按类别划分的控件:
下面分别介绍框架常用组件及示例:
1、Layout布局
- 页面
页是视图控件的容器。页面实例和根视图是在启动新用户会话时自动创建的。
- 视图
视图是所有其他控件的最顶层容器。根视图是在启动新用户会话时自动创建的。从布局角度来看,View 表示一个 Column 控件,因此它具有类似的行为并共享相同的属性。
代码语言:javascript复制向页中添加一个新控件
page.controls.append(Text("Hello!"))
page.update()
或者
page.add(Text("Hello!"))
删除页面上最上面的控件
page.controls.pop()
page.update()
页面内容与其边缘之间的空间。 默认值为每边 10 个像素。
page.padding = 0
page.update()
- Container容器
容器允许使用背景色和边框装饰控件,并使用填充,边距和对齐方式对其进行定位。
代码语言:javascript复制import flet
from flet import Container, ElevatedButton, OutlinedButton, Page, colors
def main(page: Page):
page.title = "Containers with background color"
c1 = Container(
content=ElevatedButton("Elevated Button in Container"),
bgcolor=colors.YELLOW,
padding=5,
)
c2 = Container(
content=ElevatedButton(
"Elevated Button with opacity=0.5 in Container", opacity=0.5
),
bgcolor=colors.YELLOW,
padding=5,
)
c3 = Container(
content=OutlinedButton("Outlined Button in Container"),
bgcolor=colors.YELLOW,
padding=5,
)
page.add(c1, c2, c3)
flet.app(target=main)
- Row行
在水平数组中显示其子项的控件。要使子控件展开并填充可用的水平空间,请设置其展开属性。
代码语言:javascript复制import flet
from flet import Column, Container, Page, Row, Text, alignment, colors
def main(page: Page):
def items(count):
items = []
for i in range(1, count 1):
items.append(
Container(
content=Text(value=i),
alignment=alignment.center,
width=50,
height=50,
bgcolor=colors.AMBER_500,
)
)
return items
def row_with_alignment(align):
return Column(
[
Text(align, size=16),
Container(
content=Row(items(3), alignment=align),
bgcolor=colors.AMBER_100,
),
]
)
page.add(
row_with_alignment("start"),
row_with_alignment("center"),
row_with_alignment("end"),
row_with_alignment("spaceBetween"),
row_with_alignment("spaceAround"),
row_with_alignment("spaceEvenly"),
)
flet.app(target=main)
- Column列
在垂直数组中显示其子项的控件。
要使子控件展开并填充可用的垂直空间,请设置其展开属性。
代码语言:javascript复制import flet
from flet import Column, Container, Page, Row, Text, alignment, colors
def main(page: Page):
def items(count):
items = []
for i in range(1, count 1):
items.append(
Container(
content=Text(value=i),
alignment=alignment.center,
width=50,
height=50,
bgcolor=colors.AMBER_500,
)
)
return items
def column_with_alignment(align):
return Column(
[
Text(align, size=16),
Container(
content=Column(items(3), alignment=align),
bgcolor=colors.AMBER_100,
height=400,
),
]
)
page.add(
Row(
[
column_with_alignment("start"),
column_with_alignment("center"),
column_with_alignment("end"),
column_with_alignment("spaceBetween"),
column_with_alignment("spaceAround"),
column_with_alignment("spaceEvenly"),
],
spacing=30,
alignment="start",
)
)
flet.app(target=main)
- ListView列表显示
线性排列的可滚动控件列表。ListView 是最常用的滚动控件。 它在滚动方向上一个接一个地显示其子级。 在交叉轴上,子项需要填充 ListView。
代码语言:javascript复制from time import sleep
import flet
from flet import ListView, Page, Text
def main(page: Page):
page.title = "Auto-scrolling ListView"
lv = ListView(expand=1, spacing=10, padding=20, auto_scroll=True)
page.add(lv)
count = 1
for i in range(0, 10):
sleep(1)
lv.controls.append(Text(f"Line {count}"))
count = 1
page.update()
flet.app(target=main)
- ListTile标题列表
单个固定高度的行,通常包含一些文本以及前导或图标。
代码语言:javascript复制import flet
from flet import (
Card,
Column,
Container,
Icon,
Image,
ListTile,
PopupMenuButton,
PopupMenuItem,
Text,
icons,
padding,
)
def main(page):
page.title = "ListTile Examples"
page.add(
Card(
content=Container(
width=500,
content=Column(
[
ListTile(
title=Text("One-line list tile"),
),
ListTile(title=Text("One-line dense list tile"), dense=True),
ListTile(
leading=Icon(icons.SETTINGS),
title=Text("One-line selected list tile"),
selected=True,
),
ListTile(
leading=Image(src="/icons/icon-192.png", fit="contain"),
title=Text("One-line with leading control"),
),
ListTile(
title=Text("One-line with trailing control"),
trailing=PopupMenuButton(
icon=icons.MORE_VERT,
items=[
PopupMenuItem(text="Item 1"),
PopupMenuItem(text="Item 2"),
],
),
),
ListTile(
leading=Icon(icons.ALBUM),
title=Text("One-line with leading and trailing controls"),
trailing=PopupMenuButton(
icon=icons.MORE_VERT,
items=[
PopupMenuItem(text="Item 1"),
PopupMenuItem(text="Item 2"),
],
),
),
ListTile(
leading=Icon(icons.SNOOZE),
title=Text("Two-line with leading and trailing controls"),
subtitle=Text("Here is a second title."),
trailing=PopupMenuButton(
icon=icons.MORE_VERT,
items=[
PopupMenuItem(text="Item 1"),
PopupMenuItem(text="Item 2"),
],
),
),
],
spacing=0,
),
padding=padding.symmetric(vertical=10),
)
)
)
flet.app(target=main)
- GridView网格列表
GridView 对于大型列表(数千个项目)非常有效。更喜欢它而不是包装 Column 或 Row 以实现平滑滚动。
- Tabs标签
选项卡控件用于导航经常访问的不同内容类别。选项卡允许在两个或多个内容视图之间导航,并依靠文本标题来表达内容的不同部分。
代码语言:javascript复制import flet
from flet import Container, Icon, Page, Tab, Tabs, Text, alignment, icons
def main(page: Page):
t = Tabs(
selected_index=1,
animation_duration=300,
tabs=[
Tab(
text="Tab 1",
content=Container(
content=Text("This is Tab 1"), alignment=alignment.center
),
),
Tab(
tab_content=Icon(icons.SEARCH),
content=Text("This is Tab 2"),
),
Tab(
text="Tab 3",
icon=icons.SETTINGS,
content=Text("This is Tab 3"),
),
],
expand=1,
)
page.add(t)
flet.app(target=main)
- Card卡片
材料设计卡:带有略微圆角和高程阴影的面板。
代码语言:javascript复制import flet
from flet import Card, Column, Container, Icon, ListTile, Row, Text, TextButton, icons
def main(page):
page.title = "Card Example"
page.add(
Card(
content=Container(
content=Column(
[
ListTile(
leading=Icon(icons.ALBUM),
title=Text("The Enchanted Nightingale"),
subtitle=Text(
"Music by Julie Gable. Lyrics by Sidney Stein."
),
),
Row(
[TextButton("Buy tickets"), TextButton("Listen")],
alignment="end",
),
]
),
width=400,
padding=10,
)
)
)
flet.app(target=main, view=flet.WEB_BROWSER)
- Divider水平分隔线
import flet
from flet import Column, Container, Divider, Page, alignment, colors
def main(page: Page):
page.add(
Column(
[
Container(
bgcolor=colors.AMBER,
alignment=alignment.center,
expand=True,
),
Divider(),
Container(bgcolor=colors.PINK, alignment=alignment.center, expand=True),
Divider(height=1, color="white"),
Container(
bgcolor=colors.BLUE_300,
alignment=alignment.center,
expand=True,
),
Divider(height=9, thickness=3),
Container(
bgcolor=colors.DEEP_PURPLE_200,
alignment=alignment.center,
expand=True,
),
],
spacing=0,
expand=True,
),
)
flet.app(target=main)
- VerticalDivider垂直分隔线
import flet
from flet import Container, Page, Row, VerticalDivider, alignment, colors
def main(page: Page):
page.add(
Row(
[
Container(
bgcolor=colors.ORANGE_300,
alignment=alignment.center,
expand=True,
),
VerticalDivider(),
Container(
bgcolor=colors.BROWN_400,
alignment=alignment.center,
expand=True,
),
VerticalDivider(width=1, color="white"),
Container(
bgcolor=colors.BLUE_300,
alignment=alignment.center,
expand=True,
),
VerticalDivider(width=9, thickness=3),
Container(
bgcolor=colors.GREEN_300,
alignment=alignment.center,
expand=True,
),
],
spacing=0,
expand=True,
)
)
flet.app(target=main)
2、Navigation导航
- AppBar设计应用栏
import flet
from flet import (
AppBar,
Icon,
IconButton,
Page,
PopupMenuButton,
PopupMenuItem,
Text,
colors,
icons,
)
def main(page: Page):
def check_item_clicked(e):
e.control.checked = not e.control.checked
page.update()
page.appbar = AppBar(
leading=Icon(icons.PALETTE),
leading_width=40,
title=Text("AppBar Example"),
center_title=False,
bgcolor=colors.SURFACE_VARIANT,
actions=[
IconButton(icons.WB_SUNNY_OUTLINED),
IconButton(icons.FILTER_3),
PopupMenuButton(
items=[
PopupMenuItem(text="Item 1"),
PopupMenuItem(), # divider
PopupMenuItem(
text="Checked item", checked=False, on_click=check_item_clicked
),
]
),
],
)
page.add(Text("Body!"))
flet.app(target=main)
- 导航轨
一种材质小部件,旨在显示在应用程序的左侧或右侧,以在少量视图之间导航,通常在三到五个之间。
代码语言:javascript复制import flet
from flet import (
Column,
FloatingActionButton,
Icon,
NavigationRail,
NavigationRailDestination,
Page,
Row,
Text,
VerticalDivider,
icons,
)
def main(page: Page):
rail = NavigationRail(
selected_index=0,
label_type="all",
# extended=True,
min_width=100,
min_extended_width=400,
leading=FloatingActionButton(icon=icons.CREATE, text="Add"),
group_alignment=-0.9,
destinations=[
NavigationRailDestination(
icon=icons.FAVORITE_BORDER, selected_icon=icons.FAVORITE, label="First"
),
NavigationRailDestination(
icon_content=Icon(icons.BOOKMARK_BORDER),
selected_icon_content=Icon(icons.BOOKMARK),
label="Second",
),
NavigationRailDestination(
icon=icons.SETTINGS_OUTLINED,
selected_icon_content=Icon(icons.SETTINGS),
label_content=Text("Settings"),
),
],
on_change=lambda e: print("Selected destination:", e.control.selected_index),
)
page.add(
Row(
[
rail,
VerticalDivider(width=1),
Column([Text("Body!")], alignment="start", expand=True),
],
expand=True,
)
)
flet.app(target=main)
3、Information Displays信息显示
- Text文本
- Icon显示图标
import flet
from flet import Icon, Page, Row, colors, icons
def main(page: Page):
page.add(
Row(
[
Icon(name=icons.FAVORITE, color=colors.PINK),
Icon(name=icons.AUDIOTRACK, color=colors.GREEN_400, size=30),
Icon(name=icons.BEACH_ACCESS, color=colors.BLUE, size=50),
Icon(name="settings", color="#c1c1c1"),
]
)
)
flet.app(target=main)
- Image图片
- Markdown
- CircleAvatar圆形头像
- ProgressBar进度条
- ProgressRing进度环
代码语言:javascript复制from time import sleep
import flet
from flet import Column, Page, ProgressRing, Row, Text
def main(page: Page):
pr = ProgressRing(width=16, height=16, stroke_width=2)
page.add(
Text("Circular progress indicator", style="headlineSmall"),
Row([pr, Text("Wait for the completion...")]),
Text("Indeterminate cicrular progress", style="headlineSmall"),
Column(
[ProgressRing(), Text("I'm going to run for ages...")],
horizontal_alignment="center",
),
)
for i in range(0, 101):
pr.value = i * 0.01
sleep(0.1)
page.update()
flet.app(target=main)
4、Buttons按钮
5、Input and Selections输入和选择
- checkbox复选框
复选框允许从组中选择一个或多个项目,或在两个互斥选项(选中或未选中,打开或关闭)之间切换。
代码语言:javascript复制import flet
from flet import Checkbox, ElevatedButton, Text
def main(page):
def button_clicked(e):
t.value = (
f"Checkboxes values are: {c1.value}, {c2.value}, {c3.value}, {c4.value}, {c5.value}."
)
page.update()
t = Text()
c1 = Checkbox(label="Unchecked by default checkbox", value=False)
c2 = Checkbox(label="Undefined by default tristate checkbox", tristate=True)
c3 = Checkbox(label="Checked by default checkbox", value=True)
c4 = Checkbox(label="Disabled checkbox", disabled=True)
c5 = Checkbox(
label="Checkbox with rendered label_position='left'", label_position="left"
)
b = ElevatedButton(text="Submit", on_click=button_clicked)
page.add(c1, c2, c3, c4, c5, b, t)
flet.app(target=main)
- Dropdown下拉框
下拉列表允许用户从多个项目中进行选择。 下拉列表显示当前选定的项目以及打开菜单以选择另一个项目的箭头。
代码语言:javascript复制import flet
from flet import Dropdown, Page, dropdown
def main(page: Page):
page.add(
Dropdown(
label="Color",
hint_text="Choose your favourite color?",
options=[
dropdown.Option("Red"),
dropdown.Option("Green"),
dropdown.Option("Blue"),
],
autofocus=True,
)
)
flet.app(target=main)
- Radio单选按钮
import flet
from flet import Column, ElevatedButton, Radio, RadioGroup, Text
def main(page):
def button_clicked(e):
t.value = f"Your favorite color is: {cg.value}"
page.update()
t = Text()
b = ElevatedButton(text='Submit', on_click=button_clicked)
cg = RadioGroup(content=Column([
Radio(value="red", label="Red"),
Radio(value="green", label="Green"),
Radio(value="blue", label="Blue")]))
page.add(Text("Select your favorite color:"), cg, b, t)
flet.app(target=main)
- Slider滑动条
import flet
from flet import Slider, Text
def main(page):
page.add(
Text("Slider with value:"),
Slider(value=0.3),
Text("Slider with a custom range and label:"),
Slider(min=0, max=100, divisions=10, label="{value}%"))
flet.app(target=main)
- Switch切换开关
import flet
from flet import Page, Switch
def main(page: Page):
def theme_changed(e):
page.theme_mode = "dark" if page.theme_mode == "light" else "light"
c.label = "Light theme" if page.theme_mode == "light" else "Dark theme"
page.update()
page.theme_mode = "light"
c = Switch(label="Light theme", on_change=theme_changed)
page.add(c)
flet.app(target=main)
- TextField文本域
文本字段允许用户使用硬件键盘或屏幕键盘输入文本。
代码语言:javascript复制import flet
from flet import ElevatedButton, Page, Text, TextField, icons
def main(page: Page):
def button_clicked(e):
t.value = f"Textboxes values are: '{tb1.value}', '{tb2.value}', '{tb3.value}', '{tb4.value}', '{tb5.value}'."
page.update()
t = Text()
tb1 = TextField(label="Standard")
tb2 = TextField(label="Disabled", disabled=True, value="First name")
tb3 = TextField(label="Read-only", read_only=True, value="Last name")
tb4 = TextField(label="With placeholder", hint_text="Please enter text here")
tb5 = TextField(label="With an icon", icon=icons.EMOJI_EMOTIONS)
b = ElevatedButton(text="Submit", on_click=button_clicked)
page.add(tb1, tb2, tb3, tb4, tb5, b, t)
flet.app(target=main)
Multiline TextFields多行输入
代码语言:javascript复制import flet
from flet import Page, TextField
def main(page: Page):
page.add(
TextField(label="standard", multiline=True),
TextField(
label="disabled",
multiline=True,
disabled=True,
value="line1nline2nline3nline4nline5",
),
TextField(
label="Auto adjusted height with max lines",
multiline=True,
min_lines=1,
max_lines=3,
),
)
flet.app(target=main)
TextFields with prefixes and suffixes文本带前缀、后缀
代码语言:javascript复制import flet
from flet import Page, TextField, icons
def main(page: Page):
page.add(
TextField(label="With prefix", prefix_text="https://"),
TextField(label="With suffix", suffix_text=".com"),
TextField(
label="With prefix and suffix", prefix_text="https://", suffix_text=".com"
),
TextField(
label="My favorite color",
icon=icons.FORMAT_SIZE,
hint_text="Type your favorite color",
helper_text="You can type only one color",
counter_text="0 symbols typed",
prefix_icon=icons.COLOR_LENS,
suffix_text="...is your color",
),
)
flet.app(target=main)
6、Dialogs, Alerts and Panels对话框、警报和面板
- Banner横幅面板
横幅显示重要、简洁的消息,并为用户提供处理(或关闭横幅)的操作。需要用户操作才能将其关闭。
横幅显示在屏幕顶部,顶部应用栏下方。它们是持久的和非模态的,允许用户在任何时候忽略它们或与它们交互。
- SnackBar提示信息
带有可选操作的轻量级消息,在屏幕底部短暂显示。
代码语言:javascript复制import flet
from flet import ElevatedButton, SnackBar, Text
class Data:
def __init__(self) -> None:
self.counter = 0
d = Data()
def main(page):
page.snack_bar = SnackBar(
content=Text("Hello, world!"),
action="Alright!",
)
def on_click(e):
page.snack_bar = SnackBar(Text(f"Hello {d.counter}"))
page.snack_bar.open = True
d.counter = 1
page.update()
page.add(ElevatedButton("Open SnackBar", on_click=on_click))
flet.app(target=main)
- AlertDialog警报对话框
警报对话框通知用户需要确认的情况。警报对话框有一个可选的标题和一个可选的操作列表。标题显示在内容上方,动作显示在内容下方。
代码语言:javascript复制import flet
from flet import AlertDialog, ElevatedButton, Page, Text, TextButton
def main(page: Page):
page.title = "AlertDialog examples"
dlg = AlertDialog(
title=Text("Hello, you!"), on_dismiss=lambda e: print("Dialog dismissed!")
)
def close_dlg(e):
dlg_modal.open = False
page.update()
dlg_modal = AlertDialog(
modal=True,
title=Text("Please confirm"),
content=Text("Do you really want to delete all those files?"),
actions=[
TextButton("Yes", on_click=close_dlg),
TextButton("No", on_click=close_dlg),
],
actions_alignment="end",
on_dismiss=lambda e: print("Modal dialog dismissed!"),
)
def open_dlg(e):
page.dialog = dlg
dlg.open = True
page.update()
def open_dlg_modal(e):
page.dialog = dlg_modal
dlg_modal.open = True
page.update()
page.add(
ElevatedButton("Open dialog", on_click=open_dlg),
ElevatedButton("Open modal dialog", on_click=open_dlg_modal),
)
flet.app(target=main)
7、FilePicker文件选择器
- 一个控件,允许您使用本机文件资源管理器来选择单个或多个文件,并具有扩展过滤支持和上传。
import flet
from flet import (
ElevatedButton,
FilePicker,
FilePickerResultEvent,
Page,
Row,
Text,
icons,
)
def main(page: Page):
# Pick files dialog
def pick_files_result(e: FilePickerResultEvent):
selected_files.value = (
", ".join(map(lambda f: f.name, e.files)) if e.files else "Cancelled!"
)
selected_files.update()
pick_files_dialog = FilePicker(on_result=pick_files_result)
selected_files = Text()
# Save file dialog
def save_file_result(e: FilePickerResultEvent):
save_file_path.value = e.path if e.path else "Cancelled!"
save_file_path.update()
save_file_dialog = FilePicker(on_result=save_file_result)
save_file_path = Text()
# Open directory dialog
def get_directory_result(e: FilePickerResultEvent):
directory_path.value = e.path if e.path else "Cancelled!"
directory_path.update()
get_directory_dialog = FilePicker(on_result=get_directory_result)
directory_path = Text()
# hide all dialogs in overlay
page.overlay.extend([pick_files_dialog, save_file_dialog, get_directory_dialog])
page.add(
Row(
[
ElevatedButton(
"Pick files",
icon=icons.UPLOAD_FILE,
on_click=lambda _: pick_files_dialog.pick_files(
allow_multiple=True
),
),
selected_files,
]
),
Row(
[
ElevatedButton(
"Save file",
icon=icons.SAVE,
on_click=lambda _: save_file_dialog.save_file(),
disabled=page.web,
),
save_file_path,
]
),
Row(
[
ElevatedButton(
"Open directory",
icon=icons.FOLDER_OPEN,
on_click=lambda _: get_directory_dialog.get_directory_path(),
disabled=page.web,
),
directory_path,
]
),
)
flet.app(target=main)
Upload multiple files上传多个文件
代码语言:javascript复制from typing import Dict
import flet
from flet import (
ElevatedButton,
FilePicker,
FilePickerResultEvent,
FilePickerUploadEvent,
FilePickerUploadFile,
Page,
ProgressRing,
Ref,
Row,
Text,
icons,
)
from flet.column import Column
def main(page: Page):
prog_bars: Dict[str, ProgressRing] = {}
files = Ref[Column]()
upload_button = Ref[ElevatedButton]()
def file_picker_result(e: FilePickerResultEvent):
upload_button.current.disabled = True if e.files == None else False
prog_bars.clear()
files.current.controls.clear()
if e.files != None:
for f in e.files:
prog = ProgressRing(value=0, bgcolor="#eeeeee", width=20, height=20)
prog_bars[f.name] = prog
files.current.controls.append(Row([prog, Text(f.name)]))
page.update()
def on_upload_progress(e: FilePickerUploadEvent):
prog_bars[e.file_name].value = e.progress
prog_bars[e.file_name].update()
file_picker = FilePicker(on_result=file_picker_result, on_upload=on_upload_progress)
def upload_files(e):
uf = []
if file_picker.result != None and file_picker.result.files != None:
for f in file_picker.result.files:
uf.append(
FilePickerUploadFile(
f.name,
upload_url=page.get_upload_url(f.name, 600),
)
)
file_picker.upload(uf)
# hide dialog in a overlay
page.overlay.append(file_picker)
page.add(
ElevatedButton(
"Select files...",
icon=icons.FOLDER_OPEN,
on_click=lambda _: file_picker.pick_files(allow_multiple=True),
),
Column(ref=files),
ElevatedButton(
"Upload",
ref=upload_button,
icon=icons.UPLOAD,
on_click=upload_files,
disabled=True,
),
)
flet.app(target=main, upload_dir="uploads")#目标文件路径
一个典型的 Flet 程序以调用 flet.app() 结束,应用程序开始等待新的用户会话。函数 main() 是 Flet 应用程序中的入口点。每个用户会话都在一个新线程上调用它,并传入一个Page 实例。
目前这个项目还只是一个BETA版的,功能组件还在进一步完善,目前已有组件交互效果、美观程度非常nice!造车的轮子都给大家准备好了,各位有兴趣的小伙伴可以尝试参考官方文档开发一些基础应用。下次分享Flet框架实际应用案例讲解,敬请期待~