功能
- 纯Flutter:您可以使用纯Flutter编写整个覆盖窗口。
- 简单:至少仅需1行代码即可启动您的覆盖窗口。
- 自动调整大小:只需关心您的Flutter小部件的大小,它将自动调整为Android视图。
- 多窗口:支持在一个应用中创建多个覆盖窗口,并且窗口可以拥有子窗口。
- 可通信:您的主应用可以与窗口通信,窗口之间也支持通信。
- 事件机制:触发窗口生命周期和其他操作(如拖动)的事件,您可以更灵活地控制您的窗口。
- 更多功能即将推出…
预览
| 夜间模式 | 简单示例 | 辅助触摸模拟 |
|---|---|---|
![]() |
![]() |
![]() |
安装
打开位于应用文件夹中的pubspec.yaml文件,并在dependencies下添加flutter_floatwing。
dependencies:
flutter_floatwing: <latest_version>
然后您应该安装它,
- 从终端:运行
flutter pub get。 - 从Android Studio/IntelliJ:点击
pubspec.yaml顶部操作栏中的Packages get。 - 从VS Code:点击
pubspec.yaml顶部操作栏右侧的Get Packages。
或者直接在命令行中添加
flutter pub add flutter_floatwing
快速入门
我们使用Android的系统警告窗口进行显示,因此首先需要在AndroidManifest.xml中添加权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
为将在覆盖窗口中显示的小部件添加一个路由
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: "/",
routes: {
"/": (_) => HomePage(),
// add a route as entrypoint for your overlay window.
"/my-overlay-window": (_) => MyOverlayWindow(),
},
);
}
在我们启动浮动窗口之前,我们需要在initState或任何按钮的回调函数中检查并请求权限,并初始化flutter_floatwing插件
// check and grant the system alert window permission.
FloatwingPlugin().checkPermission().then((v) {
if (!v) FloatwingPlugin().openPermissionSetting();
});
// initialize the plugin at first.
FloatwingPlugin().initialize();
接下来开始创建并启动您的覆盖窗口
// define window config and start the window from config.
WindowConfig(route: "/my-overlay-window")
.to() // create a window object
.create(start: true); // create the window and start the overlay window.
了解更多信息。
架构
在我们详细了解flutter_floatwing如何管理窗口之前,我们需要了解该插件设计的一些内容。
id是窗口的唯一标识符,所有对窗口的操作都基于此id,创建前必须提供一个。- 我们将打开主应用程序创建的第一个引擎视为
主引擎或插件引擎。由服务创建的其他引擎是窗口引擎。 - 不同的
引擎是不同的线程,无法直接通信。 - 允许从
主引擎订阅所有窗口的事件,也允许在窗口引擎中订阅自身和子窗口的事件。但我们不能订阅兄弟窗口或父窗口的事件。 share数据是窗口引擎或插件引擎之间通信的唯一方式,它没有限制,除了数据需要可序列化。这意味着您可以将数据从任何地方共享到任何地方。
一个floatwing窗口对象包含:一个通过runApp运行小部件的Flutter引擎和一个添加到窗口管理器的视图。
整个视图层次结构如下
用法
在我们详细了解如何在Flutter中使用flutter_floatwing之前,让我们先谈谈flutter_floatwing如何创建一个新的覆盖窗口
- 首先,我们需要由主应用启动一个作为管理器的服务。
- 然后创建窗口请求发送给服务。
- 在服务中,我们使用入口点启动Flutter引擎。
- 创建一个新的Flutter视图并将其附加到Flutter引擎。
- 将视图添加到Android窗口管理器。
窗口与配置
WindowConfig包含窗口的所有配置。我们可以使用配置来创建窗口,如下所示
void _createWindow() {
var config = WindowConfig();
w = Window(config, id="my-window");
w.create();
}
如果您不需要注册事件或数据处理程序,只需使用配置创建窗口即可。
void _createWindow() {
WindowConfig(id="my-window").create();
}
但是正如您所见,如果您想为窗口提供一个id,则必须在WindowConfig中提供。
如果要注册处理程序,可以使用to()函数首先将配置转换为窗口,这对于简化代码非常有用。
void _createWindow() {
WindowConfig(id="my-window").to()
.on(EventType.WindowCreated, (w, _) {})
.create();
}
窗口生命周期
- 已创建
- 已启动
- 已暂停
- 已恢复
- 已销毁
待办事项
入口点
入口点是引擎执行的起点。我们支持3种配置模式
| 名称 | Config(配置) | 如何使用 |
|---|---|---|
路由 |
WindowConfig(route: "/my-overlay") |
– 在您的主路由中为覆盖窗口添加一个路由– 使用配置启动窗口:WindowConfig(route: "/my-overlay") |
静态函数 |
WindowConfig(callback: myOverlayMain) |
– 定义一个调用runApp以启动小部件的静态函数void Function()– 使用配置启动窗口:WindowConfig(callback: myOverlayMain) |
入口点 |
WindowConfig(entry: "myOverlayMain") |
– 第一步与静态函数相同。– 在静态函数上方添加@pragma("vm:entry-point")– 使用配置启动窗口:WindowConfig(entry: "myOverlayMain")– *类似于静态函数,但将函数名称字符串作为参数* |
route示例
- 为主应用程序中的覆盖小部件添加路由。
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: "/",
routes: {
"/": (_) => HomePage(),
// add a route as entrypoint for your overlay window.
"/my-overlay-window": (_) => MyOverlayWindow(),
},
);
}
- 使用
route作为配置启动窗口。
void _startWindow() {
// define window config and start the window from config.
WindowConfig(route: "/my-overlay-window")
.to() // create a window object
.create(start: true); // create the window and start the overlay window.
}
静态函数示例
- 定义一个调用
runApp的静态函数
void myOverlayMain() {
runApp(MaterialApp(
home: AssistivePannel(),
));
// or simply use `floatwing` method to inject `MaterialApp`
// runApp(AssistivePannel().floatwing(app: true));
}
- 使用
callback作为配置启动窗口。
void _startWindow() {
// define window config and start the window from config.
WindowConfig(callback: myOverlayMain)
.to() // create a window object
.create(start: true); // create the window and start the overlay window.
}
entry-point示例
- 定义为调用
runApp并添加prama的静态函数
@pragma("vm:entry-point")
void myOverlayMain() {
runApp(MaterialApp(
home: AssistivePannel(),
));
// or simply use `floatwing` method to inject `MaterialApp`
// runApp(AssistivePannel().floatwing(app: true));
}
- 使用
entry作为配置启动窗口。
void _startWindow() {
// define window config and start the window from config.
WindowConfig(entry: "myOverlayMain")
.to() // create a window object
.create(start: true); // create the window and start the overlay window.
}
包装您的小部件
对于简单的小部件,您无需处理您的小部件。但是,如果您需要更多功能并使您的代码整洁,我们为您的小部件提供了一个注入器。
目前有以下列出的功能,
- 自动调整窗口视图大小。
- 自动同步并确保窗口。
- 包装
MaterialApp - 更多功能即将推出
之前,我们像这样编写我们的覆盖主函数,
void overlayMain() {
runApp(MaterialApp(
home: MyOverView(),
))
}
现在,我们可以简洁地编码,
void overlayMain() {
runApp(MyOverView().floatwing(app: true)))
}
我们可以包装成Widget和WidgetBuilder,包装WidgetBuilder,我们可以通过Window.of(context)访问窗口实例,而FloatwingPlugin().currentWindow是获取窗口实例以包装Widget的唯一方法。
如果我们要通过Window.of(context)访问窗口,请像这样更改代码,
void overlayMain() {
runApp(((_)=>MyOverView()).floatwing(app: true).make()))
}
在覆盖窗口中访问窗口
在您的窗口引擎中,您可以通过两种方式访问窗口对象
- 直接访问插件的缓存字段:
FloatwingPlugin().currentWindow。 - 如果小部件通过
.floatwing()注入,您可以通过Window.of(context)获取窗口。
FloatwingPlugin().currentWindow将返回null,除非初始化已完成。
如果您使用.floatwing(debug: true)注入WidgetBuilder,则可以访问当前窗口。它将始终返回非空值,除非您启用调试.floatwing(debug: true)。
例如,如果我们想获取当前窗口的id,我们可以这样做,
/// ...
import 'package:flutter_floatwing/flutter_floatwing.dart';
class _ExampleViewState extends State<ExampleView> {
Window? w;
@override
void initState() {
super.initState();
SchedulerBinding.instance?.addPostFrameCallback((_) {
w = Window.of(context);
print("my window id is ${w.id}");
});
}
}
订阅事件
我们可以订阅来自窗口的事件,并在事件触发时触发操作。窗口的事件将发送到主引擎、自身窗口引擎和父窗口引擎。这意味着您可以从主应用程序的Flutter、覆盖窗口和父覆盖窗口订阅窗口的事件。
目前我们支持窗口生命周期和拖动操作的事件。
enum EventType {
WindowCreated,
WindowStarted,
WindowPaused,
WindowResumed,
WindowDestroy,
WindowDragStart,
WindowDragging,
WindowDragEnd,
}
更多事件类型即将推出,欢迎贡献!
例如,我们想在窗口启动时执行一些操作,我们可以像这样编码,
@override
void initState() {
super.initState();
SchedulerBinding.instance?.addPostFrameCallback((_) {
w = Window.of(context);
w?.on(EventType.WindowStarted, (window, _) {
print("$w has been started.");
}).on(EventType.WindowDestroy, (window, data) {
// data is a boolean value, which means that the window
// are destroyed force or not.
print("$w has been destroy, force $data");
});
});
}
与窗口共享数据
共享数据是与窗口通信的唯一方式。我们提供了一种简单的方法来实现这一点:window.share(data)。
例如,如果您想从主应用程序共享数据到覆盖窗口。
首先在主应用程序中获取目标窗口,通常可以使用创建的窗口,或者您可以从windows缓存中按id获取一个,
Window w;
void _startWindow() {
w = WindowConfig(route: "/my-overlay-window").to();
}
void _shareData(dynamic data) {
w.share(data).then((value) {
// and window can return value.
});
// or just take one from cache
// FloatwingPlugin().windows["default"]?.share(data);
}
如果你想为数据命名,可以添加名称参数:`“w.share(data, name=\”name-1\”)"`。
然后您应该在窗口中通过注册数据处理程序来监听数据。
@override
void initState() {
super.initState();
SchedulerBinding.instance?.addPostFrameCallback((_) {
w = Window.of(context);
w?.onData((source, name, data) async {
print("get $name data from $source: $data");
});
});
}
处理程序的函数签名是Future<dynamic> Function(String? source, String? name, dynamic data)。
source是数据的来源,如果是来自主应用程序则为null,来自窗口则是窗口的id。name是数据名称,您可以出于不同目的共享数据。data是您获取的数据。- 如果您想执行一些操作,则可以返回值。
通信方向存在限制,除非您将数据发送给自己,否则不允许。这意味着只要您知道窗口的id,就可以发送数据。*目前尚未实现共享到主应用程序。*
注意:您共享的数据应该是可序列化的。
API参考
FloatwingPlugin实例
FloatwingPlugin是一个单例类,每次调用FloatwingPlugin()工厂方法时都会返回相同的实例。
WindowConfig对象
待办事项
Window对象
待办事项
事件
窗口生命周期
操作
更多事件类型即将推出,欢迎贡献!
支持
您觉得这个插件有用吗?请考虑捐款以帮助改进它!
贡献
随时欢迎贡献!




