¿De verdad te atreves a aterrizar en el escritorio de Flutter?

Si desea utilizar la tecnología Flutter para aterrizar en el escritorio, técnicamente hablando, debe resolver estos tres problemas principales:
1. Ventanas de aplicaciones, que brindan la capacidad de operar ventanas,
2. Implementación de múltiples ventanas,
3. Compatibilidad con periféricos.

prefacio

En primer lugar, saquemos la conclusión de que Flutter ha aterrizado en el escritorio, 完全是可行的pero la ecología está lejos de ser tan completa como el dicho oficial, incluso creo que lo es 达不到stable的标准.
En la actualidad, nuestros dispositivos de escritorio se utilizan principalmente 有Windows、Android系统y los sistemas son diferentes, pero la interfaz de usuario es la misma. Resolveremos los problemas anteriores en estas dos plataformas e implementaremos Flutter.

1. Problemas con las ventanas y el funcionamiento de las ventanas

  1. Implementar ventanas de aplicación: es decir, la aplicación se muestra en una ventana y se puede arrastrar y hacer clic fuera de la aplicación .
    Flutter Windows en sí tiene ventanas;
    Android es una aplicación de pantalla completa de forma predeterminada, y las aplicaciones ordinarias deben ser compatibles con las ventanas ; si es una aplicación de tipo gadget, también debe ser compatible con lugares en los que se puede arrastrar y hacer clic fuera de la aplicación, que están todos en Flutter Necesitamos implementarlo de forma nativa.
  2. Después de implementar las ventanas de la aplicación, en el proceso general de desarrollo, definitivamente se requerirán las siguientes operaciones en la ventana:
    • Aplique efectos de forma circular y sombra;
    • Configure la posición de visualización inicial de la aplicación (muchos widgets pueden no estar centrados)
    • De ventana a pantalla completa, de pantalla completa a ventana;
    • ......

2. Admite múltiples ventanas

Actualmente, Flutter es 明确不支持多窗口的. El funcionario parece no estar muy interesado en la ventana múltiple, y no ha elevado la prioridad, y aún se mantiene en el nivel p4, vea el problema para más detalles .
Pero como aplicación de escritorio, la necesidad de múltiples ventanas es muy común, por lo que esta barrera técnica debe romperse.

3. Esquema de realización de ventanas.

1. Lado de las ventanas

Flutter en el lado de Windows admite ventanas de forma predeterminada, y el método de interacción es básicamente habitual, por lo que no hay necesidad de un mayor desarrollo.

2. Lado de Android

  • Android普通应用实现窗口化,是把整个应用展示成窗口的效果,但是点击外部窗口外的地方其实是不响应。 同一时间只能显示一个应用进程,这是安卓的机制,也保证了其安全性。要实现窗口化,需要把应用Theme设置成Dialog的样式;同时设置窗口全屏,但是背景色为透明,设置点击外部Dialog不消失,即可实现应用的窗口化展示。

    1. 设置主题

      <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>
      
      <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>
      
    2. 设置窗口全屏,但是背景色为透明,点击外部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
          }
      }
      
    3. 到这里原生提供给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上支持多窗口的库也都是这个原理,但是库的质量其实不高,大家还是自己写吧。

实现步骤

  1. Plugin与原生通信,由于操作都是异步的,所以务必使用双向通信机制BasicMessageChannel,而且需要两个通道:主应用与子窗口通道
  2. 定义接口协议,一般至少需提供以下能力
// 主应用打开子窗口
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);
  1. 各端实现,下面贴下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
        )
    )
    
  1. 实现悬浮框后,Android平台上的桌面小工具,也就顺利成章的实现了,只是在小工具这个项目的MainAcitivy上,就不需要去加载FlutterActivity了,直接启动悬浮框即可。

外设支持

usb设备在Flutter上,支持也是非常若的。具体可见我上一篇文章:Flutter桌面端实践之识别外接媒体设备

写在最后

以上是我在桌面端预研Flutter的一些经验和思路分享,如果你想在桌面端落地Flutter,我想这边文章对你是很有帮助的。
以上问题,我们遇到了,也解决了。但转念一想这么多基础的操作Flutter都不支持,这真的可以称得上Stable版本了吗?
Flutter桌面端的生态,急需我们共同建设,文中多次提起的window_manager插件就是国内出色的组织:LeanFlutter 提供的,期待Flutter桌面端越来越好!

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

Supongo que te gusta

Origin juejin.im/post/7120944715419090957
Recomendado
Clasificación