Flutter浮动窗口管理插件flutter_floatwing的使用

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

Flutter浮动窗口管理插件flutter_floatwing的使用

1. 插件简介

flutter_floatwing 是一个用于在 Android 上创建和管理浮动窗口(overlay windows)的 Flutter 插件。它允许你使用纯 Flutter 代码来构建浮动窗口,并提供了多种功能,如自动调整大小、多窗口支持、事件机制等。

2. 主要特性

  • 纯 Flutter:你可以用纯 Flutter 代码编写整个浮动窗口。
  • 简单易用:只需一行代码即可启动浮动窗口。
  • 自动调整大小:根据 Flutter 小部件的大小自动调整 Android 视图。
  • 多窗口支持:可以在一个应用中创建多个浮动窗口,并且窗口可以有子窗口。
  • 通信能力:主应用和窗口之间可以进行通信,窗口之间也可以互相通信。
  • 事件机制:可以监听窗口生命周期和其他操作(如拖动)的事件,灵活控制窗口行为。

3. 预览

夜间模式 简单示例 辅助触摸模拟
夜间模式 简单示例 辅助触摸模拟

4. 安装

  1. 打开 pubspec.yaml 文件,在 dependencies 中添加 flutter_floatwing

    dependencies:
      flutter_floatwing: ^最新版本
    
  2. 安装依赖:

    • 终端:运行 flutter pub get
    • Android Studio/IntelliJ:点击 pubspec.yaml 顶部的 “Packages get”
    • VS Code:点击 pubspec.yaml 顶部的 “Get Packages”

或者直接在命令行中添加:

flutter pub add flutter_floatwing

5. 快速开始

5.1 添加权限

由于 flutter_floatwing 使用的是 Android 的系统警报窗口(SYSTEM_ALERT_WINDOW),因此需要在 AndroidManifest.xml 中添加权限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
5.2 创建路由

在主应用中为浮动窗口添加路由:

[@override](/user/override)
Widget build(BuildContext context) {
  return MaterialApp(
    debugShowCheckedModeBanner: false,
    initialRoute: "/",
    routes: {
      "/": (_) => HomePage(),
      // 为浮动窗口添加路由
      "/my-overlay-window": (_) => MyOverlayWindow(),
    },
  );
}
5.3 检查并请求权限

在启动浮动窗口之前,需要检查并请求系统警报窗口权限,并初始化 flutter_floatwing 插件:

// 检查并请求权限
FloatwingPlugin().checkPermission().then((v) {
  if (!v) FloatwingPlugin().openPermissionSetting();
});

// 初始化插件
FloatwingPlugin().initialize();
5.4 创建并启动浮动窗口

定义窗口配置并启动浮动窗口:

// 定义窗口配置并启动窗口
WindowConfig(route: "/my-overlay-window")
    .to() // 创建窗口对象
    .create(start: true); // 创建并启动浮动窗口

6. 完整示例 Demo

以下是一个完整的示例,展示了如何使用 flutter_floatwing 创建和管理浮动窗口。

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

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

[@pragma](/user/pragma)("vm:entry-point")
void floatwing() {
  runApp(((_) => NonrmalView()).floatwing().make());
}

void floatwing2(Window w) {
  runApp(MaterialApp(
    // floatwing on widget can't use Window.of(context)
    // to access window instance
    // should use FloatwingPlugin().currentWindow
    home: NonrmalView().floatwing(),
  ));
}

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var _configs = [
    WindowConfig(
      id: "normal",
      route: "/normal",
      draggable: true,
    ),
    WindowConfig(
      id: "assitive_touch",
      route: "/assitive_touch",
      draggable: true,
    ),
    WindowConfig(
      id: "night",
      route: "/night",
      width: WindowSize.MatchParent, 
      height: WindowSize.MatchParent,
      clickable: false,
    )
  ];

  Map<String, WidgetBuilder> _builders = {
    "normal": (_) => NonrmalView(),
    "assitive_touch": (_) => AssistiveTouch(),
    "night": (_) => NightView(),
  };

  Map<String, Widget Function(BuildContext)> _routes = {};

  [@override](/user/override)
  void initState() {
    super.initState();

    _routes["/"] = (_) => HomePage(configs: _configs);

    _configs.forEach((c) => {
          if (c.route != null && _builders[c.id] != null)
            {_routes[c.route!] = _builders[c.id]!.floatwing(debug: false)}
        });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      initialRoute: "/",
      routes: _routes,
    );
  }
}

class HomePage extends StatefulWidget {
  final List<WindowConfig> configs;
  const HomePage({Key? key, required this.configs}) : super(key: key);

  [@override](/user/override)
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  [@override](/user/override)
  void initState() {
    super.initState();

    FloatwingPlugin().initialize();

    initAsyncState();
  }

  List<Window> _windows = [];
  Map<Window, bool> _readys = {};
  bool _ready = false;

  initAsyncState() async {
    var p1 = await FloatwingPlugin().checkPermission();
    var p2 = await FloatwingPlugin().isServiceRunning();

    // 获取权限
    if (!p1) {
      FloatwingPlugin().openPermissionSetting();
      return;
    }

    // 启动服务
    if (!p2) {
      FloatwingPlugin().startService();
    }

    _createWindows();

    setState(() {
      _ready = true;
    });
  }

  _createWindows() async {
    await FloatwingPlugin().isServiceRunning().then((v) async {
      if (!v)
        await FloatwingPlugin().startService().then((_) {
          print("启动后台服务成功");
        });
    });

    _windows.forEach((w) {
      var _w = FloatwingPlugin().windows[w.id];
      if (_w != null) {
        // 替换 w 为 _w
        _readys[w] = true;
        return;
      }
      w.on(EventType.WindowCreated, (window, data) {
        _readys[window] = true;
        setState(() {});
      }).create();
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Floatwing 示例'),
      ),
      body: _ready
          ? ListView(
              children: _windows.map((e) => _item(e)).toList(),
            )
          : Center(
              child: ElevatedButton(
                  onPressed: () {
                    initAsyncState();
                  },
                  child: Text("开始")),
            ),
    );
  }

  _debug(Window w) {
    Navigator.of(context).pushNamed(w.config!.route!);
  }

  Widget _item(Window w) {
    return Card(
      margin: EdgeInsets.all(10),
      child: Padding(
          padding: EdgeInsets.all(10),
          child: Column(
            children: [
              Text(w.id,
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
              SizedBox(height: 10),
              Container(
                width: double.infinity,
                padding: EdgeInsets.all(5),
                decoration: BoxDecoration(
                    color: Color.fromARGB(255, 214, 213, 213),
                    borderRadius: BorderRadius.all(Radius.circular(4))),
                child: Text(w.config?.toString() ?? ""),
              ),
              SizedBox(height: 10),
              Row(
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  TextButton(
                    onPressed: (_readys[w] == true) ? () => w.start() : null,
                    child: Text("打开"),
                  ),
                  TextButton(
                      onPressed: w.config?.route != null ? () => _debug(w) : null,
                      child: Text("调试")),
                  TextButton(
                    onPressed: (_readys[w] == true)
                        ? () => {w.close(), w.share("close")}
                        : null,
                    child: Text("关闭", style: TextStyle(color: Colors.red)),
                  ),
                ],
              )
            ],
          )),
    );
  }
}

// 示例页面
class NonrmalView extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("普通窗口")),
      body: Center(child: Text("这是一个普通的浮动窗口")),
    );
  }
}

class AssistiveTouch extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("辅助触摸")),
      body: Center(child: Text("这是一个辅助触摸窗口")),
    );
  }
}

class NightView extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("夜间模式")),
      body: Center(child: Text("这是一个全屏的夜间模式窗口")),
    );
  }
}

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

1 回复

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


当然,下面是一个关于如何使用 flutter_floatwing 插件来管理 Flutter 应用中的浮动窗口的示例代码。这个插件允许你在 Android 和 iOS 上创建和管理浮动窗口。

首先,确保你已经在 pubspec.yaml 文件中添加了 flutter_floatwing 依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_floatwing: ^最新版本号  # 请替换为实际最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来是具体的代码实现。

1. 初始化浮动窗口插件

在你的 Flutter 应用的入口文件(通常是 main.dart)中,初始化 FlutterFloatwing 插件:

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

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

  // 初始化 FlutterFloatwing 插件
  FlutterFloatwing.instance.init();
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

2. 创建浮动窗口

接下来,你可以创建一个浮动窗口。例如,在 HomePage 中添加一个按钮,点击按钮时显示浮动窗口:

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

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Float Window Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            _showFloatWindow();
          },
          child: Text('Show Float Window'),
        ),
      ),
    );
  }

  void _showFloatWindow() {
    FlutterFloatwing.instance.createFloatWindow(
      FloatWindowParams(
        context: context,
        width: 300,
        height: 400,
        x: 100,
        y: 200,
        isDraggable: true,
        isResizable: true,
        child: FloatWindowContent(),
      ),
    );
  }
}

class FloatWindowContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.white,
      elevation: 8.0,
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('This is a float window!'),
            ElevatedButton(
              onPressed: () {
                FlutterFloatwing.instance.closeAllFloatWindows();
              },
              child: Text('Close'),
            ),
          ],
        ),
      ),
    );
  }
}

3. 权限处理

请注意,在 Android 上使用浮动窗口通常需要请求特定的权限,如 SYSTEM_ALERT_WINDOW。你可以在 MainActivity.ktMainActivity.java 中处理这些权限请求。

对于 Kotlin:

package com.example.yourapp

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import androidx.annotation.NonNull
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.android.FlutterActivity

class MainActivity: FlutterActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.SYSTEM_ALERT_WINDOW) != PackageManager.PERMISSION_GRANTED) {
            val intent = Intent(
                Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                Uri.parse("package:$packageName")
            )
            startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMISSION)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_CODE_OVERLAY_PERMISSION) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.SYSTEM_ALERT_WINDOW) == PackageManager.PERMISSION_GRANTED) {
                // Permission granted, do something
            } else {
                // Permission denied, show a message to the user
            }
        }
    }

    companion object {
        private const val REQUEST_CODE_OVERLAY_PERMISSION = 1000
    }
}

对于 Java:

package com.example.yourapp;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import io.flutter.embedding.android.FlutterActivity;

public class MainActivity extends FlutterActivity {

    private static final int REQUEST_CODE_OVERLAY_PERMISSION = 1000;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.SYSTEM_ALERT_WINDOW) != PackageManager.PERMISSION_GRANTED) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMISSION);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_OVERLAY_PERMISSION) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.SYSTEM_ALERT_WINDOW) == PackageManager.PERMISSION_GRANTED) {
                // Permission granted, do something
            } else {
                // Permission denied, show a message to the user
            }
        }
    }
}

这段代码展示了如何使用 flutter_floatwing 插件来创建和管理浮动窗口,并处理必要的权限请求。确保在发布应用时,仔细阅读插件的文档,以了解所有可用的功能和最佳实践。

回到顶部