跳到主要内容

将现有 Flutter 包集成到您的 Flet 应用中

正在进行中

本指南正在更新。

介绍

虽然 Flet 控件利用了许多内置的 Flutter 小部件,甚至能够创建复杂的应用程序,但并非所有的 Flutter 小部件或第三方包都能直接得到 Flet 团队的支持或包含在核心 Flet 框架中。

为了解决这个问题,Flet 框架提供了一个可扩展性机制。这允许您将自己的自定义 Flutter 包或第三方库中的小部件和 API 直接集成到您的 Flet 应用程序中。

先决条件

要将自定义 Flutter 包集成到 Flet 中,您需要对如何用 Dart 语言创建 Flutter 应用和包有基本的了解,并配置好 Flutter 开发环境。有关 Flutter 和 Dart 的更多信息,请查看Flutter 入门

创建 Flet 扩展

集成第三方 Flutter 包的 Flet 扩展包括以下部分:

  1. Flet Dart 包。

  2. 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类,该类具有所有控件共有的属性,例如visibleopacitytooltip,仅举几例。有关常见 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 中有三种类型的控件:

  1. 视觉控件,添加到应用程序/页面表面,例如 Spinkit。
  2. 弹出控件(对话框、选择器、面板等)。
  3. 非视觉控件或服务,添加到overlay,例如视频或音频。

在大多数情况下,视觉控件可以继承自ConstrainedControl,它具有许多额外的属性,例如在 Stack 中的位置的topleft以及一堆动画属性。

要使用这些属性,从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 ControlConstrainedControl提供的属性,让我们定义您正在构建的新控件特有的属性。

在 Spinkit 示例中,让我们定义它的colorsize

在 Python 类中,定义新的colorsize属性:

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文件中,使用辅助方法attrColorattrDouble来访问颜色和大小值:

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);
}
}

在您的应用程序中使用colorsize属性:

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)

您可以在 此处 找到此示例的源代码。

不同类型的属性和事件的示例

枚举属性

例如,AppBarclip_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 属性

例如,Cardshape 属性。

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")
子控件

例如,AlertDialogcontent

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);
事件

例如,ElevatedButtonon_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 控件在外部包中实现,可以作为您自己的控件的起点: