跳到主要内容

文件选择器和上传

· 阅读需 7 分钟
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)

如果upload_dir中不存在/{username}/pictures目录,Flet会自动创建它。:::

上传存储

请注意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,并告诉我们您的想法!

动画乐趣

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

尽管 Flet 的发布版本早就推出了动画支持,但我们刚刚完成了对其新功能的文件化!我们都知道,如果功能没有被文档化,那就等于不存在!😉

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

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

演示时间

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

隐式动画

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

此外,现在可以对全部 Container 控件属性 进行动画处理,并且添加了一个新的 AnimatedSwitcher 控件,用于在旧内容和新内容之间进行动画过渡。

其他新功能

Markdown 控件

允许以 Markdown 格式呈现文本。支持各种扩展:CommonMarkGitHub WebGitHub Flavored

请查看 Markdown 控件文档 以获取更多信息和示例。

URL Launcher

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

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

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

快捷键

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

请查看此 简单使用示例

辅助功能改进

我们在文档中添加了 辅助功能 部分,涵盖了屏幕阅读器的语义支持。

ShaderMark 控件

一个应用Shader生成的遮罩到其内容上的控件。可以创建出漂亮的效果,例如 逐渐淡出图像

就是这些了!

希望你喜欢使用 Flet!如果你有任何问题,请随时在我们的 Discord 服务器上联系我们。

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

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

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

  • 容器中的渐变背景
  • 对按钮、文本框和下拉框控件进行了全面的样式设置
  • ...等等

渐变背景

线性渐变

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)

请查看Container.gradient文档,以获取有关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", # yellow sun
"0xFF0099FF", # blue sky
],
stops=[0.4, 1.0],
),
width=150,
height=150,
border_radius=5,
)
)

ft.app(target=main)

请查看Container.gradient文档,以获取有关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)

请查看Container.gradient文档,了解有关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.MaterialState.HOVERED: ft.colors.WHITE,
ft.MaterialState.FOCUSED: ft.colors.BLUE,
ft.MaterialState.DEFAULT: ft.colors.BLACK,
},
bgcolor={ft.MaterialState.FOCUSED: ft.colors.PINK_200, "": ft.colors.YELLOW},
padding={ft.MaterialState.HOVERED: 20},
overlay_color=ft.colors.TRANSPARENT,
elevation={"pressed": 0, "": 1},
animation_duration=500,
side={
ft.MaterialState.DEFAULT: BorderSide(1, ft.colors.BLUE),
ft.MaterialState.HOVERED: BorderSide(2, ft.colors.BLUE),
},
shape={
ft.MaterialState.HOVERED: RoundedRectangleBorder(radius=20),
ft.MaterialState.DEFAULT: RoundedRectangleBorder(radius=2),
},
),
)
)

ft.app(target=main)

ft.MaterialState.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类及其属性的完整描述。

文本字段和下拉菜单的样式设置

现在可以为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)吗?greetings是一个Row还是一个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.来访问ref的控件,但这只是个人偏好的问题 :)

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

Flet移动战略

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

Flet项目最近受到了很多关注,我们要感谢所有尝试使用Flet并在社区中传播有关它的开发人员!您的支持激励我们以更快的速度推进Flet项目!

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

在本文中,我想分享一下我们对Flet移动化的愿景并提供一个路线图。

服务器驱动的UI

Flet是一个服务器驱动的UI(SDUI)框架。SDUI是一种新兴的技术,最好在Technology Radar post中进行描述:

服务器驱动的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,并告诉我们您的想法!