Repository navigation
Questioncode import 'dart:developer';
import 'dart:isolate';
import 'dart:ui';
import 'package:flet/flet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
@pragma("vm:entry-point")
void overlayMain() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: Text("Overlay Window Test"),
),
);
}
class FletOverlayWindowControl extends StatefulWidget {
final Control control;
const FletOverlayWindowControl({
super.key,
required this.control,
});
@override
State<FletOverlayWindowControl> createState() =>
_FletOverlayWindowControlState();
}
class _FletOverlayWindowControlState extends State<FletOverlayWindowControl> {
static const String _kPortNameOverlay = 'OVERLAY';
static const String _kPortNameHome = 'UI';
final _receivePort = ReceivePort();
SendPort? homePort;
String? latestMessageFromOverlay;
@override
void initState() {
// TODO: implement initState
super.initState();
if (homePort != null) return;
final res = IsolateNameServer.registerPortWithName(
_receivePort.sendPort,
_kPortNameHome,
);
log("$res: OVERLAY");
_receivePort.listen((message) {
log("message from OVERLAY: $message");
});
}
// 1. 检查悬浮窗权限
Future<bool> isPermissionGranted() async {
return await FlutterOverlayWindow.isPermissionGranted();
}
// 2. 请求悬浮窗权限
Future<bool?> requestPermission() async {
return await FlutterOverlayWindow.requestPermission();
}
// 3. 显示悬浮窗
Future<void> showOverlay({
bool enableDrag = true,
String overlayTitle = "",
String overlayContent = "",
OverlayFlag flag = OverlayFlag.defaultFlag,
NotificationVisibility visibility = NotificationVisibility.visibilityPublic,
PositionGravity positionGravity = PositionGravity.auto,
int height = 0,
int width = 0,
OverlayPosition startPosition = const OverlayPosition(0, -259),
}) async {
await FlutterOverlayWindow.showOverlay(
enableDrag: enableDrag,
overlayTitle: overlayTitle,
overlayContent: overlayContent,
flag: flag,
visibility: visibility,
positionGravity: positionGravity,
height: height,
width: width,
startPosition: startPosition,
);
}
// 4. 检查悬浮窗是否激活
Future<bool> isActive() async {
return await FlutterOverlayWindow.isActive();
}
// 5. 调整悬浮窗大小
Future<bool?> resizeOverlay(int width, int height, bool enableDrag) async {
return await FlutterOverlayWindow.resizeOverlay(
width,
height,
enableDrag,
);
}
// 6. 向悬浮窗发送消息
void sendMessageToOverlay(String message) {
homePort ??= IsolateNameServer.lookupPortByName(_kPortNameOverlay);
homePort?.send('$message');
}
// 2. 获取悬浮窗坐标
Future<OverlayPosition> getOverlayPosition() {
return FlutterOverlayWindow.getOverlayPosition();
}
// 3. 移动悬浮窗到指定位置
Future<bool?> moveOverlay(OverlayPosition position) async {
return await FlutterOverlayWindow.moveOverlay(position);
}
// 4. 关闭悬浮窗
Future<bool?> closeOverlay() async {
return await FlutterOverlayWindow.closeOverlay();
}
@override
Widget build(BuildContext context) {
overlayMain();
requestPermission();
showOverlay(
width: 300,
height: 200,
overlayTitle: "Overlay Window",
overlayContent: "This is an overlay window");
String text = widget.control.getString("value", "")!;
Widget myControl = Text(text);
return LayoutControl(control: widget.control, child: myControl);
}
}
Code sampleNo response Error messageNo response ------------------------------------------------------
|
Replies: 2 comments 7 replies
|
简短回答:你的排查是对的。 Your diagnosis is correct, and here is the mechanism behind it. The overlay runs in a second FlutterEngine, and the plugin creates that engine with an entrypoint built like this (in DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"overlayMain")An entrypoint given only by function name is resolved in the root library of the app bundle, which is Two bugs in the posted control worth fixing at the same time:
To package it as a reusable extension when // lib/flet_overlay_window.dart
@pragma("vm:entry-point")
void overlayEntrypoint() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
home: Text("Overlay Window Test"),
));
}There are two routes to get the trampoline into the generated Route 1: local template directory. Without The zip unpacks to a Route 2: forked repo (better for a team or CI). Fork flet-build-template, make the two edits on a branch, and build with The two edits, in whichever template copy you chose: In import 'package:flet_overlay_window/flet_overlay_window.dart' as fow;
@pragma("vm:entry-point")
void overlayMain() => fow.overlayEntrypoint();In <service
android:name="flutter.overlay.window.flutter_overlay_window.OverlayService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="app overlay window" />
</service>and next to the existing <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />Minimal variant while validating: instead of the trampoline, paste your exact proven Runtime order from Python: call your control's request permission method and await it (the user flips the "Display over other apps" toggle in Settings), then call show. If it still does not appear, |

简短回答:你的排查是对的。
overlayMain必须在main.dart里,因为插件是按函数名在应用根库里查找入口点的。下面给出两种封装路线:本地模板目录(最简单、版本安全)或 fork 模板仓库,另外还有一个最小改动的变体。Your diagnosis is correct, and here is the mechanism behind it. The overlay runs in a second FlutterEngine, and the plugin creates that engine with an entrypoint built like this (in
onAttachedToActivity, source, with the same construction repeated as a fallback inOverlayService):An entrypoint given only by function name is resolved in the root library of the app bundle, which is
lib/main.dart. A@pragma("vm:entry-point")function inside an extension package …