大家好,欢迎来到IT知识分享网。
如果你想用Flutter技术在桌面端落地,从技术上来讲,你必须解决这三大难题:
1. 应用窗口化,提供窗口操作的能力;
2. 实现多窗口;
3. 对外设的支持。
前言
首先给个结论,Flutter在桌面端落地,完全是可行的
;但生态远没有官方所说的那么完善,我甚至认为其达不到stable的标准
。
目前我们的桌面设备主要有Windows、Android系统
,系统不同但UI一致,我们将在这两个平台上解决以上问题,并落地Flutter。
一、窗口化和窗口操作存在的问题
- 实现应用窗口化:即应用是窗口化展示的,同时可拖拽、可以点击应用外的地方。
Flutter Windows本身是窗口化的;
而Android默认是全屏应用,需要让普通应用支持窗口化;若是小工具性质的应用,还需要支持可拖拽、可点击应用外的地方,这些在Flutter上都是需要我们在原生实现的。 - 实现应用窗口化后,一般开发过程中,肯定会需要以下对窗口的操作:
- 应用窗体圆形、阴影效果;
- 配置应用初始的显示位置;(很多小工具可能不是居中展示)
- 从窗口变为全屏、从全屏变为窗口;
- ……
二、支持多窗口
目前Flutter是明确不支持多窗口的
。官方好像对多窗口不太感兴趣,一直没有把优先级提上来,还是停留在p4级别,具体见issue。
但是作为桌面应用,多窗口的需求是非常普遍的,因此这个技术壁垒是必须打破的。
三、窗口化实现方案
1. Windows端
Windows端Flutter默认支持窗口化,交互方式基本符合习惯,因此无需再做开发。
2. Android端
-
Android普通应用实现窗口化,是把整个应用展示成窗口的效果,但是点击外部窗口外的地方其实是不响应。
同一时间只能显示一个应用进程,这是安卓的机制,也保证了其安全性。
要实现窗口化,需要把应用Theme设置成Dialog的样式;同时设置窗口全屏,但是背景色为透明,设置点击外部Dialog不消失,即可实现应用的窗口化展示。-
设置主题
<style name="Theme.DialogApp" parent="Theme.AppCompat.Light.Dialog"> <item name="android:windowBackground">@drawable/launch_application</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowContentOverlay">@null</item> <!-- 不显示遮罩层 --> <item name="android:backgroundDimEnabled">false</item> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style>
IT知识分享网
IT知识分享网
<activity android:name=".MainActivity" android:exported="true" android:hardwareAccelerated="true" android:launchMode="singleTop" android:theme="@style/Theme.DialogApp" android:windowSoftInputMode="adjustResize"> <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/Theme.DialogApp" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
-
设置窗口全屏,但是背景色为透明,点击外部Dialog不消失
class MainActivity : FlutterActivity() { // 设置窗口背景透明 override fun getTransparencyMode(): TransparencyMode { return TransparencyMode.transparent } override fun onResume() { super.onResume() // 点击外部,dialog不消失 setFinishOnTouchOutside(false) // 设置窗口全屏 var lp = window.attributes lp.width = -1 lp.height = -1 window.attributes = lp } }
-
到这里
原生提供给Flutte一个全屏的透明窗体
,那么Flutter的视图想长成啥样都可以
。
-
-
若是小工具之类的,需要实现应用可拖拽,可点击应用区域外,这在android的实现相对复杂。我们利用
原生的窗口管理,弹出一个悬浮框,然后通过entry-point 找到Flutter层的UI
。这其实就是我们实现多窗口的思路,这里就不单纯讲解,跟着后面一起讲了。
窗口化操作
实现窗口化后,需要做很多相关的操作,我们分两个系统讲。
1. Windows端
应用窗体圆形、阴影效果
:通过window_manager插件,让应用背景色透明;然后我们在MaterialApp外面套一层Container可以设置圆角和阴影,再在外面加一次Container,加入padding以展示内层容器的阴影;小工具配置初始位置
:通过window_manager插件的setPosition可以设置位置;从窗口变为全屏、从全屏变为窗口
:通过window_manager插件可以实现全屏和退出全屏,在切换的过程中页面会闪烁,解决思路是:把透明度设置为0 → 全屏 → 透明度恢复为1
。设置透明度的方法也由window_manager插件提供。
2. Android端
对于普通应用,我们上面实现窗口化后,原生就已经为Flutter提供了一个透明的全屏窗口,因此任何窗体的操作都是Flutter层去实现的,没啥技术难度。
应用窗体圆形、阴影效果
:上面我们实现应用窗口后,其实整个应用窗体的背景色就是透明的了,因此我们比Windows少做了背景色透明这一步,然后后面的Container都是通用的,代码达到多平台复用;小工具配置初始位置
:直接通过Stack和Positioned来配置就行了。但这种场景一般使用悬浮弹框做,设置定位见后面多窗口;从窗口变为全屏、从全屏变为窗口
:Android依然很简单,只需要在全屏的时候把整个Flutter窗口的padding去除,恢复的时候加上就可以了。
多窗口的实现
首先明确一个观点,Flutter应用是基于Flutter engine,由原生提供的一个Surface画布,在这个画布上面用Skia 2绘制Flutter Widget。
也就是说本身这个应用就是一个窗口,它绝对没有能力为自己再创建一个窗口。 所以多窗口的实现,需要依赖于原生的窗口管理。下面是Android端的实现原理图,这个原理适用于任何平台。
原生新建一个Flutter engine,通过dart执行器DartExecutor执行方法executeDartEntrypoint,根据传入的字符串找到对应的方法入口点Entrypoint,从而拿到Flutter widget;
Flutter在方法上声明@pragma('vm:entry-point') 后,此方法即便在Flutter项目没有被调用到,也能编译进去,因此原生新的engine就能找到这个切入点,拿到方法返回的widget;
这是非常典型的Flutter玩法,诸如混合开发都是如此。带来的影响是存在多引擎(engine),增加一些内存,但是这个不可避免,除非你定制Flutter引擎. 目前pub上支持多窗口的库也都是这个原理,但是库的质量其实不高,大家还是自己写吧。
实现步骤
- Plugin与原生通信,由于操作都是异步的,所以务必使用
双向通信机制BasicMessageChannel
,而且需要两个通道:主应用与子窗口通道
。 - 定义接口协议,一般
至少需提供以下能力
:
IT知识分享网// 主应用打开子窗口
void open(String entryPoint, Size size, GravityConfig? gravityConfig,
bool draggable);
// 主应用关闭子窗口
void close();
// 主应用设置大小
void resize(int width, int height);
// 主应用设置位置
void setPosition(int x, int y);
// 子窗口启动app,需要支持后台唤起以及命令行启动
void launchApp();
// 子窗口自行关闭
void closeByWindows();
// 子窗口设置大小
void resizeByWindows(int width, int height);
// 子窗口设置位置
void setPositionByWindows(int x, int y);
- 各端实现,下面贴下Android端的关键代码
-
新建Flutter engine,找到Dart中的方法,此时engine就拿到了Flutter的widget实例;
engine = FlutterEngine(application) val entry = intent.getStringExtra("entryPoint") ?: "multiWindow" val entryPoint = DartExecutor.DartEntrypoint(findAppBundlePath(), entry) engine.dartExecutor.executeDartEntrypoint(entryPoint)
-
新建窗口管理类,通过FlutterViewe吸附engin,然后渲染到悬浮框的view上
///...... private var windowManager = service.getSystemService(Service.WINDOW_SERVICE) as WindowManager ///...... windowManager.addView(rootView, layoutParams) ///......///...... flutterView = FlutterView(inflater.context, FlutterSurfaceView(inflater.context, true)) flutterView.attachToFlutterEngine(engine) ///...... engine.lifecycleChannel.appIsResumed() ///...... rootView.findViewById<LinearLayout>(R.id.floating_window) .addView( flutterView, ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) )
- 实现悬浮框后,Android平台上的桌面小工具,也就顺利成章的实现了,只是在小工具这个项目的MainAcitivy上,就不需要去加载FlutterActivity了,直接启动悬浮框即可。
外设支持
usb设备在Flutter上,支持也是非常若的。具体可见我上一篇文章:Flutter桌面端实践之识别外接媒体设备
写在最后
以上是我在桌面端预研Flutter的一些经验和思路分享,如果你想在桌面端落地Flutter,我想这边文章对你是很有帮助的。
以上问题,我们遇到了,也解决了。但转念一想这么多基础的操作Flutter都不支持,这真的可以称得上Stable版本了吗?
Flutter桌面端的生态,急需我们共同建设,文中多次提起的window_manager插件就是国内出色的组织:LeanFlutter 提供的,期待Flutter桌面端越来越好!
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/9480.html