将现有 Flutter 包集成到您的 Flet 应用中
本指 南正在更新。
介绍
虽然 Flet 控件利用了许多内置的 Flutter 小部件,甚至能够创建复杂的应用程序,但并非所有的 Flutter 小部件或第三方包都能直接得到 Flet 团队的支持或包含在核心 Flet 框架中。
为了解决这个问题,Flet 框架提供了一个可扩展性机制。这允许您将自己的自定义 Flutter 包或第三方库中的小部件和 API 直接集成到您的 Flet 应用程序中。
先决条件
要将自定义 Flutter 包集成到 Flet 中,您需要对如何用 Dart 语言创建 Flutter 应用和包有基本的了解,并配置好 Flutter 开发环境。有关 Flutter 和 Dart 的更多信息,请查看Flutter 入门。
创建 Flet 扩展
集成第三方 Flutter 包的 Flet 扩展包括以下部分:
-
Flet Dart 包。
-
Flet Python 控件。
Flet Dart 包包括一个根据 Control 的_get_control_name()
函数返回的控件名称创建 Flutter 小部件的机 制。这个机制遍历所有第三方包并返回第一个匹配的小部件。
Flet Python 控件是您将在 Flet 程序中使用的 Python 类。
例如,看一下flutter_spinkit包的基本Flet 扩展。
Flet Dart 包
要创建一个新的Dart 包,运行以下命令:
flutter create --template=package <package_name>
您将看到这个文件夹结构:
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── lib
│ └── <package_name>.dart
├── pubspec.lock
├── pubspec.yaml
├── test
│ └── <package_name>_test.dart
└── <package_name>.iml
在lib
文件夹中,您需要创建src
文件夹,其中包含两个文件:create_control.dart
和<control_name>.dart
:
└── <package_name>
├── lib
│ ├── <package_name>.dart
│ └── src
│ ├── create_control.dart
│ └── <control_name>.dart
└── pubspec.yaml
pubspec.yaml
一个包含指定包的元数据的 yaml 文件。
在您的pubspec.yaml
中,您应该添加对flet
和您正在为其创建扩展的 Flutter 包的依赖。
在 Flet Spinkit 示例中,pubspec.yaml
包含对flutter_spinkit
的依赖:
dependencies:
flet: ^0.22.0
flutter_spinkit: ^5.2.1
<package_name>.dart
扩展包应该导出两个方法:
createControl
- 被调用以创建与 Python 端的控件相对应的小部件。ensureInitialized
- 在 Flet 程序启动时调用一次。
library <package_name>;
export "../src/create_control.dart" show createControl, ensureInitialized;
create_control.dart
Flet 对所有控件调用createControl
并返回第一个匹配的小部件。
import 'package:flet/flet.dart';
import 'pinkit.dart';
CreateControlFactory createControl = (CreateControlArgs args) {
switch (args.control.type) {
case "spinkit":
return SpinkitControl(
parent: args.parent,
control: args.control,
);
default:
return null;
}
};
void ensureInitialized() {
// 无需初始化
}
<control-name>.dart
在这里,您创建 Flutter“包装器”小部件,该小部件将构建您希望在 Flet 应用中使用的 Flutter 小部件或 API。
包装器小部件将 Python 控件的状态传递给将在页面上显示的 Flutter 小部件,并提供一个 API,将事件从 Flutter 小部件路由回 Python 控件。
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
class SpinkitControl extends StatelessWidget {
const SpinkitControl({
super.key,
});
@override
Widget build(BuildContext context) {
return const SpinKitRotatingCircle(
color: Colors.red,
size: 100.0,
);
}
}
作为概念验证,我们希望在我们的 Flet 程序中看到硬编码的SpinKitRotatingCircle
,稍后我们将对其属性进行自定义。
Flet Python 控件
Flet Python 控件是您可以在 Flet 应用程序中创建或作为外部 Python 模块创建的 Python 类。在 Flet Spinkit 示例中,我们在/controls/spinkit.py
文件中创建了 Spinkit 类:
from flet_core.control import Control
class Spinkit(Control):
"""
Spinkit 控件。
"""
def __init__(self):
Control.__init__(self)
def _get_control_name(self):
return "spinkit"
这个类的最低要求是它必须继承自 Flet 的Control
,并且必须具有_get_control_name
方法,该方法将返回控件名称。这个名称应该与我们在create_control.dart
文件中检查的args.control.type
相同。
连接您的 Python 应用和 Dart 包
一旦您创建了 Flet Dart 包和 Flet Python 控件,在main.py
中创建一个使用它的 Python 程序:
import flet as ft
from controls.spinkit import Spinkit
def main(page: ft.Page):
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.add(Spinkit())
ft.app(main)
让我们使用flet run
命令运行这个简单的应用程序。我们期望在页面上看到硬编码的SpinKitRotatingCircle
,但这还没有发生。相反,我们看到了这条消息代替了 Spinkit 控件:
我们的 Flet 应用还不知道我们创建的新 Flet Dart 包。
要连接您的 Python 应用和新的 Flet Dart 包,您需要在与main.py
相同的级别创建一个pubspec.yaml
文件。它应该具有以下内容:
dependencies:
flet_spinkit:
path: {绝对路径到 Flet Dart 包文件夹}
这种方法可能会改变,本指南正在更新。
现在,您需要通过运行flet build
命令为您选择的平台构建应用程序,例如:
flet build macos
最后,我们打开构建的应用程序:
open build/macos/flet_spinkit_app.app
您可以在此处找到此示例的源代码这里。
每次您需要对扩展的 Python 或 Dart 部分进行更改时,都需要重新运行构建命令。
自定义属性
在上面的示例中,Spinkit 控件创建了一个硬编码的 Flutter 小部件。现在让我们自定义它的属性。
Flet Control
属性
当我们在 Python 中创建 Spinkit 类时,它继承自 Flet 的Control
类,该类具有所有控件共有的属性,例如visible
、opacity
和tooltip
,仅举几例。有关常见 Control 属性的参考此处。
为了能够为您的新控件使用这些属性,您需要在新 Python 控件的构造函数中添加您想要使用的 Control 属性:
from typing import Any, Optional
from flet_core.control import Control, OptionalNumber
class Spinkit(Control):
"""
Spinkit 控件。
"""
def __init__(
self,
#
# Control
#
opacity: OptionalNumber = None,
tooltip: Optional[str] = None,
visible: Optional[bool] = None,
data: Any = None,
):
Control.__init__(
self,
tooltip=tooltip,
opacity=opacity,
visible=visible,
data=data,
)
def _get_control_name(self):
return "spinkit"
在<control-name>.dart
文件中,将您的小部件包装在baseControl()
中以神奇地实现所有 Python 的Control
属性:
import 'package:flet/flet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
class SpinkitControl extends StatelessWidget {
final Control? parent;
final Control control;
const SpinkitControl({
super.key,
required this.parent,
required this.control,
});
@override
Widget build(BuildContext context) {
return baseControl(
context,
const SpinKitRotatingCircle(
color: Colors.green,
size: 100.0,
),
parent,
control);
}
}
最后,在您的应用程序中使用Control
属性:
import flet as ft
from controls.spinkit import Spinkit
def main(page: ft.Page):
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.add(Spinkit(opacity=0.5, tooltip="Spinkit 工具提示"))
ft.app(main)
您可以在此处找到此示例的源代码这里。
Flet ConstrainedControl
属性
通常,Flet 中有三种类型的控件:
- 视觉控件,添加到应用程序/页面表面,例如 Spinkit。
- 弹出控件(对话框、选择器、面板等)。
- 非视觉控件或服务,添加到
overlay
,例如视频或音频。
在大多数情况下,视觉控件可以继承自ConstrainedControl
,它具有许多额外的属性,例如在 Stack 中的位置的top
和left
以及一堆动画属性。
要使用这些属性,从CostrainedControl
继承您的控件,并将这些属性添加到 Python 控件的构造函数中:
from typing import Any, Optional
from flet_core.constrained_control import ConstrainedControl
from flet_core.control import OptionalNumber
class Spinkit(ConstrainedControl):
"""
Spinkit 控件。
"""
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,
):
ConstrainedControl.__init__(
self,
tooltip=tooltip,
opacity=opacity,
visible=visible,
data=data,
left=left,
top=top,
right=right,
bottom=bottom,
)
def _get_control_name(self):
return "spinkit"
在<control-name>.dart
文件中,使用constrainedControl
方法包装 Flutter 小部件:
import 'package:flet/flet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
class SpinkitControl extends StatelessWidget {
final Control? parent;
final Control control;
const SpinkitControl({
super.key,
required this.parent,
required this.control,
});
@override
Widget build(BuildContext context) {
return constrainedControl(
context,
const SpinKitRotatingCircle(
color: Colors.green,
size: 100.0,
),
parent,
control);
}
}
在您的应用程序中使用ConstrainedControl
属性:
import flet as ft
from controls.spinkit import Spinkit
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),
Spinkit(opacity=0.5, tooltip="Spinkit 工具提示", top=0, left=0),
]
)
)
ft.app(main)
您可以在此处找到此示例的源代码这里。
控件特定属性
现在您已经充分利用了 Flet Control
和ConstrainedControl
提供的属性,让我们定义您正在构建的新控件特有的属性。
在 Spinkit 示例中,让我们定义它的color
和size
。
在 Python 类中,定义新的color
和size
属性:
from typing import Any, Optional
from flet_core.constrained_control import ConstrainedControl
from flet_core.control import OptionalNumber
class Spinkit(ConstrainedControl):
"""
Spinkit 控件。
"""
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,
#
# Spinkit 特定
#
color: Optional[str] = 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 "spinkit"
# color
@property
def color(self):
return self._get_attr("color")
@color.setter
def color(self, value):
self._set_attr("color", value)
# size
@property
def size(self):
return self._get_attr("size")
@size.setter
def size(self, value):
self._set_attr("size", value)
在<control-name>.dart
文件中,使用辅助方法attrColor
和attrDouble
来访问颜色和大小值:
import 'package:flet/flet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
class SpinkitControl extends StatelessWidget {
final Control? parent;
final Control control;
const SpinkitControl({
super.key,
required this.parent,
required this.control,
});
@override
Widget build(BuildContext context) {
var color = control.attrColor("color", context);
var size = control.attrDouble("size");
return constrainedControl(
context,
SpinKitRotatingCircle(
color: color,
size: size?? 50,
),
parent,
control);
}
}
在您的应用程序中使用color
和size
属性:
import flet as ft
from controls.spinkit import Spinkit
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),
Spinkit(
opacity=0.5,
tooltip="Spinkit tooltip",
top=0,
left=0,
color=ft.colors.PURPLE,
size=150,
),
]
)
)
ft.app(main)
您可以在 此处 找到此示例的源代码。
不同类型的属性和事件的示例
枚举属性
例如,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 控件在外部包中实现,可以作为您自己的控件的起点: