跳到主要内容

文件选择器和上传

· 阅读需 6 分钟
Feodor Fitsner
Flet 创始人和开发者

终于,文件选择器和上传功能来了!🎉

文件选择器控件打开一个原生操作系统对话框,用于选择文件和目录。它基于出色的 file_picker Flutter 包。

它可以在所有平台上运行:Web、macOS、Windows、Linux、iOS 和 Android。

查看上图示例的源码

文件选择器允许打开三种对话框:

  • 选择文件 - 一个或多个文件,可以是任意文件或仅特定类型的文件。
  • 保存文件 - 选择目录和文件名。
  • 获取目录 - 选择目录。

在浏览器中运行 Flet 应用程序时,仅提供“选择文件”选项,并且仅用于上传,因为它显然不会返回所选文件的完整路径。

文件选择器真正出色的地方在于桌面!所有三个对话框都会返回所选文件和目录的完整路径,为您的用户提供了很好的帮助!

在您的应用中使用文件选择器

建议将文件选择器添加到 page.overlay.controls 集合中,这样它就不会影响您的应用布局。尽管文件选择器的尺寸为 0x0,但放入 RowColumn 时仍被视为控件。

import flet as ft

file_picker = ft.FilePicker()
page.overlay.append(file_picker)
page.update()

要打开文件选择器对话框,请调用以下三种方法之一:

  • pick_files()
  • save_file()
  • get_directory_path()

Lambda 表达式在这种情况下非常好用:

ft.ElevatedButton("选择文件...",
on_click=lambda _: file_picker.pick_files(allow_multiple=True))

当对话框关闭时,会调用 FilePicker.on_result 事件处理程序,该事件对象具有以下属性之一:

  • files - "选择文件"对话框,所选文件的列表,如果对话框被取消,则为 None
  • path - "保存文件"和"获取目录"对话框,所选文件或目录的完整路径,如果对话框被取消,则为 None
import flet as ft

def on_dialog_result(e: ft.FilePickerResultEvent):
print("选定的文件:", e.files)
print("选定的文件或目录:", e.path)

file_picker = ft.FilePicker(on_result=on_dialog_result)

最后的结果始终可在 FilePicker.result 属性中获取。

有关所有可用对话框方法及其参数,请查看 文件选择器 控件文档。

上传文件

文件选择器具有内置的上传功能,可以在所有平台和 Web 上运行。

要上传一个或多个文件,首先需要调用 FilePicker.pick_files()。 当用户选择文件后,它们不会自动上传到任何地方,而是将它们的引用保存在文件选择器的状态中。

要执行实际的上传,可以调用 FilePicker.upload() 方法,并传入需要上传的文件列表,以及它们的上传 URL 和上传方法(PUTPOST):

import flet as ft

def upload_files(e):
upload_list = []
if file_picker.result != None and file_picker.result.files != None:
for f in file_picker.result.files:
upload_list.append(
FilePickerUploadFile(
f.name,
upload_url=page.get_upload_url(f.name, 600),
)
)
file_picker.upload(upload_list)

ft.ElevatedButton("上传", on_click=upload_files)
备注

如果需要为每个用户单独上传,可以在 page.get_upload_url() 调用中指定带有任意数量目录的文件名,例如:

upload_url = page.get_upload_url(f"/{username}/pictures/{f.name}", 600)

如果 /{username}/pictures 目录不存在,将会自动在 upload_dir 内创建。

上传存储

请注意page.get_upload_url()方法的使用 - 它会为Flet的内部上传存储生成一个预签名的上传URL。

使用任意存储进行文件上传

可以使用 boto3 库为 AWS S3 存储生成预签名上传 URL

相同的技术应适用于 WasabiBackblazeMinIO 和任何其他具有 S3 兼容 API 的存储提供商。

要使Flet将上传的文件保存到目录中,请在flet.app()调用中提供目录的完整路径或相对路径:

ft.app(target=main, upload_dir="uploads")

您甚至可以将上传文件放在"assets"目录中,以便可以从Flet客户端直接访问上传的文件,例如图片、文档或其他媒体:

ft.app(target=main, assets_dir="assets", upload_dir="assets/uploads")

在应用程序的某个地方,您可以使用以下代码显示已上传的图片:

page.add(ft.Image(src="/uploads/<some-uploaded-picture.png>"))

上传进度

一旦调用FilePicker.upload()方法,Flet客户端就会异步地逐个上传选择的文件,并通过FilePicker.on_upload回调报告进度。

on_upload 事件的事件对象是 FilePickerUploadEvent 类的实例,具有以下字段:

  • file_name
  • progress - 值从 0.01.0
  • error

对于每个上传的文件,回调至少被调用两次:上传开始前进度为 0 和上传完成时进度为 1.0。对于大于 1 MB 的文件,每上传 10% 还会额外报告一次进度。

查看这个展示多个文件上传的示例

查看 文件选择器 控件文档以了解其所有属性和示例。

将Flet模块升级到最新版本(pip install flet --upgrade),尝试一下File Picker,并告诉我们您的想法!

享受吧!

玩转动画

· 阅读需 3 分钟
Feodor Fitsner
Flet创始人兼开发者

尽管Flet的动画支持发布已有一段时间,但我们刚刚完成了其新功能的文档编写!我们都知道,如果功能没有文档记录,就等于不存在!😉

Flutter提供了多种方法来创建动画,例如“隐式”、“显式”、“补间”、“分段”、“预设”动画以及显示在Rive和Lottie编辑器中准备的动画场景。

我们将从“隐式动画”开始,它允许您通过设置目标值来实现对控件属性的动画效果;每当目标值发生变化时,控件会将属性从旧值动画过渡到新值。

演示时间

查看演示源码。顺便提一下,演示托管在Heroku上,您可以将其用作自己部署的起点。

隐式动画

以下控件属性可以启用隐式动画:

此外,所有Container控件属性现在都可以动画化,并且新增了AnimatedSwitcher控件,用于在旧内容和新内容之间进行动画过渡。

其他新功能

Markdown控件

允许以Markdown格式渲染文本。支持多种扩展:CommonMarkGitHub WebGitHub Flavored

查看更多信息和示例,请参阅Markdown控件文档

URL 启动器

page.launch_url(url)方法允许以编程方式在新浏览器窗口中打开URL,例如:

page.launch_url("https://google.com")

它还可以与Markdown控件很好地配合使用,在markdown文档中打开链接。

键盘快捷键

Page现在包含on_keyboard_event事件处理程序,可全局拦截所有按键。

查看这个简单的使用示例

无障碍改进

我们在文档中增加了无障碍部分,涵盖了屏幕阅读器的语义支持。

ShaderMark控件

一个将着色器生成的遮罩应用于其内容的控件。允许制作出像逐渐淡出图像到下边缘的漂亮效果。

就是这些了!

试试Flet,并告诉我们您的想法!

在新的Flet版本中,我们为您带来了美丽的渐变效果、按钮样式和TextField圆角

· 阅读需 5 分钟
Feodor Fitsner
Flet 创始人兼开发者

我们刚刚发布了 Flet 0.1.46,增加了令人兴奋的新功能:

  • Container 的渐变背景
  • 按钮、TextField 和 Dropdown 控件进行了全面的样式设置
  • ...等等

渐变背景

线性渐变

import math
import flet as ft

def main(page: ft.Page):

page.add(
ft.Container(
alignment=ft.alignment.center,
gradient=ft.LinearGradient(
begin=ft.alignment.top_left,
end=Alignment(0.8, 1),
colors=[
"0xff1f005c",
"0xff5b0060",
"0xff870160",
"0xffac255e",
"0xffca485c",
"0xffe16b5c",
"0xfff39060",
"0xffffb56b",
],
tile_mode=ft.GradientTileMode.MIRROR,
rotation=math.pi / 3,
),
width=150,
height=150,
border_radius=5,
)
)

ft.app(target=main)

查看 LinearGradient 文档以获取更多关于 LinearGradient 属性的信息。

径向渐变

import flet as ft

def main(page: ft.Page):

page.add(
ft.Container(
alignment=ft.alignment.center,
gradient=ft.RadialGradient(
center=Alignment(0.7, -0.6),
radius=0.2,
colors=[
"0xFFFFFF00", # 黄色太阳
"0xFF0099FF", # 蓝色天空
],
stops=[0.4, 1.0],
),
width=150,
height=150,
border_radius=5,
)
)

ft.app(target=main)

查看 RadialGradient 文档以获取更多关于 RadialGradient 属性的信息。

扫描渐变

import math
import flet as ft

def main(page: ft.Page):

page.add(
ft.Container(
alignment=ft.alignment.center,
gradient=SweepGradient(
center=ft.alignment.center,
start_angle=0.0,
end_angle=math.pi * 2,
colors=[
"0xFF4285F4",
"0xFF34A853",
"0xFFFBBC05",
"0xFFEA4335",
"0xFF4285F4",
],
stops=[0.0, 0.25, 0.5, 0.75, 1.0],
),
width=150,
height=150,
border_radius=5,
)
)

ft.app(target=main)

查看 SweepGradient 文档以获取更多关于 SweepGradient 属性的信息。

按钮样式

此次 Flet 版本引入了 style 属性到所有按钮控件中,它是 ButtonStyle 类的一个实例。 ButtonStyle 允许控制按钮的所有视觉方面,例如形状、前景色、背景色和阴影颜色、内容填充、边框宽度和半径!

此外,每个单独的样式属性都可以针对按钮的不同“材料状态”进行配置,例如“悬停”、“聚焦”、“禁用”等。例如,您可以为悬停状态配置不同的形状、背景颜色,并为所有其他状态配置回退值。

查看这个“极限”样式示例:

import flet as ft
from flet.border import BorderSide
from flet.buttons import RoundedRectangleBorder

def main(page: ft.Page):

page.add(
ft.ElevatedButton(
"Styled button 1",
style=ft.ButtonStyle(
color={
ft.ControlState.HOVERED: ft.Colors.WHITE,
ft.ControlState.FOCUSED: ft.Colors.BLUE,
ft.ControlState.DEFAULT: ft.Colors.BLACK,
},
bgcolor={ft.ControlState.FOCUSED: ft.Colors.PINK_200, "": ft.Colors.YELLOW},
padding={ft.ControlState.HOVERED: 20},
overlay_color=ft.Colors.TRANSPARENT,
elevation={"pressed": 0, "": 1},
animation_duration=500,
side={
ft.ControlState.DEFAULT: BorderSide(1, ft.Colors.BLUE),
ft.ControlState.HOVERED: BorderSide(2, ft.Colors.BLUE),
},
shape={
ft.ControlState.HOVERED: RoundedRectangleBorder(radius=20),
ft.ControlState.DEFAULT: RoundedRectangleBorder(radius=2),
},
),
)
)

ft.app(target=main)

ft.ControlState.DEFAULT 状态是一个备用样式。

按钮的形状也可以通过 ButtonStyle.shape 属性进行更改:

import flet as ft
from flet.buttons import (
BeveledRectangleBorder,
CircleBorder,
ContinuousRectangleBorder,
RoundedRectangleBorder,
StadiumBorder,
)

def main(page: ft.Page):
page.padding = 30
page.spacing = 30
page.add(
ft.FilledButton(
"Stadium",
style=ft.ButtonStyle(
shape=ft.StadiumBorder(),
),
),
ft.FilledButton(
"Rounded rectangle",
style=ft.ButtonStyle(
shape=ft.RoundedRectangleBorder(radius=10),
),
),
ft.FilledButton(
"Continuous rectangle",
style=ft.ButtonStyle(
shape=ft.ContinuousRectangleBorder(radius=30),
),
),
ft.FilledButton(
"Beveled rectangle",
style=ft.ButtonStyle(
shape=ft.BeveledRectangleBorder(radius=10),
),
),
ft.FilledButton(
"Circle",
style=ft.ButtonStyle(shape=ft.CircleBorder(), padding=30),
),
)

ft.app(target=main)

请查看ElevatedButton.style属性文档,了解ButtonStyle类及其属性的完整描述。

TextField 和 Dropdown 样式

现在可以为 TextFieldDropdown 控件的正常和聚焦状态配置文字大小、边框样式和圆角半径。TextField 还允许配置光标和选择的颜色。

此外,现在可以使用max_length属性限制输入到TextField中的最大长度。

我们还引入了capitalization属性,用于在将字符键入TextField时自动进行大小写转换。您可以从4种大小写转换策略中选择:none(默认值)、characterswordssentences

以下是一个带有max_lengthcapitalization的样式化TextField示例:

import flet as ft

def main(page: ft.Page):
page.padding=50
page.add(
ft.TextField(
text_size=30,
cursor_color=ft.Colors.RED,
selection_color=ft.Colors.YELLOW,
color=ft.Colors.PINK,
bgcolor=ft.Colors.BLACK26,
filled=True,
focused_color=ft.Colors.GREEN,
focused_bgcolor=ft.Colors.CYAN_200,
border_radius=30,
border_color=ft.Colors.GREEN_800,
focused_border_color=ft.Colors.GREEN_ACCENT_400,
max_length=20,
capitalization="characters",
)
)

ft.app(target=main)

下面是一个样式化 Dropdown 控件的示例:

import flet as ft

def main(page: ft.Page):
page.padding=50
page.add(
ft.Dropdown(
options=[
ft.dropdown.Option("a", "Item A"),
ft.dropdown.Option("b", "Item B"),
ft.dropdown.Option("c", "Item C"),
],
border_radius=30,
filled=True,
border_color=ft.Colors.TRANSPARENT,
bgcolor=ft.Colors.BLACK12,
focused_bgcolor=ft.Colors.BLUE_100,
)
)

ft.app(target=main)

其他更改

IconButton 增加了 selected 状态,并与新的 style 配合使用。

这是一个切换图标按钮的示例:

import flet as ft

def main(page: ft.Page):

def toggle_icon_button(e):
e.control.selected = not e.control.selected
e.control.update()

page.add(
ft.IconButton(
icon=ft.Icons.BATTERY_1_BAR,
selected_icon=ft.Icons.BATTERY_FULL,
on_click=toggle_icon_button,
selected=False,
style=ft.ButtonStyle(color={"selected": ft.Colors.GREEN, "": ft.Colors.RED}),
)
)

ft.app(target=main)

试试 Flet告诉我们 你的想法!

控件引用

· 阅读需 3 分钟
Feodor Fitsner
Flet 创始人和开发者

Flet 控件是对象,要访问它们的属性,我们需要保留对这些对象的引用(变量)。

请看下面的示例:

import flet as ft

def main(page):

first_name = ft.TextField(label="First name", autofocus=True)
last_name = ft.TextField(label="Last name")
greetings = ft.Column()

def btn_click(e):
greetings.controls.append(ft.Text(f"Hello, {first_name.value} {last_name.value}!"))
first_name.value = ""
last_name.value = ""
page.update()
first_name.focus()

page.add(
first_name,
last_name,
ft.ElevatedButton("Say hello!", on_click=btn_click),
greetings,
)

ft.app(target=main)

main() 方法的一开始,我们创建了三个控件,这些控件将在按钮的 on_click 处理程序中使用:两个用于输入名字和姓氏的 TextField,以及一个用于显示问候信息的 Column。我们创建控件并设置其所有属性,在 main() 方法的最后,通过 page.add() 调用,我们使用它们的引用(变量)。

当添加越来越多的控件和事件处理程序时,将所有控件定义放在一个地方变得具有挑战性,因此它们会散布在 main() 的主体中。浏览 page.add() 的参数时,很难想象(不用不断跳转到 IDE 中的变量定义)最终的表单会是什么样子:

    page.add(
first_name,
last_name,
ft.ElevatedButton("Say hello!", on_click=btn_click),
greetings,
)

first_name 是一个 TextField 吗?它是否设置了自动聚焦(autofocus)?greetingsRow 还是 Column

Ref

Flet 提供了 Ref 实用程序类,它允许定义对控件的引用,在事件处理程序中使用该引用,并在构建树时将引用设置为实际控件。这个想法来自于 React

要定义一个新的类型控件引用:

first_name = ft.Ref[ft.TextField]()

要访问引用的控件(控件解引用),请使用 Ref.current 属性:

# 清空名字
first_name.current.value = ""

要将控件分配给引用,请将 Control.ref 属性设置为引用:

page.add(
ft.TextField(ref=first_name, label="First name", autofocus=True)
)
备注

所有 Flet 控件都有 ref 属性。

我们可以重写程序以使用引用:

import flet as ft


def main(page):

first_name = ft.Ref[ft.TextField]()
last_name = ft.Ref[ft.TextField]()
greetings = ft.Ref[ft.Column]()

def btn_click(e):
greetings.current.controls.append(
ft.Text(f"Hello, {first_name.current.value} {last_name.current.value}!")
)
first_name.current.value = ""
last_name.current.value = ""
page.update()
first_name.current.focus()

page.add(
ft.TextField(ref=first_name, label="First name", autofocus=True),
ft.TextField(ref=last_name, label="Last name"),
ft.ElevatedButton("Say hello!", on_click=btn_click),
ft.Column(ref=greetings),
)

ft.app(target=main)

现在我们可以在 page.add() 中清楚地看到页面的结构以及它包含的所有控件。

是的,逻辑变得有点冗长,因为需要添加 .current. 来访问引用的控件,但这只是个人喜好的问题 :)

尝试 Flet告诉我们 你的想法!

Flet 移动战略

· 阅读需 5 分钟
Feodor Fitsner
Flet 创始人和开发者

Flet 项目最近受到了很多关注,我们要感谢所有尝试 Flet 并在社区中传播它的开发者!你们的支持激励我们以更快的步伐推动 Flet 项目向前发展!

新的 Flet 开发者经常问是否有办法将 Flet 程序打包成 .apk 文件以部署到 Android 设备或 .ipa 文件以部署到 iOS。

在这篇文章中,我想分享我们关于 Flet 移动化的愿景,并提供一个路线图。

服务驱动 UI

Flet 是一个服务驱动 UI (SDUI) 框架。SDUI 是一种新兴技术,在 Technology Radar 的文章 中有最佳描述:

服务驱动 UI 将渲染分离到移动应用中的通用容器中,而每个视图的结构和数据则由服务器提供。这意味着曾经需要往返应用商店的更改现在可以通过简单更改服务器发送的响应来完成。

DoorDashAirbnbLyft 和其他公司已经成功地在他们的移动应用中实现了服务驱动 UI,以缩短上市时间。

Flet 方法

Flet 将实现服务驱动 UI 方法,在服务器上运行用 Python 或其他语言编写的程序,客户端只需一个轻量级客户端 - 独立的 Flutter 应用程序(应用商店中的 .apk.ipa 包)或作为其他应用的一部分的 Flutter 小部件 - 传送到移动设备:

一旦 SDUI 体验准备就绪,我们将开始开发 独立移动包

路线图

为了在移动平台上提供最佳的 Flet 应用体验,我们计划在今年年底前发布以下项目:

用于 Flutter 的 Flet 小部件

我们的第一步是将 Flet 客户端分离为一个 Flutter 小部件,并在 https://pub.dev 上发布该包。然后,移动开发人员可以将 Flet 小部件集成到现有或新的 Flutter 应用中,以为核心应用功能添加动态服务驱动的 UI 体验。也可以创建一个新的 Flutter 应用,仅使用一个 Flet 小部件来托管完整的 Flet 应用。

开发者将按照 Flutter 指南将他们的应用打包、签名和分发到 AndroidiOSLinuxmacOSWindows 平台。

Flet 团队将提供示例 CI 流水线以自动化 Flutter 应用的打包、签名和发布。

用于 iOS 和 Android 的 Flet Studio

下一步是在 App Store 和 Google Play 上发布一个独立“Flet Studio”应用(名称未最终确定),用于“测试使用 Flet 框架开发的移动体验”。开发人员或测试人员将能够在 Flet Studio 中“注册”他们托管的 Flet 应用的 URL,并立即查看其在移动设备上的性能表现。

白标 Flet 移动应用

我们将提供指南和 CI 流水线,自动将白标 Flet 应用发布到用户的 App Store 或 Google Play 账户。该应用将“固定”到特定的应用 URL,并可以额外捆绑应用资产(媒体、字体),以最小化网络使用。

Flet应用的独立移动包

我们将研究并开发一个将 Flet 框架、用户程序、语言运行时和所有依赖项打包到一起的独立移动包(.apk.ipa 包)的,以便 Flet 程序不需要 Web 服务器。

嵌入 Flet 到原生应用

我们将提供指南、示例应用和 CI 流水线,以使用 Flutter Add-to-App 功能将 Flet 小部件集成到现有的原生 Android 和 iOS 应用(不是用 Flutter 开发的)中。Put Flutter to work 文章提供了一个关于如何将 Flutter 集成到现有移动应用中的实际例子。

这是当前的计划。

与此同时,试试 Flet,并 告诉我们 你的想法!