倉庫地址:https://github.com/qiuxiang/flutter-android-window
pub 地址:https://pub.dartlang.org/packages/android_window
功能
- 使用 android 原生接口實現(xiàn),可以讓 flutter app 覆蓋在其他 app 上面
- 浮窗作為 android 前臺服務(wù)運行硬爆,可完全脫離主應(yīng)用
- 默認(rèn)支持拖拽移動浮窗,且可以與 flutter 手勢共存
- 提供浮窗大小牵舱、位置控制接口
- 提供主應(yīng)用與浮窗應(yīng)用相互通信接口
安裝與配置
flutter pub add android_window
修改 MainActivity.kt
讓 MainActivity
繼承 qiuxiang.android_window.AndroidWindowActivity
:
class MainActivity : qiuxiang.android_window.AndroidWindowActivity()
創(chuàng)建 MainApplication.kt
:
package your_package // 和 MainActivity.kt 保持一致即可
class MainApplication : qiuxiang.android_window.AndroidWindowApplication()
修改 AndroidManifest.xml
的 <application>
新增屬性 android:name=".MainApplication"
:
<application
android:name=".MainApplication"
...
>
用法
main.dart:
import 'package:android_window/main.dart' as android_window;
import 'package:flutter/material.dart';
import 'android_window.dart';
@pragma('vm:entry-point')
void androidWindow() {
runApp(const AndroidWindowApp());
}
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(title: 'Flutter Demo', home: HomePage());
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => android_window.open(size: const Size(300, 200)),
child: const Icon(Icons.add),
),
);
}
}
我們需要用 @pragma('vm:entry-point')
聲明一個入口函數(shù)震捣,默認(rèn)函數(shù)名是 androidWindow
缘挽,當(dāng)然你可以隨意指定一個胞四,只是調(diào)用 open
的時候需要同時指定參數(shù) entryPoint:
井赌。
android_window.dart:
import 'package:android_window/android_window.dart';
import 'package:flutter/material.dart';
class AndroidWindowApp extends StatelessWidget {
const AndroidWindowApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return AndroidWindow(
child: Scaffold(
backgroundColor: Colors.lightGreen.withOpacity(0.9),
body: const Padding(
padding: EdgeInsets.all(8),
child: Text('Hello android window'),
),
),
);
}
}
浮窗 app 的寫法就和我們平時寫的 app 沒什么區(qū)別了蜀细,如果需要支持窗口拖拽移動舟铜,則要在最外層使用 AndroidWindow
。
最終效果:
更完整的示例請參考:https://github.com/qiuxiang/flutter-android-window/tree/main/example
浮窗與主應(yīng)用的通信
主應(yīng)用和浮窗都有 post
和 setHandler
方法用于發(fā)送消息以及設(shè)置監(jiān)聽處理函數(shù)奠衔。用法舉例:
主應(yīng)用發(fā)送消息到浮窗:
final response = await android_window.post('message_name', 'data');
浮窗監(jiān)聽并處理主應(yīng)用消息:
AndroidWindow.setHandler((name, data) async {
switch(name) {
case 'bar':
return 'data';
}
});
反過來同理谆刨。
聊聊這個庫的一些實現(xiàn)細(xì)節(jié)
- 核心功能參考了官方文檔 Adding a Flutter View to an Android app。
- 為了讓主應(yīng)用與浮窗進(jìn)行通信归斤,需要自定義
Application
痊夭,并存儲兩個互通的 channel。 - 這個庫并沒有直接用 MethodChannel脏里,而是實驗性地用了 pigeon她我,總的來說,MethodChannel 更靈活高效,而 pigeon 更規(guī)范且類型安全番舆,之后我應(yīng)該都會考慮用 pigeon 而不是 MethodChannel酝碳。