跳到主要内容

控件引用

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

导航与路由

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

Flet 0.1.42 已发布,带来了导航和路由功能!

导航和路由是单页应用程序(SPA)的一个重要功能,它允许将应用程序用户界面组织成虚拟页面(视图),并在它们之间“导航”,同时应用程序的URL反映了当前的应用状态。

对于移动应用,导航和路由还可以作为深层链接到特定的应用部分。

为了在 Flet 中添加导航和路由,我们付出了更多努力 ,因为实现是基于Navigator 2.0 Flutter API的,并需要用“页面和视图”取代 Flet 的“页面”抽象。Flutter 的新导航和路由 API 有以下显著改进:

  1. 对历史堆栈的编程控制。
  2. 一种简便的方法来拦截 AppBar 中的“后退”按钮调用。
  3. 与浏览器历史的强同步。

浏览示例源代码

页面路由

页面路由是应用程序URL中 # 符号之后的部分:

如果用户未在应用程序URL中设置默认应用程序路由,则默认为 /。所有路由都以 / 开头,例如 /store, /authors/1/books/2

可以通过读取 page.route 属性获取应用程序路由,例如:

import flet as ft

def main(page: ft.Page):
page.add(ft.Text(f"Initial route: {page.route}"))

ft.app(target=main, view=ft.AppView.WEB_BROWSER)

获取应用程序URL,打开一个新的浏览器标签页,粘贴URL,修改 # 后的部分为 /test,然后按回车。你应该会看到“Initial route: /test”。

每次URL中的路由发生变化(通过编辑URL或使用浏览器历史中的后退/前进按钮),Flet 都会调用 page.on_route_change 事件处理程序:

import flet as ft

def main(page: ft.Page):
page.add(ft.Text(f"Initial route: {page.route}"))

def route_change(route):
page.add(ft.Text(f"New route: {route}"))

page.on_route_change = route_change
page.update()

ft.app(target=main, view=ft.AppView.WEB_BROWSER)

现在尝试多次更新URL哈希,然后使用后退/前进按钮!每次路由变化时,你应该会看到页面上添加了一条新消息:

可以通过更新 page.route 属性以编程方式更改路由:

import flet as ft

def main(page: ft.Page):
page.add(ft.Text(f"Initial route: {page.route}"))

def route_change(route):
page.add(ft.Text(f"New route: {route}"))

def go_store(e):
page.route = "/store"
page.update()

page.on_route_change = route_change
page.add(ft.ElevatedButton("Go to Store", on_click=go_store))

ft.app(target=main, view=ft.AppView.WEB_BROWSER)

点击“Go to Store”按钮,你会看到应用程序URL已更改,并且在浏览器历史中添加了一个新项。你可以使用浏览器“后退”按钮导航到上一个路由。

页面视图

Flet 的 Page 现在不仅仅是一个单独的页面,而是一个包含叠加在一起的 View 的容器,像三明治一起:

视图的集合代表了导航器历史。Page具有 page.views 属性来访问视图集合。

列表中的最后一个视图是当前显示在页面上的视图。视图列表必须至少有一个元素(根视图)。

要模拟页面之间的过渡,请更改 page.route 并在 page.view 列表的末尾添加一个新 View

从集合中弹出最后一个视图并在 page.on_view_pop 事件处理程序中将路由更改为“上一个”路由以返回。

在路由变化时构建视图

为了构建可靠的导航,程序中必须有一个地方根据当前路由来构建视图列表。换句话说,导航历史堆栈(由视图列表表示)必须是路由的函数。

这个地方是 page.on_route_change 事件处理程序。

让我们将所有内容放在一起,完成一个允许在两个页面之间导航的完整示例:

import flet as ft

def main(page: ft.Page):
page.title = "Routes Example"

def route_change(route):
page.views.clear()
page.views.append(
ft.View(
"/",
[
ft.AppBar(title=ft.Text("Flet app"), bgcolor=ft.colors.SURFACE_VARIANT),
ft.ElevatedButton("Visit Store", on_click=lambda _: page.go("/store")),
],
)
)
if page.route == "/store":
page.views.append(
ft.View(
"/store",
[
ft.AppBar(title=ft.Text("Store"), bgcolor=ft.colors.SURFACE_VARIANT),
ft.ElevatedButton("Go Home", on_click=lambda _: page.go("/")),
],
)
)
page.update()

def view_pop(view):
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(page.route)


ft.app(target=main, view=ft.AppView.WEB_BROWSER)

试试使用“Visit Store”和“Go Home”按钮在页面之间导航,使用浏览器的后退/前进按钮,手动更改URL中的路由——无论怎么做都能正常工作! :)

备注

要在页面之间“导航”,我们使用了 page.go(route) - 一个辅助方法,它更新 page.route,调用 page.on_route_change 事件处理程序来更新视图,最后调用 page.update()

注意 page.on_view_pop 事件处理程序的使用。当用户点击 AppBar 控件中的自动“后退”按钮时,它会触发。在处理程序中,我们从视图集合中删除最后一个元素,并导航到其下的视图根。

路由模板

Flet 提供 TemplateRoute——一个基于 repath 库的实用类,它允许匹配类似于 ExpressJS 的路由并解析其参数,例如 /account/:account_id/orders/:order_id

TemplateRoute 与路由更改事件非常搭配:

troute = TemplateRoute(page.route)

if troute.match("/books/:id"):
print("Book view ID:", troute.id)
elif troute.match("/account/:account_id/orders/:order_id"):
print("Account:", troute.account_id, "Order:", troute.order_id)
else:
print("Unknown route")

你可以在这里阅读更多关于 repath 库支持的模板语法。

今天就到这里!

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

新版本发布:拖放、绝对定位和可点击容器

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

我们刚刚发布了 Flet 0.1.41,其中包含拖放支持以及其他一些不错的功能,如堆栈中控件的绝对定位和可点击容器!

拖放

在 Flet 中实现拖放非常愉快——这要归功于 Flutter 中智能的拖放实现!您只需使用 "draggable" 控件将其拖到 "drag target" 上,当拖动项被放置时,会调用 on_accept 事件处理程序。

查看 拖放示例

探索 DraggableDragTarget 控件,它们的属性和事件。

堆栈中的绝对定位

现在所有可见控件都有 lefttoprightbottom 属性,可以让它们在 Stack 内进行绝对定位,例如:

import flet as ft

def main(page: ft.Page):

page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.vertical_alignment = ft.MainAxisAlignment.CENTER

page.add(
ft.Container(
ft.Stack(
[
ft.Text("1", color=ft.colors.WHITE),
ft.Text("2", color=ft.colors.WHITE, right=0),
ft.Text("3", color=ft.colors.WHITE, right=0, bottom=0),
ft.Text("4", color=ft.colors.WHITE, left=0, bottom=0),
ft.Text("5", color=ft.colors.WHITE, left=40, top=35),
]
),
border_radius=8,
padding=5,
width=100,
height=100,
bgcolor=ft.colors.BROWN_700,
)
)

ft.app(target=main)

可点击容器

Container 控件获得了 on_click 事件,使您可以将任何控件变成按钮,并在 ink 设置为 True 时具有漂亮的材质波纹效果!

查看上述示例的 源代码

试用 Flet告诉我们 您的想法!

在 Flet 应用中使用自定义字体

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

现在,您可以在 Flet 应用中使用自己的字体了!

支持以下字体格式:

  • .ttc
  • .ttf
  • .otf

使用 page.fonts 属性导入字体。

page.fonts 属性设置为一个字典,其中键是字体系列名称,用于引用该字体,值是要导入的字体文件的 URL:

def main(page: ft.Page):
page.fonts = {
"Kanit": "https://raw.githubusercontent.com/google/fonts/master/ofl/kanit/Kanit-Bold.ttf",
"Aleo Bold Italic": "https://raw.githubusercontent.com/google/fonts/master/ofl/aleo/Aleo-BoldItalic.ttf"
}
page.update()

# ...

字体可以通过提供绝对 URL 从外部资源导入,或者通过提供相对 URL 和 assets_dir 从应用程序资源中导入。

flet.app() 调用中指定 assets_dir,以设置应该提供给应用程序的资产位置。assets_dir 可以是相对于您的 main.py 目录的路径,也可以是绝对路径。例如,考虑以下程序结构:

/assets
/fonts
/OpenSans-Regular.ttf
main.py

代码示例

以下程序从 GitHub 加载 "Kanit" 字体,并从资源中加载 "Open Sans"字体。 "Kanit" 被设置为默认应用字体,而 "Open Sans" 用于特定的文本控件:

import flet as ft

def main(page: ft.Page):
page.title = "Custom fonts"

page.fonts = {
"Kanit": "https://raw.githubusercontent.com/google/fonts/master/ofl/kanit/Kanit-Bold.ttf",
"Open Sans": "fonts/OpenSans-Regular.ttf",
}

page.theme = ft.Theme(font_family="Kanit")

page.add(
ft.Text("This is rendered with Kanit font"),
ft.Text("This is Open Sans font example", font_family="Open Sans"),
)

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

静态字体与可变字体

目前仅支持静态字体,即仅包含一种特定宽度/粗细/样式组合的字体,例如 "Open Sans Regular" 或 "Roboto Bold Italic"。

可变字体支持仍在开发中

但是,如果您需要在应用中使用可变字体,可以使用 fonttools 创建特定权重的静态“实例化”,然后使用这些实例:

fonttools varLib.mutator ./YourVariableFont-VF.ttf wght=140 wdth=85

要探索可用的字体特性(例如 wght 的可能选项),可以使用 Wakamai Fondue 在线工具。

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