为现有 Flutter 包创建 Flet 扩展
引言
虽然 Flet 控件利用了许多内置的 Flutter 小部件,能够创建复杂的应用程序,但并非所有的 Flutter 小部件或第三方包都能直接得到 Flet 团队的支持或包含在核心 Flet 框架中。
为了解决这个问题,Flet 框架提供了一种可扩展性机制。这允许你将自己的自定义 Flutter 包或第三方库中的小部件和 API 直接整合到你的 Flet 应用程序中。
在本指南中,你将学习如何从模板创建 Flet 扩展,然后对其进行自定义,以将第三方 Flutter 包整合到你的 Flet 应用程序中,或者与社区分享。
先决条件
要将自定义 Flutter 包整合到 Flet 中,你需要对如何用 Dart 语言创建 Flutter 应用程序和包有基本的了解,并配置好 Flutter 开发环境。有关 Flutter 和 Dart 的更多信息,请参阅Flutter 入门。
从模板创建 Flet 扩展
现在,Flet 使得基于 Flutter 小部件或 Flutter 第三方包创建和构建具有自定义控件的项目变得容易。在下面的例子中,我们将基于flutter_spinkit包创建一个自定义的 Flet 扩展。
-
创建新的虚拟环境并在那里安装 Flet。
-
从模板创建新的 Flet 扩展项目:
flet create --template extension --project-name flet-spinkit
将创建一个带有新的 FletSpinkit 控件的项目。这个控件只是一个带有文本属性的 Flutter Text 小部件,我们稍后将对其进行自定义。
- 构建你的应用程序。
从扩展模板创建的 Flet 项目在examples/flet_spinkit_example
文件夹中有示例应用程序。
当在你的应用程序的pyproject.toml
所在的文件夹(examples/flet_spinkit_example
)中时,运行flet build
命令,例如,对于 macOS:
flet build macos -v
打开应 用程序并看到新的自定义 Flet 控件:
open build/macos/flet-spinkit-example.app

- 更改你的应用程序。
一旦项目为桌面构建一次,你就可以对你的 Python 文件进行更改并运行它而无需重新构建。
首先,如果你没有使用 uv,请从 pyproject.toml 安装依赖项:
pip install.
或者
poetry install
现在你可以对你的示例应用程序 main.py 进行更改:
import flet as ft
from flet_spinkit import FletSpinkit
def main(page: ft.Page):
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.add(
ft.Container(
height=150,
width=300,
alignment=ft.alignment.center,
bgcolor=ft.Colors.PINK_200,
content=FletSpinkit(
tooltip="My new PINK FletSpinkit Control tooltip",
value="My new PINK FletSpinkit Flet Control",
),
),
)
ft.app(main)
并运行:
flet run

- 重新构建你的应用程序。
当你对你的 Flutter 包进行任何更改时,你需要重新构建:
flet build macos -v
如果你需要调试,请运行此命令:
build/macos/flet-spinkit-example.app/Contents/MacOS/flet-spinkit-example --debug
整合第三方 Flutter 包
让我们将flutter_spinkit包整合到我们的 Flet 应用程序中。
- 添加依赖项。
转到src/flutter/flet_spinkit
文件夹并运行此命令将对flutter_spinkit
的依赖项添加到pubspec.yaml
中:
flutter pub add flutter_spinkit
在此处阅读有关使用 Flutter 包的更多信息这里。
- 修改
dart
文件。
在src/flutter/flet_spinkit/lib/src/flet_spinkit.dart
文件中,添加导入语句并将 Text 小部件替换为SpinKitRotatingCircle
小部件:
import 'package:flet/flet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
class FletSpinkitControl extends StatelessWidget {
final Control? parent;
final Control control;
const FletSpinkitControl({
super.key,
required this.parent,
required this.control,
});
@override
Widget build(BuildContext context) {
Widget myControl = SpinKitRotatingCircle(
color: Colors.red,
size: 100.0,
);
return constrainedControl(context, myControl, parent, control);
}
}
- 重新构建你的示例应用程序。
转到examples/flet_spinkit_example
,清除缓存并重新构建你的应用程序:
flet build macos -v
- 运行你的应用程序:

Flet 扩展结构
从扩展模板创建新的 Flet 项目后,你将看到以下文件夹结构:
├── LICENSE
├── mkdocs.yml
├── README.md
├── docs
│ └── index.md
│ └── FletSpinkit.md
├── examples
│ └── flet_spinkit_example
│ ├── README.md
│ ├── pyproject.toml
│ └── src
│ └── main.py
├── pyproject.toml
└── src
├── flet_spinkit
│ ├── __init__.py
│ └── flet_spinkit.py
└── flutter
└── flet_spinkit
├── CHANGELOG.md
├── LICENSE
├── README.md
├── lib
│ ├── flet_spinkit.dart
│ └── src
│ ├── create_control.dart
│ └── flet_spinkit.dart
└── pubspec.yaml
Flet 扩展由以下部分组成:
- 包,位于
src
文件夹中 - 示例应用程序,位于
examples/flet-spinkit_example
文件夹中 - 文档,位于
docs
文件夹中
包
包是将在你的应用程序中使用的组件。它由两部分组成:Python 和 Flutter。
Python
flet_spinkit.py
在这里,你创建 Flet Python 控件——一个在你的 Flet 应用程序中使用的 Python 类。
这个类的最低要求是它必须继承自 Flet 的Control
,并且必须有一个_get_control_name
方法,该方法将返回控件名称。这个名称应该与我们在create_control.dart
文件中检查的args.control.type
相同。
Flutter
pubspec.yaml
一个包含指定包依赖关系的元数据的 yaml 文件。
从模板创建时已经有一个对flet
的依赖项。你需要在那里添加对你正在创建扩展的 Flutter 包的依赖项。
flet_spinkit.dart
导出了两个方法:
createControl
——用于创建与 Python 端的控件相对应的小部件。ensureInitialized
——在 Flet 程序启动时调用一次。
src/create_control.dart
基于 Control 的_get_control_name()
函数返回的控件名称创建 Flutter 小部件。这个机制遍历所有的第三方包并返回第一个匹配的小部件。
src/flet_spinkit.dart
在这里,你创建 Flutter“包装器”小部件,它将构建你想在你的 Flet 应用程序中使用的 Flutter 小部件或 API。
包装器小部件将 Python 控件的状态传递给将在页面上显示的 Flutter 小部件,并提供一个 API,将来自 Flutter 小部件的事件路由回 Python 控件。
示例应用程序
src/main.py
使用 Flet Python 控件的 Python 程序。
pyproject.toml
在这里,你指定对你的包的依赖项,它可以是:
- 路径依赖
你的 Flet 扩展文件夹的绝对路径,例如:
dependencies = [
"flet-spinkit @ file:///Users/user-name/projects/flet-spinkit",
"flet>=0.26.0",
]
- Git 依赖
指向 Git 仓库的链接,例如:
dependencies = [
"flet-ads @ git+https://github.com/flet-dev/flet-ads.git",
"flet>=0.26.0",
]
- PyPi 依赖
发布在 pypi.org 上的包的名称,例如:
dependencies = [
"flet-ads",
"flet>=0.26.0",
]
文档
如果你计划与社区分享你的扩展,你可以使用mkdocs轻松地从你的源代码生成文档。
Flet 扩展带有包含你的文档初始文件和mkdocs.yml
的docs
文件夹。
运行以下命令以查看你的文档在本地的外观:
mkdocs serve
在你的浏览器中打开 http://127.0.0.1:8000 :

一旦你的文档准备就绪,如果你的包托管在 GitHub 上,你可以运行以下命令将你的文档托管在 GitHub 页面上:
mkdocs gh-deploy
你可能会发现这个指南对开始使用 mkdocs 很有帮助。
自定义属性
在上面的例子中,Spinkit 控件创建了一个硬编码的 Flutter 小部件。现在让我们自定义它的属性。
通用属性
一般来说,Flet 中有两种类型的控件:
-
可视化控件,添加到应用程序/页面表面,如 FletSpinkit。
-
非可视化控件,可以是:
-
弹出控件(对话框、选择器、面板等)。
-
添加到
overlay
的服务,如视频或音频。
-
Flet 的Control
类具有所有控件共有的属性,如visible
、opacity
和tooltip
等。
Flet 的ConstrainedControl
类继承自Control
,并具有许多其他属性,如在 Stack 中的位置的top
和left
以及一堆动画属性。
当创建非可视化控件时,你的 Python 控件应该继承自'Control。然后,为了能够在你的应用程序中使用Control
属性,你需要将它们添加到你的 Python 控件的构造函数中。在它的 Dart 对应部分(src/flet_spinkit.dart
)中,使用baseControl()
来包装你的 Flutter 小部件。
当创建可视化控件时,你的 Python 控件应该继承自ConstrainedControl
。在它的 Dart 对应部分(src/flet_spinkit.dart
)中,使用constrainedControl()
来包装你的 Flutter 小部件。
然后,为了能够在你的应用程序中使用Control
和ConstrainedControl
属性,你需要将它们添加到你的 Python 控件的构造函数中。
在此处查看通用 Control 属性的参考这里。
如果你从 Flet 扩展模板创建了你的扩展项目,你的 Python 控件已经继承自ConstrainedControl
,你可以在你的示例应用程序中使用它的属性:
import flet as ft
from flet_spinkit import FletSpinkit
def main(page: ft.Page):
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.add(
ft.Stack(
[
ft.Container(height=200, width=200, bgcolor=ft.Colors.BLUE_100),
FletSpinkit(opacity=0.5, tooltip="Spinkit tooltip", top=0, left=0),
]
)
)
ft.app(main)

特定于控件的属性
现在你已经充分利用了 Flet 的Control
和ConstrainedControl
提供的属性,让我们定义特定于你正在构建的新控件的属性。
在 FletSpinkit 示例中,让我们定义它的color
和size
。
在 Python 类中,定义新的color
和size
属性:
from enum import Enum
from typing import Any, Optional
from flet.core.constrained_control import ConstrainedControl
from flet.core.control import OptionalNumber
from flet.core.types import ColorEnums, ColorValue
class FletSpinkit(ConstrainedControl):
FletSpinkit 控件。
def __init__(
self,
#
# Control
#
opacity: OptionalNumber = None,
tooltip: Optional[str] = None,
visible: Optional[bool] = None,
data: Any = None,
#
# ConstrainedControl
#
left: OptionalNumber = None,
top: OptionalNumber = None,
right: OptionalNumber = None,
bottom: OptionalNumber = None,
#
# FletSpinkit 特定
#
color: Optional[ColorValue] = None,
size: OptionalNumber = None,
):
ConstrainedControl.__init__(
self,
tooltip=tooltip,
opacity=opacity,
visible=visible,
data=data,
left=left,
top=top,
right=right,
bottom=bottom,
)
self.color = color
self.size = size
def _get_control_name(self):
return "flet_spinkit"
# color
@property
def color(self) -> Optional[ColorValue]:
return self.__color
@color.setter
def color(self, value: Optional[ColorValue]):
self.__color = value
self._set_enum_attr("color", value, ColorEnums)
# size
@property
def size(self):
return self._get_attr("size")
@size.setter
def size(self, value):
self._set_attr("size", value)
在src/flet_spinkit.dart
文件中,使用辅助方法attrColor
和attrDouble
来访问颜色和大小值:
import 'package:flet/flet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
class FletSpinkitControl extends StatelessWidget {
final Control? parent;
final Control control;
const FletSpinkitControl({
super.key,
required this.parent,
required this.control,
});
@override
Widget build(BuildContext context) {
var color = control.attrColor("color", context);
var size = control.attrDouble("size");
Widget myControl = SpinKitRotatingCircle(
color: color,
size: size?? 100,
);
return constrainedControl(context, myControl, parent, control);
}
}
在你的应用程序中使用color
和size
属性:
import flet as ft
from flet_spinkit import FletSpinkit
def main(page: ft.Page):
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.add(
ft.Stack(
[
ft.Container(height=200, width=200, bgcolor=ft.Colors.BLUE_100),
FletSpinkit(
opacity=0.5,
tooltip="Spinkit tooltip",
top=0,
left=0,
color=ft.Colors.YELLOW,
size=150,
),
]
)
)
ft.app(main)
重新构建并运行:

你可以在此处找到这个例子的源代码here.
不同类型的属性和事件示例
枚举属性
例如,AppBar
的clip_behaviour
。
在 Python:
# clip_behavior
@property
def clip_behavior(self) -> Optional[ClipBehavior]:
return self._get_attr("clipBehavior")
@clip_behavior.setter
def clip_behavior(self, value: Optional[ClipBehavior]):
self._set_attr(
"clipBehavior",
value.value if isinstance(value, ClipBehavior) else value,
)
在 Dart:
var clipBehavior = Clip.values.firstWhere(
(e) =>
e.name.toLowerCase() ==
widget.control.attrString("clipBehavior", "")!.toLowerCase(),
orElse: () => Clip.none);
JSON 属性
例如,Card
的shape
属性。
在 Python:
def before_update(self):
super().before_update()
self._set_attr_json("shape", self.__shape)
# shape
@property
def shape(self) -> Optional[OutlinedBorder]:
return self.__shape
@shape.setter
def shape(self, value: Optional[OutlinedBorder]):
self.__shape = value
在 Dart:
var shape = parseOutlinedBorder(control, "shape")
子元素
例如,AlertDialog
的content
。
在 Python:
def _get_children(self):
children = []
if self.__content:
self.__content._set_attr_internal("n", "content")
children.append(self.__content)
return children
在 Dart:
var contentCtrls =
widget.children.where((c) => c.name == "content" && c.isVisible);
事件
例如,ElevatedButton
的on_click
事件。
在 Python:
# on_click
@property
def on_click(self):
return self._get_event_handler("click")
@on_click.setter
def on_click(self, handler):
self._add_event_handler("click", handler)
在 Dart:
Function()? onPressed =!disabled
? () {
debugPrint("Button ${widget.control.id} clicked!");
if (url!= "") {
openWebBrowser(url,
webWindowName: widget.control.attrString("urlTarget"));
}
widget.backend.triggerControlEvent(widget.control.id, "click");
}
: null;
示例
Flet 有一些作为内置扩展实现的控件,可以作为你自己的控件的起点。