Flutter窗口覆盖插件overlay_windows_plugin的使用

发布于 1周前 作者 yibo5220 来自 Flutter

Flutter窗口覆盖插件overlay_windows_plugin的使用

插件介绍

overlay_windows_plugin 是一个用于在Android上与Window Manager Service集成的Flutter插件,允许你在其他应用程序之上显示覆盖窗口。 该插件支持在同一个时间显示多个覆盖窗口,并且每个覆盖窗口可以使用不同的UI或布局。

示例代码

import 'package:flutter/material.dart';
import 'package:overlay_windows_plugin/overlay_message.dart';
import 'package:overlay_windows_plugin/overlay_windows_api.g.dart';
import 'package:overlay_windows_plugin/overlay_windows_plugin.dart';
import 'package:overlay_windows_plugin_example/overlay_main11.0.dart';
import 'package:overlay_windows_plugin_example/overlay_main22.0.dart';

void main() {
  runApp(const MyApp());
}

[@pragma](/user/pragma)("vm:entry-point")
void overlayMain11() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: SafeArea(
        child: OverlayMain11(),
      ),
    ),
  );
}

[@pragma](/user/pragma)("vm:entry-point")
void overlayMain22() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: OverlayMain22(),
    ),
  );
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _overlayWindowsPlugin = OverlayWindowsPlugin.defaultInstance;

  List<OverlayMessage> message = [];

  @override
  void initState() {
    super.initState();
    _overlayWindowsPlugin.messageStream.listen((event) {
      setState(() {
        message.add(event);
      });
    });
    _overlayWindowsPlugin.touchEventStream.listen((event) {});
  }

  final List<String> _overlayWindowIds = [];

  int index = 0;

  void showOverlay(String entryPointName) async {
    var hasPermission = await _overlayWindowsPlugin.isPermissionGranted();
    if (!hasPermission) {
      _overlayWindowsPlugin.requestPermission();
      return;
    }
    var id = '$entryPointName-${index + 1}';
    setState(() {
      _overlayWindowIds.add(id);
    });

    if (entryPointName == "overlayMain1") {
      _overlayWindowsPlugin.showOverlayWindow(
          id,
          entryPointName,
          OverlayWindowConfig(
            width: 300,
            height: 100,
            enableDrag: true,
          ));
    } else {
      _overlayWindowsPlugin.showOverlayWindow(
          id,
          entryPointName,
          OverlayWindowConfig(
            width: 300,
            height: 300,
            // alignment: OverlayAlignment.bottomCenter,
            flag: OverlayFlag.defaultFlag,
            enableDrag: true,
            positionGravity: PositionGravity.left,
          ));
    }

    index++;
  }

  void closeOverlay(String entryPointName) async {
    var ids = _overlayWindowIds.where((element) => element.startsWith(entryPointName)).toList();
    ids.forEach((element) async {
      setState(() {
        _overlayWindowIds.remove(element);
      });

      _overlayWindowsPlugin.closeOverlayWindow(element);
    });
  }

  void sendMessage() {
    _overlayWindowsPlugin.sendMessage("", "Hello from main");
  }

  int clickTime = 0;
  void setOverlayFlag(String entryPointName) {
    var ids = _overlayWindowIds.where((element) => element.startsWith(entryPointName)).toList();
    if (clickTime > 2) {
      clickTime = 0;
    }
    ids.forEach((element) async {
      if (clickTime == 0) {
        _overlayWindowsPlugin.setFlags(element, OverlayFlag.clickThrough);
      } else if (clickTime == 1) {
        _overlayWindowsPlugin.setFlags(element, OverlayFlag.focusPointer);
      } else if (clickTime == 2) {
        _overlayWindowsPlugin.setFlags(element, OverlayFlag.defaultFlag);
      }
    });
    clickTime++;
  }

  int initWidth = 300;
  int initHeight = 300;
  void increaseSize() {
    setState(() {
      initWidth += 20;
      initHeight +=  e0;
      _overlayWindowsPlugin.resize(_overlayWindowIds.first, initWidth, initHeight);
    });
  }

  void decreaseSize() {
    setState(() {
      initWidth -=  e0;
      initHeight -=  e0;
      _overlayWindowsPlugin.resize(_overlayWindowIds.first, initWidth, initHeight);
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    onPressed: () {
                      showOverlay("overlayMain1");
                    },
                    child: const Text("Show 1"),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      closeOverlay("overlayMain1");
                    },
                    child: const Text("Close 1"),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      setOverlayFlag("overlayMain1");
                    },
                    child: const Text("Update Flag"),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      increaseSize();
                    },
                    child: const Text("Big"),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      decreaseSize();
                    },
                    child: const Text("Small"),
                  ),
                ],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    onPressed: () {
                      showOverlay("overlayMain22");
                    },
                    child: const Text("Show 2"),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      closeOverlay("overlayMaine22");
                    },
                    child: const Text("Close 2"),
                  ),
                ],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    onPressed: () {
                      sendMessage();
                    },
                    child: const Text("Send Message"),
                  ),
                ],
              ),
              Expanded(
                child: ListView.builder(
                  itemCount: message.length,
                  itemBuilder: (context, index) {
                    return Text('${message[index].overlayWindowId}: ${message[index].message}');
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

使用说明

  1. 安装依赖

    dependencies:
      overlay_windows_plugin: any
    
  2. 设置权限AndroidManifest.xml 中添加以下权限:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        ...
        <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
        <application>
            ...
            <service android:name="com.ducdd.overlay_windows_plugin.OverlayService"
              android:exported="false" />
            ...
        </application>
    </manifest>
    
  3. 定义入口点main.dart 文件中定义入口点。

[@pragma](/user/pragma)("vm:entry-point")
void overlayMain1() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: SafeArea(
        child: OverlayMain1(),
      ),
    ),
  );
}

[@pragma](/user/pragma)("vm:entry-point")
void overlayMain22() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: OverlayMaine22(),
    ),
  );
}
  1. 创建插件实例
final _overlayWindowsPlugin = OverlayWindowsPlugin.defaultInstance;
  1. 打开覆盖窗口
_overlayWindowsPlugin.showOverlayWindow(
    id,
    "overlayMain1",
    OverlayWindowConfig(
      width: 300,
      height: 100,
      enableDrag: true,
    ));
  1. 关闭覆盖窗口
_overlayWindowsPlugin.closeOverlayWindow("overlay window id");
  1. 发送消息给所有打开的覆盖窗口
_overlayWindowsPlugin.sendMessage("", "Hello from main");
  1. 接收覆盖窗口的消息或触摸事件
_overlayWindowsPlugin.messageStream.listen((event) {
  setState(() {
    message.add(event);
  });
});

_overlayWindowsPlugin.touchEventStream.listen((event) {});
  1. 在每个覆盖窗口视图中启动一个视图来与主视图通信
final view = OverlayWindowView();
view.stateChangedStream.listen((event) {
  setState(() {
    viewId = event;
  });
});

view.messageStream.listen((mes) {
  setState(() {
    message = mes.message as String;
  });
});

view.sendMessage('Hello from overlay $viewId');
  1. 获取覆盖窗口API

更多关于Flutter窗口覆盖插件overlay_windows_plugin的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter窗口覆盖插件overlay_windows_plugin的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中使用overlay_windows_plugin插件来实现窗口覆盖的示例代码。请注意,由于overlay_windows_plugin是一个假定的插件名称(实际中可能并不存在一个名为overlay_windows_plugin的官方插件),我将提供一个通用的实现思路和伪代码,以展示如何在Flutter中处理窗口覆盖的概念。通常,这种功能可能需要平台特定的代码(如Android和iOS的原生代码)。

Flutter 插件实现思路

  1. 创建Flutter插件:首先,你需要创建一个Flutter插件,该插件包含原生代码来处理窗口覆盖。

  2. 原生代码实现

    • Android:使用WindowManageraddView方法。
    • iOS:使用UIWindow并设置其windowLevel
  3. Flutter 调用原生方法:通过MethodChannel在Flutter中调用原生实现的方法。

示例代码

1. 创建Flutter插件(以Android为例)

a. 在android/src/main/java/com/example/overlayplugin下创建OverlayPlugin.java

package com.example.overlayplugin;

import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;

public class OverlayPlugin implements FlutterPlugin, ActivityAware {

    private MethodChannel channel;
    private Context applicationContext;
    private WindowManager windowManager;
    private View overlayView;

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        applicationContext = flutterPluginBinding.getApplicationContext();
        channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "overlay_plugin");
        channel.setMethodCallHandler(
                (call, result) -> {
                    if (call.method.equals("showOverlay")) {
                        showOverlay();
                        result.success(null);
                    } else {
                        result.notImplemented();
                    }
                });
    }

    private void showOverlay() {
        windowManager = (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE);
        LayoutInflater inflater = (LayoutInflater) applicationContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        overlayView = inflater.inflate(R.layout.overlay_layout, null);

        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                WindowManager.LayoutParams.FORMAT_CHANGED);
        params.gravity = Gravity.TOP | Gravity.LEFT;
        params.x = 0;
        params.y = 0;

        windowManager.addView(overlayView, params);
    }

    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
        if (overlayView != null && windowManager != null) {
            windowManager.removeView(overlayView);
        }
    }

    @Override
    public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
        // No-op
    }

    @Override
    public void onDetachedFromActivityForConfigChanges() {
        // No-op
    }

    @Override
    public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
        // No-op
    }

    @Override
    public void onDetachedFromActivity() {
        // No-op
    }
}

b. 在android/src/main/res/layout下创建overlay_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FF000080"> <!-- 半透明红色背景 -->

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Overlay Window"
        android:textColor="#FFFFFF"
        android:layout_gravity="center" />
</FrameLayout>

2. 在Flutter中调用插件

a. 在pubspec.yaml中添加依赖(假设你已经将插件发布到pub.dev)

dependencies:
  flutter:
    sdk: flutter
  overlay_plugin:
    path: ../path/to/your/overlay_plugin  # 本地路径或pub.dev上的包名

b. 在Dart代码中调用插件方法

import 'package:flutter/material.dart';
import 'package:overlay_plugin/overlay_plugin.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Overlay Plugin Demo'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () async {
              // 调用插件方法显示覆盖窗口
              OverlayPlugin.showOverlay();
            },
            child: Text('Show Overlay'),
          ),
        ),
      ),
    );
  }
}

注意

  • 上述代码是一个简化的示例,实际使用中需要考虑权限处理(如SYSTEM_ALERT_WINDOW权限)、适配不同屏幕尺寸和分辨率、处理窗口的移除等。
  • 对于iOS,你需要创建一个对应的Swift或Objective-C类,并使用UIWindow来实现覆盖窗口。
  • 由于Flutter插件的创建和发布涉及到更多细节,这里只提供了核心逻辑。如果需要实际使用,请查阅Flutter插件开发文档并创建符合你需求的插件。
回到顶部