使用 Flutter 开发小程序的尝试(下)

6,391 阅读5分钟

在上一篇文章《使用 Flutter 开发小程序的尝试(上)》当中,我们提到了,通过减少 Dom 的更新次数、减少 Flutter Framework 的事件处理次数,优化 Flutter For Web 的性能体现。在这篇文章中,我们将继续探讨 Flutter 输出到微信小程序的方法。

本文将与您讨论以下问题

  1. 如何将种类繁多的 Flutter Widget 逐一导出;
  2. 如何保持 Flutter Widget 的组件状态,并做到脏刷新;
  3. 如何导出 Flutter Widget 的布局信息,并完整地映射到 Dom 中;
  4. 如何将上述研究成果输出到微信小程序。

如何将种类繁多的 Flutter Widget 逐一导出

如果您是一名有经验的 Flutter 开发者,一定知道 Flutter 的 Widget 类是出了名的多,仅仅是控制不透明度也可以单独设计出一个 Opacity 的 Widget。按常规的方法,我们理应在对端(Web)完整地实现一遍 Flutter 所有的 Widget,这是一件极为苦力的事情。

然而,转换一下思路总能省下不少时间。

虽说 Flutter 的 Widget 非常之多,但其实他们都继承于屈指可数的几个基类,最常见的是:

  • SingleChildRenderObjectWidget
  • MultiChildRenderObjectWidget

我们只需要把这两个基类导出,再额外地加上某些 Widget 的样式属性,即可生成完整的组件树,以 Opacity 举例,Opacity Widget 会被序列化成以下格式的 JSON 对象。

{  
    "hashCode": 123,  
    "name": "opacity",  
    "children": [...], // The children nodes  
    "constraints": {"x": 0,"y": 0,"w": 44,"h": 44},  
    "attributes": {"opacity": 0.5},
}

我们会把 Opacity 的相对定位布局、样式属性、子视图都装载一个对象里。

类似 Opacity 这样的 Widget 总共有 20+ 个,我们只需要把最基础的组件识别出来,序列化,即可完成一整个 Flutter Framework 的导出。

即使遇到像 ListTile 这样的复杂组件,这种思路也能灵活应对,因为复杂的布局组件也是通过最基础的 Widget 组合而成。

如何保持 Flutter Widget 的组件状态,并做到脏刷新

如果您对 Flutter Framework 有一定的研究,那一定知道 Framework 中存在三棵树。

我们的思路是通过导出 Element 树,为何是 Element 树而不是 Widget 树?

因为 Element 实例是可复用的,Flutter Framework的每一次界面更新,伴随的是 build 方法的重复执行,每次 build 的过程中 Widget 实例会被销毁、生成、再次销毁、再次生成,这意味每一次界面更新,都会生成全新的 Widget 对象,假如我们要导出的是 Widget 树,那么我们会很难实现 Diff 更新。

Element 对象则相反,Widget 在重复创建时,会尝试寻找可复用的 Element 对象,并重新设置 Element 的参数,此时 Element 对象是被缓存并复用的(hashCode 保持不变)。

正在基于这个原理,我们得以尝试将完整的 Element 树导出,并通过 hashCode 作为标记,实现脏刷新。

如图所示,我们可以识别出元素 E 变化,使用 Diff 的方式更新对端(Web)元素。通过脏刷新的方式,我们可以做到更优的性能表现,这种方式在各种前框架中均有使用,如 React Vue 等。

如何导出 Flutter Widget 的布局信息,并完整地映射到 Dom 中

作者在开发 MPFlutter 之前,曾借鉴过《把Flutter扩展到微信小程序端的探索》所使用的布局方式,此文提出一种方式,将特定的 Flutter Widget 使用特定的 CSS 样式重构其布局。

例如:

Center Widget,可以使用以下 CSS 样式,包裹下一个子元素,达到居中布局的效果。

{    
    display: flex;
    justify-content: center;
    align-items: center;
}

但在实际开发过程中,这种方式存在很大的问题。首先,我们无法通过 CSS 完美地复刻所有 Flutter Widget 布局,例如 AspectRatio。其次,CSS 的兼容性问题始终是一个无法绕过的坑,例如 GridView 和 Wrap 布局,只能在非常新的 WebKit 内核才能实现。

我们换一种思路解决问题,我们可以把每一个元素的相对布局(x, y, width, height)导出,然后在 CSS 中使用同样的方法映射布局。

同样是 Center Widget,我们会导出以下信息。

{  
    "hashCode": 1,
    "name": "center",
    "children": [
    {
        "hashCode": 2,
        "name": "div",
        "constraints"{"x": 25,"y": 25,"w": 100,"h": 100}
    }], // The children nodes  
    "constraints": {"x": 0,"y": 0,"w": 200,"h": 200}
}

我们在对端(Web)只需要使用 absolute 布局,加上 left top width height 即可还原布局信息。

如何将上述研究成果输出到微信小程序

我们已经成功地将 Flutter 输出到 Web,其原理无非就是使用 dart2js 把 dart 编译成 JavaScript,使用 DOM API 将视图重现。

而微信小程序呢?其实也是一样的原理,只不过,微信小程序并没有提供 DOM API 给开发者使用。我们可以使用 kbone 库,模拟 DOM API,同样能实现上述能力。

当然了,在 DOM API 以外,MPFlutter 也添加了非常实用的构建工具。

例如,MPFlutter 专门提供了 universal_miniprogram_api 库,用于在 dart 中调用小程序的各种 API。又例如,MPFlutter 专门提供了 mpkit 库,用于在 flutter 应用中调用小程序的内置组件,如 video canvas webview 等。

如果您对使用 Flutter 开发微信小程序或是 Web 应用感兴趣,不妨阅读一下我们的官方文档吧,我们的文档非常通俗易懂。

关于 MPFlutter

MPFlutter 是由 PonyCui 发起的社区开源项目,旨在推广 Flutter 至 Web / 微信小程序等 Flutter 官方社区未能涉足的平台,MPFlutter 是一个草根项目(非 KPI 驱动)。

- 请通过官网 mpflutter.com/ 尝试使用预览版本

- 请通过 GitHub github.com/mpflutter 关注我们的代码以及开发进度

- 请关注『MPFlutter 开发者』微信公众号以获取最新资讯