跳到主要内容

手势检测器

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

我们刚刚发布了支持手势处理的 Flet 0.1.62版本!

我们刚刚发布了Flet 0.1.62,新增了手势处理的支持!

新增了一个控件 - GestureDetector,它允许处理各种手势:单击和双击,包括左(主要)和右(次要)鼠标(指针)按钮,垂直、水平和双向拖动,缩放(捏合和放大)手势以及悬停事件。现在,通过将其包装到 GestureDetector 中,您可以使任何 Flet 控件可“点击”和“拖动”!

以下是一个简单的示例应用,允许您在 Stack 内拖动容器:

import flet as ft

def main(page: ft.Page):
def on_pan_update(e: ft.DragUpdateEvent):
e.control.top = max(0, e.control.top + e.delta_y)
e.control.left = max(0, e.control.left + e.delta_x)
e.control.update()

gd = ft.GestureDetector(
mouse_cursor=ft.MouseCursor.MOVE,
on_vertical_drag_update=on_pan_update,
left=100,
top=100,
content=ft.Container(bgcolor=ft.colors.BLUE, width=50, height=50, border_radius=5),
)

page.add( ft.Stack([gd], expand=True))

ft.app(target=main)

手势检测器是 Flet 原语集合中的又一个伟大补充,它使您可以构建仅受您想象力限制的应用程序。本月晚些时候将推出2D绘图功能,将完善该组合!

此次发布不仅涉及手势 - 这也是一个“稳定性”发布。我们修复了一些错误,并添加了一些其他小功能,您可以在这里查看

升级到最新版本的 Flet 模块 (pip install flet --upgrade),在您的应用程序中集成身份验证,然后告诉我们您的想法!

享受吧!

用户认证

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

Flet 现在支持用户认证了!🎉

现在,您可以在 Flet 应用中使用第三方身份提供商(如 GitHub、Google、Azure、Auth0、LinkedIn 等)实现用户认证(“使用 X 登录”按钮):

这个发布版本不仅仅是关于认证,还新增了很多相关功能和小的改进:

认证

Flet 认证功能:

  • 适用于 Flet 桌面、Web 和移动应用。
  • 在一个应用中使用多个认证提供商。
  • 内置 OAuth 提供商,自动获取用户详细信息:
    • GitHub
    • Azure
    • Google
    • Auth0
  • 可选的群组获取。
  • 自动刷新令牌。
  • 使用已保存的令牌登录(“记住我”)。
  • 自定义 OAuth 提供商。

一个简单示例,展示如何在 Flet 应用中添加“使用 GitHub 登录”按钮:

import os

import flet as ft
from flet.auth.providers.github_oauth_provider import GitHubOAuthProvider

def main(page: ft.Page):

provider = GitHubOAuthProvider(
client_id=os.getenv("GITHUB_CLIENT_ID"),
client_secret=os.getenv("GITHUB_CLIENT_SECRET"),
redirect_url="http://localhost:8550/api/oauth/redirect",
)

def login_click(e):
page.login(provider)

def on_login(e):
print("访问令牌:", page.auth.token.access_token)
print("用户 ID:", page.auth.user.id)

page.on_login = on_login
page.add(ft.ElevatedButton("使用 GitHub 登录", on_click=login_click))

ft.app(target=main, port=8550, view=ft.AppView.WEB_BROWSER)
备注

在运行应用之前,请在命令行中设置密钥环境变量:

$ export GITHUB_CLIENT_ID="<client_id>"
$ export GITHUB_CLIENT_SECRET="<client_secret>"

阅读认证指南获取更多信息和示例

客户端存储

Flet 的客户端存储 API 允许在客户端的持久存储中存储键值数据。Flet 实现使用了 shared_preferences Flutter 包。

将数据写入存储:

page.client_storage.set("key", "value")

读取数据:

value = page.client_storage.get("key")

阅读客户端存储指南获取更多信息和示例

会话存储

Flet 引入了在服务器端用户会话中存储键值数据的 API。

将数据写入会话:

page.session.set("key", "value")

读取数据:

value = page.session.get("key")

阅读会话存储指南获取更多信息和示例

加密 API

在此版本中,Flet 引入了使用对称算法(使用相同密钥进行加密和解密)加密和解密敏感文本数据的实用方法。它使用 cryptography 包中的 Fernet 实现,Fernet 是 AES 128 的增强版本,加上 PBKDF2 从用户密码短语派生加密密钥。

加密数据:

from flet.security import encrypt, decrypt
secret_key = "S3CreT!"
plain_text = "这是一条秘密消息!"
encrypted_data = encrypt(plain_text, secret_key)

解密数据:

from flet.security import encrypt, decrypt
secret_key = "S3CreT!"
plain_text = decrypt(encrypted_data, secret_key)
print(plain_text)

继续阅读获取更多信息和示例

其他改进

import flet as ft
def main(page: ft.Page):
page.window_bgcolor = ft.colors.TRANSPARENT
page.bgcolor=ft.colors.TRANSPARENT
page.window_title_bar_hidden = True
page.window_frameless = True
page.window_left = 400
page.window_top = 400
page.add(ft.ElevatedButton("我是一颗浮动的按钮!"))
ft.app(target=main)
  • page.get_clipboard()
  • page.launch_url() - 通过额外参数更好地控制:
    • web_window_name - 打开 URL 的窗口选项卡/名称:_self - 当前选项卡,_blank - 新选项卡,或 <your name> - 指定名称的选项卡。
    • web_popup_window - 设置为 True 在浏览器弹出窗口中显示 URL。默认值为 False
    • window_width - 可选,弹出窗口宽度。
    • window_height - 可选,弹出窗口高度。
  • page.window_to_front()
  • page.close_in_app_web_view()

升级 Flet 模块到最新版本(pip install flet --upgrade),将认证集成到您的应用中并 告诉我们 您的想法!

享受吧!

文件选择器和上传

· 阅读需 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告诉我们 你的想法!