From 1bd4cca5276668b7cf8bab36ce97b6cd2c19feae Mon Sep 17 00:00:00 2001 From: guoshuyu <359369982@qq.com> Date: Wed, 4 Jan 2023 09:38:40 +0800 Subject: [PATCH] update --- FWREADME.md | 6 + GCH.md | 12 ++ N15.md | 112 +++++++++++++++ README.md | 13 ++ SQS.md | 316 ++++++++++++++++++++++++++++++++++++++++ SUMMARY.md | 92 +++++++++++- Z1.md | 249 ++++++++++++++++++++++++++++++++ Z10.md | 266 ++++++++++++++++++++++++++++++++++ Z2.md | 332 ++++++++++++++++++++++++++++++++++++++++++ Z3.md | 233 ++++++++++++++++++++++++++++++ Z5.md | 408 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Z6.md | 164 +++++++++++++++++++++ 12 files changed, 2198 insertions(+), 5 deletions(-) create mode 100644 GCH.md create mode 100644 N15.md create mode 100644 SQS.md create mode 100644 Z1.md create mode 100644 Z10.md create mode 100644 Z2.md create mode 100644 Z3.md create mode 100644 Z5.md create mode 100644 Z6.md diff --git a/FWREADME.md b/FWREADME.md index 9ca6c2b..f4b7771 100644 --- a/FWREADME.md +++ b/FWREADME.md @@ -67,4 +67,10 @@ * [Flutter 小技巧之优化你的代码性能](Fluttter-N13.md) * [Flutter 之快速理解混合开发里的手势事件传递](Flutter-N17.md) * [一文快速带你了解 KMM 、 Compose 和 Flutter 的现状](Flutter-CCK.md) +* [Android 开发者的跨平台 - Flutter or Compose ?](SQS.md) +* [Flutter 小技巧之快速理解手势逻辑](N15.md) + + + + diff --git a/GCH.md b/GCH.md new file mode 100644 index 0000000..93a680b --- /dev/null +++ b/GCH.md @@ -0,0 +1,12 @@ +# Flutter 工程化框架选择 + +Flutter 开发如何快速搭建工程脚手架,在繁杂的第三方插件中快速挑选合适的开发工具,如状态管理、数据库、UI控件库、混合开发模式、动画库等,满足你对 Flutter 工程的快速落地需求。 + +[Flutter 工程化选择](GCH.md) + +- [Flutter 工程化框架选择——搞定 Flutter 动画](Z1.md) +- [Flutter 工程化框架选择 — 搞定 UI 生产力](Z3.md) +- [Flutter 工程化框架选择 — 搞定数据存储选型](Z5.md) +- [Flutter 工程化框架选择 — 混合开发的摸爬滚打](Z6.md) +- [Flutter 工程化框架选择 — add-to-app 的指路明灯](Z10.md) +- [Flutter 工程化框架选择 — 状态管理何去何从](Z2.md) \ No newline at end of file diff --git a/N15.md b/N15.md new file mode 100644 index 0000000..a81c80f --- /dev/null +++ b/N15.md @@ -0,0 +1,112 @@ +# Flutter 小技巧之快速理解手势逻辑 + +又到了小技巧系列更新时间,今天我们主要分享 Flutter 里的手势触摸逻辑,其实在很久之前我就写过 [《面深入触摸和滑动原理》](https://juejin.cn/post/6844903841742192648)相关的源码分析文章,但是最近有人说源码分析看不懂,有没有简要好理解的,**那么本篇就用更简单的角度,带大家理解 Flutter 里的手势相关逻辑**。 + +# GestureDetector + +不管你用 `InkWell` 、`InkResponse` 、`TextButton` 还是 `ElevatedButton` , 它们针对手势的处理逻辑都是来自于 `GestureDetector` ,也就是理解 Flutter 的手势处理逻辑入门,核心可以从分析 `GestureDetector` 开始。 + +> 其实更严格意义上讲,手势事件是来自 `Listener` , `GestureDetector` 是针对 `Listener` 进行了封装,只是为了避免复杂的源码分析,这里就不做展开,你可以简单理解为:**并不是所有的控件都会响应手势,只有带有 `Listener` 的才会响应**,这主要体现在触摸事件的递归响应上。 + +**在 `GestureDetector` 里关于事件的响应逻辑主要来自于各种 `GestureRecognizer` (手势识别)的实现逻辑**,不同的手势识别逻辑会响应不同手势结果,相互竞争,最后在 `GestureArenaManager` (竞技场) 决定出一个胜利者。 + +简单来说,在竞技场里手势基本遵循两个逻辑: + +- **每个 Recognizer 都可以随时选择失败退出,当竞技场只有它一个的时候它就赢了** +- **每个 Recognizer 都可以随时宣布自己获得胜利,这时其他 Recognizer 也将不再响应** + +那么如下图所示,在 `GestureDetector` 里主要有这 8 种 `GestureRecognizer` 在处理不同的场景,他们会根据用户使用 `GestureDetector` 时的参数搭配来参与到事件竞技场里。 + +![](http://img.cdn.guoshuyu.cn/20221215_N15/image1.png) + +举个例子,当你使用了 `GestureDetector` 并配置了 `onTap` 、`onLongPress` 和 `onDoubleTap` ,它们是如何分别响应手势事件的? + +**这里的核心逻辑就在于 deadline (时间) 的处理,不管是 `onLongPress` 还是 `onDoubleTap` 都是靠 deadline 来判断胜负**。 + +![](http://img.cdn.guoshuyu.cn/20221215_N15/image2.png) + +例如,当用户只是普通点击时,如下代码所示,因为默认 `LongPressGestureRecognizer` 的 deadline 是 500 毫秒,所以**在定时器达到 500ms 响应之前,就会因为 `PointerUpEvent` 导致长按定时器停止,无法触发响应长按事件**。 + +反之如果没有 `PointerUpEvent` 产生,那么 500 ms 之后 `LongPressGestureRecognizer` 就会响应,直接宣布胜利(accepted)。 + +![](http://img.cdn.guoshuyu.cn/20221215_N15/image3.png) + +> 默认情况下 `GestureDetector` 是不支持修改 deadline ,只有直接使用 `LongPressGestureRecognizer` 时才可以修改 deadline 的时长。 + +类似的逻辑在 `DoubleTapGestureRecognizer` 下也有,DoubleTap 的 deadline 是 300 毫秒,当用户首次点击时会注册一个定时器,**如果 300 毫秒以内用户没有产生新的点击,那么 `DoubleTapGestureRecognizer` 就会宣布“失败“退出竞技**,反之如果在 300 毫秒内有新的点击,则直接宣布“获胜”,响应 DoubleTap 回调。 + +![](http://img.cdn.guoshuyu.cn/20221215_N15/image4.png) + +那这时候有人就要问了:“*在 `DoubleTap` 过程中,为什么不会触发 `onTap`*” ? 这就需要说到 `TapGestureRecognizer` 的触发逻辑。 + +继续前面 `GestureDetector` 并配置了 `onTap` 、`onLongPress` 和 `onDoubleTap` 的例子,在用户只做普通点击的时候,前面说过: + +- `LongPressGestureRecognizer` 的定时器 deadline 还没到 500 毫秒会因为 Up 事件而导致失败退出 +- `DoubleTapGestureRecognizer` 会因为定时器超过 deadline 300 毫秒,没有下一个点击而宣布退出 + +**那么在 Long 和 Double 都失败的情况下,此时 `GestureArenaManager` (竞技场) 里的成员就只有 `TapGestureRecognizer`** ,这时候竞技场会 close ,会触发竞技场的 `sweep` 逻辑,直接让最后剩下来的 `Recognizer `“胜利”,响应 `onTap` 事件。 + +> 所以 `TapGestureRecognizer` 靠的是胜者为王。 + +所以基于这个例子,配合一开始说的两个逻辑,就可以直观的理解 Flutter 手势竞技场里的响应逻辑和关键 `deadline` 的作用。 + +# 多个 GestureDetector + +那么前面都是只有一个 `GestureDetector` 的场景,如果有两个呢?如下代码所示,在嵌套两个 `GestureDetector` 下,它们的响应逻辑会是怎么样的? + +![](http://img.cdn.guoshuyu.cn/20221215_N15/image5.png) + +当区域内有两个 `GestureDetector` 的时候,用户在普通点击时,因为 deadline 影响,依旧会是在竞技场 `close` 时才响应 `onTap` , **但是不同在于此时竞技场里还会有多个 `Recognizer` 存在,这时候只有排在列表的第一个的 `Recognizer` 可以赢得事件**,也就是上门代码里的红色 200x200 小方块。 + +![](http://img.cdn.guoshuyu.cn/20221215_N15/image6.png) + +因为对于多个 `GestureDetector` 的情况, `Recognizer` 在竞技场列表(`List 同时对于单个 `GestureDetector` 而言,`TapGestureRecognizer` 会是 `_recognizers` 的第一个,所以 `first` 会是响应了 `TapGestureRecognizer` ,详细逻辑可以看 [《面深入触摸和滑动原理》](https://juejin.cn/post/6844903841742192648) 。 + +所以简单理解: + +- 两个 `GestureDetector` 在竞技场里的 `member` 列表排序时,作为 child 的红色 `GestureDetector` 因为 HitTest 递归会被排前面 +- `GestureDetector` 内部 `TapGestureRecognizer` 会在其内部 `_recognizers` 排第一 + +所以 `member.first` 最终响应了 `TapGestureRecognizer` ,回到上面两个定律,如果结合多个 `GestureDetector` 的场景,就应该是: + +- **每个 Recognizer 都可以随时选择失败退出,当竞技场只有它一个的时候它就赢了;如果不止一个,那么在竞技场 `close` 时, `member.first` 会获得响应** +- **每个 Recognizer 都可以随时宣布自己获得胜利,这是其他 Recognizer 也将不再响应** + +##### + +# 进阶补充 + +前面简单介绍了 Flutter 的手势响应的基础逻辑,这里再额外补充两个知识点。 + +首先,*当用户在长按的时候, `GestureDetector` 何时会发出 `onTapDown` 事件*? + +这其实就涉及了另外一个 deadline 参数,当用户在长按的时候,Recognizer 还会触发另外一个定时器,然后通过执行 `didExceedDeadline` 来发出 `onTapDown` 事件。 + +![](http://img.cdn.guoshuyu.cn/20221215_N15/image7.png) + +那么问题又来了,既然长按会触发 `onTapDown` 事件,如果点击区域内有两个 `TapGestureRecognizer` ,长按的时候因为定时器都触发了 `didExceedDeadline` ,那是不是两个都会收到 `onTapDown` 事件 ? + +![](http://img.cdn.guoshuyu.cn/20221215_N15/image8.png) + +答案是:会的!**因为定时器都触发了 `didExceedDeadline`,从而都发出了 `onTapDown` 事件,所以两个 `onTapDown` 回调都会执行,但是后续竞争只会有一个控件能响应 `onLongPress` 。** + +> 另外,如果不是长按导致的 Down 事件, 是不会导致两个 `GestureDetector` 都触发回调 `onTapDown` 回调。 + +第二个补充的是 `Listener` , 如果你还想深入去看 `GestureDetector` 的实现,你就会发现 `GestureDetector` 对 `Listener` 的封装也许和你想象不大一样, **因为 `Listener` 的封装只用到了 `PointerDown` ,并没有用到 `onPointerUp`** ,那 `GestureDetector` 是怎么响应 Up 和 Move 事件? + +![](http://img.cdn.guoshuyu.cn/20221215_N15/image9.png) + +这就需要说到前面介绍 [《面深入触摸和滑动原理》](https://juejin.cn/post/6844903841742192648) 里的源码分析,但是为了简单,我们这里只说结论: + +> 因为只有响应了 `PointerDown` 事件,对应的 `GestureRecognizer` 才能被添加到 `GestureBinding` 的 `PointerRouter` 事件路由和 `GestureArenaManager` 事件竞技中,而**后续的 Up 和 Move 事件主要是通过 `GestureBinding` 来处理**。 + +更简单的说,**就是只有响应了 `PointerDown` 事件,控件的 `Recognizer` 才能响应后续统一处理的其他手势事件,而其他事件不需要在 `Listener` 这里获取回调**。 + + + +# 结束 + +那本篇的小技巧到这里就结束了,本篇主要是用更直观和简单的方式,帮助大家理解 Flutter 里的触摸响应逻辑,如果对更详细实现感兴趣,可以结合 [《面深入触摸和滑动原理》](https://juejin.cn/post/6844903841742192648) 帮助理解,如果你还有什么感兴趣或者有疑惑的,欢迎留言评论~ + diff --git a/README.md b/README.md index 518d6b9..0f9e67e 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,19 @@ * [Flutter 小技巧之优化你的代码性能](Fluttter-N13.md) * [Flutter 之快速理解混合开发里的手势事件传递](Flutter-N17.md) * [一文快速带你了解 KMM 、 Compose 和 Flutter 的现状](Flutter-CCK.md) + * [Android 开发者的跨平台 - Flutter or Compose ?](SQS.md) + * [Flutter 小技巧之快速理解手势逻辑](N15.md) + + [Flutter 工程化选择](GCH.md) + + - [Flutter 工程化框架选择——搞定 Flutter 动画](Z1.md) + - [Flutter 工程化框架选择 — 搞定 UI 生产力](Z3.md) + - [Flutter 工程化框架选择 — 搞定数据存储选型](Z5.md) + - [Flutter 工程化框架选择 — 混合开发的摸爬滚打](Z6.md) + - [Flutter 工程化框架选择 — add-to-app 的指路明灯](Z10.md) + - [Flutter 工程化框架选择 — 状态管理何去何从](Z2.md) + + diff --git a/SQS.md b/SQS.md new file mode 100644 index 0000000..f81405c --- /dev/null +++ b/SQS.md @@ -0,0 +1,316 @@ +# Android 开发者的跨平台 - Flutter or Compose ? + +hello 大家好,我是 Flutter GDE 郭树煜,同时也是 Github GSY 项目的负责人,比如 GSYVideoPlayer ,今天要给大家分享的主题是 Android 开发者的跨平台 - Flutter or Compose ? 今天的分享不会是很深入的技术内容,更多可能是科普向,特别是对 Flutter 和 Compose 还不是特别了解的 Androider 们,通过数据帮助大家来理解 Flutter 和 Compose。 + +# 一、Android 开发和跨平台开发的现状 + +首先我们聊聊现状,不知道你有没有这种感觉,就是现在的 Android 开发者很多时候不再是 Android 开发,或者说不是纯 Android App 开发,目前简单总结大致可以分为两类: + +- **以 Android 为技术栈的嵌入式开发**,如电视、手表、教育平板、监控等,这里面近年来又以车机开发较为突出。 +- **大前端开发**,从 Android 、 iOS 、Web 到小程序等各类面向 UI 相关的工作内容 + +这个现状的具体原因在于:**Android 开源让它可以更好地被各行各业消化,同时这些年 App 开发体系越发成熟**。 + +> 我还记得 2015 年那会我带的移动团队开发一款 App ,标配就是 Android 和 iOS 各自 4-5 个人,还经常需要加班加点,项目里会用到大量第三方的开源框架。 + +而现在随着官方这些年 Jetpack 体系的成熟,Android 开发者更多会聚焦到 Jetpack 体系内,比如:`Room`、`CameraX`、`Hilt`、`Navigation`、`Paging`、`WorkManager`、`Emoji2`、`DataStore`、`Media`、`Startup` 等,而 Compose 就是 Jetpack 里的最大亮点之一,**如果说 Android 开发现在进入了 Jetpack 纪元,那 Jetpack Compose 就是 Jetpack 纪元里的超新星**。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image1.png) + + + +**Jetpack Compose 是 Android 新推出的声明式 UI 工具包,它主要是用于简化和加速 Android 上的 UI 开发,同时 Compose 经过 Jetbrains 开源的 compose-jb 支持到跨平台开发的能力**。 + +这里三个重点: + +- Jetpack Compose 是 Android 的 UI 的工具包 +- Jetbrains 开源了 compose-jb 支持跨平台 +- Compose 是声明式开发 + +**从大前端开发的角度看,声明式开发可以说是当今的主流**,React、Vue、SwiftUI、Flutter 等都是声明式编程,还有如近期发布的 HarmonyOS 3.1版本,也着重标注了它将全面进入声明式开发阶段。 + +> 所以不管你喜不喜欢,声明式开发是主流,虽然它近期看起来不会完全替代 Android 的 XML 布局,但是这是主流的趋势。 + +再说跨平台,如今跨平台开发相信大家都不会陌生,各类跨平台开发框架都相当成熟,**但是对于 Android 开发来说, Flutter 和 Compose 确实会显得比较特殊**,因为它们都是属于 Google 开源的产品,都能支持跨平台,所以可以对于部分 Android 开发者来说会陷入困惑:我该选哪个? + +针对这个疑惑,我们先看一些数据对比,首先如下图所示,是 Google Trend 上 Flutter 和 Compose 在全球和国内的一些关键词搜索热度对比,可以看到**全球范围内都是在稳步上升,但是在国内是属于强烈波动的状态,也就是国内有很多人对于 Flutter 和 Compose 都还处于徘徊观望状态**。 + +| ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image2.png) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image3.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | + +另外也可以看到 , Flutter 出来比较久,所以如今他的热度会比较高,当然这也是现在跨平台的需求剧增有关系,很多平台开发人员不再仅仅 ”安居” 于自己的平台,**并且从国内的数据可以看到,国内对于跨平台的热情其实一直很高**。 + +> 虽然 Flutter 从发布以来一直争议不断,但是这些年下来 Flutter 也证明了自己的价值,后面我们会有单独的详细的数据分析。 + +再看 StackOverFlow 的数据,可以看到 Flutter 和 Compose 都在稳步上升,这里面 Compose 相关看起来比较少的原因,和它发布时间还较短有关系,而 Flutter 从目前的占有比例上看其实不算低了。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image4.png) + +既然说 Flutter 和 Compose 就不得不说 Dart 和 Kotlin ,同样是 StackOverFlow 的数据,可以看到虽然他们发布都有一段时间了,但是其实是在 2017 年开始它们的占有率才出现了爆发式的上升,这是为什么呢? + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image5.png) + +其实这个现象和 2017 Google I/O 大会有直接关系: + +- Kotlin 是 2012 年开源的,而 2017 Google I/O 大会上官方正式支持将 Kotlin 作为 Android 开发的 First-Class(一等公民)语言 + +- Dart 亮相于 2011 年,而 2017 年 Google I/O 正式向外界公布了 Flutter,Dart 是它的主要开发语言 + +> 所以 Kotlin 本来不温不火,但是因为 Android 它开始被更多人所使用,同样 Dart 本来已经快被雪藏,却因为 Flutter 而焕发第二春,**看起谷歌在这方面运营能力还是很值得肯定的**。 + +我们再看一份数据,这是目前 Kotlin 和 Dart 在 Github 上关于 PR 和 Star 的数据趋势,可以看到开始增长的时间节点依然是 2017,不过 Dart 的关注度还是从 Flutter 正式版发布后才有爆发式增长, 而从增长上看,不管是 Kotlin 还是 Dart 目前都很过得去,**不过目前它们的主要应用场景还是局限在 Android 和 Flutter**。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image6.png) + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image7.png) + + + +再看国外调研的一份关于 Flutter 和 Compose 在 Droidcon 上有多少次关于这两个项目的演讲主题和结果,虽然数据相对较小和局限,但是可以看到 Flutter 自发布以来每年都在稳定的主题,而 Compose 在稳定版发布后有明显爆发式增长 + +| ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image8.png) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image9.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | + + + +最后,恰好 GitHub 也发布了 2022年度报告,其中一些数据还是值得我们关注,例如在开发语言增长上, Kotlin 排进了前十,另外移动端开发依旧是开源主流之一,其中 Kotlin、Dart 、Flutter 、Android 都是主要的对象。 + +| ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image10.png) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image11.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | + +当然,就像前面说的,**不管是 Dart 还是 Kotlin ,它们主要是场景还是在于 Flutter 和 Android ,通过 [TIOBE](https://www.tiobe.com/tiobe-index/) 在 2022 年 11 月的编程语言指数上可以看到, Dart 和 Kotlin 还是未能进入 TIOBE 指数前 20 名**,该指数每月更新一次,在编程语言的流行程度上有比较高的参考价值。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image12.png) + +不是说其他领域不能用,比如 Kotlin 和 Dart 都能写后端,我以前自己也有一些后端项目,当时为了方便,直接把Android 上 kotlin 的逻辑复用到服务端,而 Dart 本身通过 ffi 直接支持数据库等的能力,也让它可以脱离 Flutter 作为单独的后台服务运行,这在我以前的文章也分享过,目前支持 Dart 的 ffi 数据库也有好几款了,所以不是说不能用不支持,只是相对还是少很多。 + +我们最后在看一份数据, [RedMonk 2022 Q3](https://redmonk.com/sogrady/2022/10/20/language-rankings-6-22/) 的调查里,在 Github 和 StackOverflow 的流行指数上还是比较靠前的,Kotin 排在第 17 位,而 Dart 排在第 19 位,从应用领域看,这两种语言目前的势头还是不错的。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image13.png) + + + +> 所以总结来说: **Android 成就了 Kotlin ,而 Kotlin 成就了 Compose ,同样 Flutter 成就了 Dart **。 + +# 二、Compose 对于 Android 开发来说是什么 + +接下来进入第二个主题,Compose 对于 Android 开发来说是什么: + +- **Compose 对 Android 来说最重要的是新的现代化 UI 开发框架,Compose 提供了 Andorid 声明式的 UI 开发能力,这是核心的重点**。 +- **Jetbrains 开源的 compose-jb 让 Compose 得到了额外的增值,可以把开发能力拓展到其他平台,这是附带价值**。 + +> 所以从这个角度看,如果你继续做 Android 开发,那么学会 Compose 是必须的技能,因为它未来可能会是 Android 上主流的开发模式,尽管它目前还比较年轻。 + +说他年轻,是因为目前他的正式版发布也就一年的时间,目前 Jetpack Compose 和 compose-jb 的正式版本都在 1.2 ,第一个正式版是都是在 2021 年发布,并且在近一年时间内发布了两个大版本,另外可以看到 compose-jb 都是在 Jetpack Compose 发布之后再跟进版本更新,**所以 Jetpack Compose 是一些的基础核心**。 + +| ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image14.png) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image15.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | + +*那么大家肯定很关心一个问题,有哪些大厂 App 在使用 Compose* ? + +目前关于 Flutter 的技术文章我们可能看到过很多大厂的分享,但是 Compose 相关的比例却不多,所以我也只能从我自己手机里一些常用软件的归类,目前恰好可以找到如下图所示的 6 款 App 里有存在 Compose 的痕迹。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image16.png) + + + +> **当然,使用 Jetpack Compose 和使用 compose-jb 其实是两码事**,所以我也不知道上述产品是否也有使用 compose-jb 的场景,因为其实 compose-jb 目前在跨平台开发上的体验还是有些差别的。 + +**而作为全新的独立 UI 框架, Compose 自然不会像以前一样的控件体系**,类似的声明式布局方式对于 Android 开发者来说可能会有一定的学习成本,最直观的就是 Compose 里会使用状态管理和绑定,而不会是像以前一样拿 `view.xxxx` 这样的操作方法,这是一个思路转变的过程。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image17.png) + +另外从反编译后的代码里可以看到,**Compose 里的控件和原生控件并不是一个体系**,大家如果去看编译后的内容,就会发现例如 `BOX` 这样的控件在编译后是通过 `ComposerKt` 和 `BoxKt` 等的 framework 实现来完成的布局与绘制。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image18.png) + +所以 **Compose 编译后不是转化为原生的 Android 上的 View 去显示**,而是依赖于平台的 `Canvas` ,在这点上和 Flutter 有点相似,简单地说可以理解为 Compose 是全新的一套 View 。 + +> 另外友情提示:虽然是全新的 View ,但是 `Compose` 的组件在 Android 上是可以显示了布局边界。 + +**另外 Compose 里的代码基本都是可以被混淆的**,所以开启混淆之后代码的压缩率也很高。 + +在 Compose 里两棵树的设计,虚拟 Dom 风格的 SlotTable 和 React 设计类似,从而保证了 LayoutNode 的性能,Compose 的核心设计开发人员 Jim Sproch 之前是 React 的核心开发。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image19.png) + +> **其实这也是一种大前端的趋势,不管你是客户端还是前端开发,你的能力都应该可以得到应用。** + +再说 compose-jb ,如下图所示, compose-jb 在跨平台开发体验上还是有所区别,**Compose 目前是通过多个模块实现来支持多平台,所以目前 Jetpack Compose 和 compose-jb 有一些“割裂”**, compose-jb 本质上是将 compose-desktop,compose-web 以及 compose-android 进行了整合,特别是在 Web 端,想要达到 Flutter 一样共享代码的比例还需要继续努力。 + +![](http://img.cdn.guoshuyu.cn/20221027_M1/image7.png) + + + +如上图所示的 iOS 其实目前也已经进入[实验阶段](https://github.com/JetBrains/compose-jb/issues/2397#issuecomment-1277536570) ,[` androidx.compose.ui.main.defaultUIKitMain` ](https://github.com/JetBrains/compose-jb/blob/master/experimental/examples/falling-balls-mpp/src/uikitMain/kotlin/main.uikit.kt) 相关的支持距离正式发布可以期待,而 compose-jb 目前对跨平台的支持的 “割裂” 也来自于此,比如 Web 下的代码会是这种感觉。 + +```kotlin +import org.jetbrains.compose.web.dom.* +import org.jetbrains.compose.web.css.* + +fun main() { + var count: Int by mutableStateOf(0) + + renderComposable(rootElementId = "root") { + Div({ style { padding(25.px) } }) { + Button(attrs = { + onClick { count -= 1 } + }) { + Text("-") + } + + Span({ style { padding(15.px) } }) { + Text("$count") + } + + Button(attrs = { + onClick { count += 1 } + }) { + Text("+") + } + } + } +} +``` + +> **另外值得一提的是,Compose for Wear OS 的 1.0 稳定版也发布了**。 + +还有一个关键点就是,如果使用 compose-jb ,你可能还会接触到 Kotlin/JS 、Kotlin/Native、KMM 等对应的名词,这部分对于 Android 开发来说也算是额外的学习成本,**所以在跨平台体验上目前 compose-jb 的路才刚刚开始,一致性的跨平台开发体验相信会是 compose-jb 努力的方向**。 + +其实前面所说的“割裂”问题,目前可以看到官方也在有序推进,其中就有 desktop 的部分代码已经挪到了androidx 上,从这里看或者统一的 Compose lib 并不遥远。 + +![](http://img.cdn.guoshuyu.cn/20221027_M1/image8.png) + + + +另外 **Jetpack Compose 现在针对 Compose 体系的官方依赖支持,也推出了 Gradle BOM (Bill of Materials) 依赖模式**,用于指定每个 Compose 库的稳定版本。 + +> 目前第一个版本是 Compose 2022 年 10 月版,现在最新的应该是 2022.11.00。 + +那么 Gradle BOM 的作用是什么?如下图所示,**简单说就是不用再单独写依赖版本, 我们可以通过仅指定 BOM 的版本管理所有 Compose 库版本**,BOM 本身具有指向不同 Compose 库的稳定版本的链接,所以它们可以很好地协同工作。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image20.png) + +*当然,聊到这个就顺便聊一聊制约 compose-jb 的问题:缺少插件社区*。 + +这其实是跨平台领域必不可少的配置:**前端有 npm 、Flutter 有 pub,你可以通过它们的中央官网搜索你想要的库,查看它们的热度,版本,兼容和使用量等等信息,设置官方认证和安全保障,甚至还有趋势推荐,库支持的平台有哪些等等,但是 Maven 时代在这方面一直很弱**,这是 compose-jb 后续发展需要解决的最大问题之一。 + +最后说到 Compose 就不得不说 Android Studio :**Compose 和 Android Studio 版本其实有很强的依赖关系**,例如: + +1、Android Studio Arctic Fox (白狐狸) 开始支持 Compose preview 、 Interactive preview 和 Deploy to Device ,并开始支持 Live Edit of literals 和 Layout Inspector 。 + +2、Android Studio Bumblebee (小蜜蜂) Layout Inspector 才支持检查 Compose 布局中的语义信息,并且默认启用 interactive preview,interactive preview 允许在预览时进行交互,就像是已经运行到设备上工作一样。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image21.gif) + +> 这里的 Preview interactive 模式直接在 Android Studio 中运行,无需运行模拟器,这会导致一些限制:没有网络访问权限、没有文件访问权限、某些上下文 API 可能不完全可用。 + +值得一提的是, **Compose 目前不支持 hotload**,Android Studio 的 apply code 的实用程度相信大家深有体会,不过 Compose 有支持 preview 时的文本 Live Edit of literals ,这也算是一个可将一下的工具。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image22.gif) + + + +3、Android Studio Chipmunk (小松鼠) 开始支持 [`animatedVisibility`](https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.android.com%2Fjetpack%2Fcompose%2Fanimation%23animatedvisibility) 的动画预览,动画预览和`animatedVisibility`需要使用 Compose 1.1.0 或更高版本。 + +| ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image23.gif) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image24.gif) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | + + + +4、Android Studio Dolphin (海豚) 支持 Compose Animation Coordination,如果你的动画是用于 composable preview,那么可以使用 [Animation Preview](https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.android.com%2Fjetpack%2Fcompose%2Ftooling%23animations) 来同时检查和协调所有动画,甚至还可以冻结特定的动画,并且支持 composables 何时进行或不进行重构 + +| ![](http://img.cdn.guoshuyu.cn/20220916_AS/image1.gif) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image25.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | + +最后,如果你好奇现在哪些大厂在使用 compose-jb ,目前我还没找到比较有价值的数据,不过据称 JetBrains 目前就已经将 Toolbox 应用通过 compose-jb 实现并且发布使用。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image26.png) + + + + + +# 三、 Flutter 对于 Android 开发来说是什么 + +接下来我们聊聊 Flutter 对于 Android 开发者来说又是什么?**你如果选择还是只做 Android ,那么 Flutter 对你来说可能意义不大**,Flutter 其实起源于 Chrome 内部团队,所示它其实和 Android 没什么关系,**但是如果你对其他平台感兴趣,那么 Flutter 绝对是你不错的选择**。 + +Flutter 正式版是在 2018 年发布,发布之后几乎就是每个季度都会更新一个大版本,如下图所示可以看到 Flutter 的推进和迭代速度是很快的,这也侧面反映了 Flutter 社区的活力。 + + + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image27.png) + + + +**如果大版还不够有说服力,那么近期的小版本迭代速度,这也可以体现 Flutter 社区的生命力,当然也侧面反映出全平台支持的困难,需要解决的问题很多**,因为跨平台需要兼容处理的问题会更多,这也是 Flutter 坑多的原因。 + +![](http://img.cdn.guoshuyu.cn/20221124_SQS/image28.png) + + + +我们再从 Github 发布的 2022 年度报告上看,在各项开源数据里 Flutter 都名列前矛: + +- 顶级开源项目里按贡献者排序 Flutter 排第三 +- 顶级开源项目里按首次贡献者统计的,Flutter 在获取贡献“一血”里排名第四 +- 在每个项目的贡献者数量里 Flutter 排名第二 +- 在外部贡献者百分比里 Flutter 排名第三 + +| ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image29.png) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image30.png) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image31.png) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image32.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | + +> **由此可以看到 Flutter 强大的生命力,特别是来自社区的活跃,Flutter 每个版本发布都会合并大量来着社区的 PR ,这也是 Flutter 这些年来快速发展的原因**,同时也是在本次 Github 发布的报告里,随处可以见 Flutter 的原因。 + +*那么说一千道一万,目前有哪些我们熟知的企业在使用 Flutter 呢*? + +如下图所示是目前我手机里有 Flutter 存在的 App ,另外还有过去三年里 Flutter 在跨平台领域的占有率增长,**可以看到 Flutter 目前在跨平台领域的存在感并不低,其实介绍这么多数据,只是为了和大家说明一个问题:Flutter 已经不算小众了**。 + +> 百度网盘、转转、阿里云盘、闲鱼、微信、掘金、企业微信、微博、B站漫画、阿里云、 UC浏览器、优酷视频、钉钉、360摄像头、网易邮箱、天猫精灵、链家、美团众包、凤凰新闻、腾讯课堂、喜马拉雅、携程旅行、贝壳找房、WPS、学习强国、唯品会、同花顺 + +| ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image33.png) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image34.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | + +回到 Flutter 本身上,**Flutter 的优势体现在于 single codebase ,它是真的做道一套代码编译成不同平台的 native 代码运行**,Flutter 跨平台最特殊在于它不依赖平台控件,控件最后都是利用 Skia 通过平台 GPU 渲染出来。 + +![](http://img.cdn.guoshuyu.cn/20221027_M1/image9.png) + +**所以 Flutter 里的控件基本和平台无关,这对于跨平台来说有很大优势**,因为控件只和 Flutter 框架有关系,在 Andriod 上得到的效果,在 iOS 上也可以得到一样的结果,所见即所得,这对开发效率有很高的帮助。 + +> 但是这也大大提供了框架维护的工作量,例如文本输入框 `TextFiled` 和 `Text` 都针对移动平台和 PC 平台,需要在控件内部兼容手势触摸和键鼠操作,,这也是为什么类似 Global Selection 等功能会到 3.3 才开始有官方支持。 + +所有 Flutter 需要面对的 Bug 也很多,在面对越来越多不可控的底层渲染问题之后,**Flutter 开始自建渲染引擎,因为直接使用 Skia 已经无法满足日益增长的 Bug 和性能极限,所以官方开始了自研[渲染引擎Impeller](https://link.juejin.cn/?target=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FGptJbPXPediNRc4KvZzr6g)** 。 + +因为 Flutter 团队现在出现问题每次都要和 Skia 团队沟通,然后等跟进,这样的节奏太慢了,从前面官方的小版本更新日志上就可以看出目前 Flutter 的迭代速度依然很夸张。 + +> **这次自研的 Impeller 本质上是为了解决 Skia 需要运行时遇到的问题,让 Impeller 可以直接在编译器就完成 GLSL 和 MSL ,不需要 SKSL 从而提高了性能和运行时的稳定性** ,目前优先在 iOS 平台上开始支持 ,配合 Metal 做优化,后续如果没问题也会同步支持 Android 和 Vulkan 。 + +从这个角度猜测,Flutter 在 Skia 遇到的问题 compose-jb 也很可能会遇上,而如果后续 Impeller 项目进展顺利,那它或者并不会局限在 Flutter ,也许也可以拓展支持到 compose-jb上, + +> 其实**在 Jetbrains 的开源项目里有一个叫 [skiko ](https://github.com/JetBrains/skiko) 的项目**,Skiko(Kotlin 的 Skia 的缩写)是一个图形库,它支持 Kotlin/JVM 、Kotlin/JS 、Kotlin/Native 等相关实现。 + +所以自研发引擎的模式并不奇怪,随着项目的发展和深入,很多底层问题没办法快速推进就会反推自研,例如 [Hermes 在 RN 0.7 成为默认 Engine](https://juejin.cn/post/7140474062211383333) 也是类似问题的体现,**自研底层属于是一个负责任的开源团队的必经之路**。 + +最后,**如果真要总结 Flutter 对于 Android 开发者最大的意义,就是拥有开发其他平台的能力**,通过 Flutter 去了解和接触开发其他平台的,同时还能提前习得大部分 Compose 的开发能力,如下图所示是 Compose 和 Flutter 的代码对比: + +| ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image35.png) | ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image36.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | + +**所以这也是 Andorid 开发会了 Flutter 就离 Compose 不远的原因,反过来也是**。 + +这里可以顺便推荐下大佬的 Flutter 和 Compose 入门项目 [FlutterUnit](https://github.com/toly1994328/FlutterUnit) 和 [ComposeUnit](https://github.com/toly1994328/ComposeUnit) ,上面的代码就是来自这两个项目,里面列举了很多案例,特别适合入门的小伙伴。 + +| ![](http://img.cdn.guoshuyu.cn/20221124_SQS/image37.png) | ![image-20221115171152896](http://img.cdn.guoshuyu.cn/20221124_SQS/image38.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | + + + +# 最后 + +本次分享的核心还行想告诉大家,目前 Compose 和 Flutter 成熟度已经不错了,当你的领导和你说,Kotlin、Dart 还不够普及,Flutter 和 Compose 还太小众的时候,或者你就可有一些数据依据。 + +最后可以总结的是: + +- Compose 的核心还是 Android 的 UI 库,做 Android 的必须掌握这个未来的能力,至于 compose-jb 的跨平台增值能力,还有一段路要走。 +- Flutter 的核心是全平台更稳定的支持,更有社区活力,特别是在相对“冷清”的桌面端上的优势,在目前公开信息上,钉钉、字节和企业微信都在 Flutter 桌面端开始有投入。 + +**所以结合自己的路线,选哪个应该就很清楚了,而不管选择哪一个,都会对另外一个框架有提前铺垫的作用,所以也不用担心得此失彼,感兴趣的小伙伴可以开始动起来了**~ diff --git a/SUMMARY.md b/SUMMARY.md index 10324b1..c21516c 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -70,76 +70,158 @@ * [番外](FWREADME.md) * [Flutter 跨平台框架应用实战-2019极光开发者大会](Flutter-jg-meet.md) + * [Flutter 面试知识点集锦](Flutter-msjj.md) + * [全网最全 Flutter 与 ReactNative深入对比分析](qwzqdb.md) + * [Flutter 开发实战与前景展望 - RTC Dev Meetup](Flutter-rtc-meetup.md) + * [Flutter Interact 的 Flutter 1.12 大进化和回顾](Flutter-Interact-2019.md) + * [Flutter 升级 1.12 适配教程](Flutter-update-1.12.md) + * [Spuernova 是如何提升 Flutter 的生产力](Flutter-Supernova.md) + * [Flutter 中的图文混排与原理解析](Flutter-TWHP.md) + * [Flutter 实现视频全屏播放逻辑及解析](Flutter-Player-Full.md) + * [Flutter 上的一个 Bug 带你了解键盘与路由的另类知识点](Flutter-keyboard-rs.md) + * [Flutter 上默认的文本和字体知识点](Flutter-Font-Other.md) + * [带你深入理解 Flutter 中的字体“冷”知识](Flutter-Font-Cool.md) + * [Flutter 1.17 中的导航解密和性能提升](Flutter-nav+1_17.md) + * [Flutter 1.17 对列表图片的优化解析](Flutter-Image+1_17.md) + * [Flutter 1.20 下的 Hybrid Composition 深度解析](flutter-hy-composition.md) + * [2020 腾讯Techo Park - Flutter与大前端的革命](Flutter-TECHO.md) + * [带你全面了解 Flutter,它好在哪里?它的坑在哪里? 应该怎么学?](Flutter-WHAT.md) + * [Flutter 中键盘弹起时,Scaffold 发生了什么变化](Flutter-KEY.md) + * [Flutter 2.0 下混合开发浅析](Flutter-Group.md) + * [Flutter 搭建 iOS 命令行服务打包发布全保姆式流程](Flutter-iOS-Build.md) + * [不一样角度带你了解 Flutter 中的滑动列表实现](Flutter-N-Scroll.md) + * [带你深入 Dart 解析一个有趣的引用和编译实验](DEMO-INTEREST.md) + * [Dart 里的类型系统](Dart-SYS.md) + * [Dart VM 的相关简介与运行模式解析](Dart-VM.md) + * [Flutter 里的语法糖解析,知其所然方能潇洒舞剑](Flutter-SU.md) + * [Flutter 实现完美的双向聊天列表效果,滑动列表的知识点](Flutter-SC.md) + * [Flutter 启动页的前世今生适配历程](Flutter-LA.md) + * [Flutter 快速解析 TextField 的内部原理](Flutter-TE.md) + * [谷歌DevFest 2021 广州国际嘉年华-带你了解不一样的 Flutter](Flutter-DevFest2021.md) + * [Flutter for Web 2022 年:简单探讨](Flutter-W2022.md) + * [2021 年的 Flutter 状态管理:如何选择?](Flutter-StateM.md) + * [Flutter 2.10 升级填坑指南](Flutter-210-FIX.md) + * [Flutter Riverpod 全面深入解析,为什么官方推荐它?](Flutter-Riverpod.md) + * [ Flutter 2022 战略和路线解读与想法](Flutter-2022-roadmap.md) + * [原生开发如何学习 Flutter | 谷歌社区说](Flutter-SQS.md) + * [Fluttter 混合开发下 HybridComposition 和 VirtualDisplay 的实现与未来演进](Flutter-HV.md) + * [Flutter 双向聊天列表效果进阶优化](Flutter-Chat2.md) + * [Flutter 上字体的另类玩法:FontFeature ](Flutter-FontFeature.md) + * [移动端系统生物认证技术详解](Flutter-BIO.md) + * [完整解析使用 Github Action 构建和发布 Flutter 应用](Flutter-GB.md) + * [Flutter 120hz 高刷新率在 Android 和 iOS 上的调研总结](Flutter-120HZ.md) + * [Flutter Festival | 2022 年 Flutter 适合我吗?Flutter VS Other 量化对比](Flutter-FF.md) + * [Flutter 从 TextField 安全泄漏问题深入探索文本输入流程](Flutter-TL.md) + * [Flutter iOS OC 混编 Swift 遭遇动态库和静态库问题填坑](Flutter-BIOS.md) + * [Flutter Web : 一个编译问题带你了解 Flutter Web 的打包构建和分包实现 ](Flutter-WP.md) + * [大前端时代的乱流:带你了解最全面的 Flutter Web](Flutter-Web-T.md) + * [Flutter 深入探索混合开发的技术演进](Flutter-DWW.md) + * [Flutter 3.0 之 PlatformView :告别 VirtualDisplay ,拥抱 TextureLayer](Flutter-P3.md) + * [Google I/O Extended | Flutter 游戏和全平台正式版支持下 Flutter 的现状](Flutter-Extended.md) + * [掘金x得物公开课 - Flutter 3.0下的混合开发演进](Flutter-DWN.md) + * [Flutter 小技巧之 ButtonStyle 和 MaterialStateProperty ](Flutter-N1.md) + * [Flutter 小技巧之 Flutter 3 下的 ThemeExtensions 和 Material3 ](Flutter-N2.md) + * [Flutter 小技巧之玩转字体渲染和问题修复 ](Flutter-N3.md) + * [Flutter 小技巧之有趣的动画技巧](Flutter-N4.md) + * [Flutter 小技巧之 Dart 里的 List 和 Iterable 你真的搞懂了吗?](Flutter-N6.md) + * [Flutter 小技巧之 MediaQuery 和 build 优化你不知道的秘密](Flutter-N7.md) + * [Flutter 小技巧之 ListView 和 PageView 的各种花式嵌套](Flutter-N5.md) + * [Flutter 小技巧之优化你使用的 BuildContext](Flutter-N8.md) + * [如何利用 Flutter 实现炫酷的 3D 卡片和帅气的 360° 展示效果](Flutter-N9.md) + * [给掘金 Logo 快速添加动画效果,并支持全平台开发框架](Flutter-N10.md) + * [Flutter 实现 “真” 3D 动画效果,用纯代码实现立体 Dash 和 3D 掘金 Logo](Flutter-N11.md) + * [Flutter 3.3 之 SelectionArea 好不好用?用 “Bug” 带你全面了解它](Flutter-N12.md) + * [Flutter 小技巧之优化你的代码性能](Fluttter-N13.md) + * [Flutter 之快速理解混合开发里的手势事件传递](Flutter-N17.md) + * [一文快速带你了解 KMM 、 Compose 和 Flutter 的现状](Flutter-CCK.md) + * [Android 开发者的跨平台 - Flutter or Compose ?](SQS.md) - - - - - + * [Flutter 小技巧之快速理解手势逻辑](N15.md) + +* [Flutter 工程化选择](GCH.md) + * [Flutter 工程化框架选择——搞定 Flutter 动画](Z1.md) + * [Flutter 工程化框架选择 — 搞定 UI 生产力](Z3.md) + * [Flutter 工程化框架选择 — 搞定数据存储选型](Z5.md) + * [Flutter 工程化框架选择 — 混合开发的摸爬滚打](Z6.md) + * [Flutter 工程化框架选择 — add-to-app 的指路明灯](Z10.md) + * [Flutter 工程化框架选择 — 状态管理何去何从](Z2.md) + + + +​ + + + + + + + + + diff --git a/Z1.md b/Z1.md new file mode 100644 index 0000000..82065a4 --- /dev/null +++ b/Z1.md @@ -0,0 +1,249 @@ +# Flutter 工程化框架选择——搞定 Flutter 动画 + +> 本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! + +首先,这次会写一个新的系列《Flutter 工程化框架选择》,但是系列其实并非前端的工程治理方面的内容,**这个系列只是单纯告诉你,创建一个 Flutter 工程,或者说搭建一个 Flutter 工程脚手架,应该如何选择快速适合自己的插件模块,或者说这是一个指引系列,相信会适合新手同学**。 + +> **为什么会想要写一个这样的系列?因为这类的问题太多了**,简单检索一个群的聊天记录,单单 `有没有` 这个关键字就可以搜索翻好几页,所以这个系列的目的,是帮大家整理 Flutter 工程里可能会需要的各种第三方模块,并对比一些技术细节,**也是方便以后回答问题我可以直接甩链接**。 + +![](http://img.cdn.guoshuyu.cn/20220920_Z1/image1.png) + +首先作为系列第一篇文章,本篇我们先聊动画。 + +为什么第一篇聊它,因为近期刚好做了一些关于 Flutter 动画的调研,不久前也刚好发布过一些关于它的内容,所以素材比较多,而本篇将针对你可能会遇到的动画场景,**给你推荐各式各样的动画框架来加速开发**。 + +# 前言 + +在之前的 [Flutter 小技巧](https://juejin.cn/post/7111071430292275213) 系列里我们聊过,如果没有使用封装, Flutter 里创建动画一般需要: + +- `AnimationController` : 用于控制动画启动、暂停 +- `TickerProvider` : 用于创建 `AnimationController` 所需的 `vsync` 参数,一般最常使用 `SingleTickerProviderStateMixin` +- `Animation` : 用于处理动画的 value ,例如常见的 `CurvedAnimation` +- 接收动画的对象:例如 `FadeTransition` + +这种写法如下代码所示,我们一般可以称为显式动画(不要纠结名词叫法),大致特征就是:以 **`*Transition`** 命名,比如 `FadeTransition` 、`SizeTransition` 和 `RotationTransition` 等,**需要我们自己定义和操作 `AnimationController`** 。 + +```dart +class _AnimatedOpacityState extends State + with TickerProviderStateMixin { + late final AnimationController _controller = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + )..repeat(reverse: true); + late final Animation _animation = CurvedAnimation( + parent: _controller, + curve: Curves.easeIn, + ); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.white, + child: FadeTransition( + opacity: _animation, + child: const Padding(padding: EdgeInsets.all(8), child: FlutterLogo()), + ), + ); + } +} +``` + +但是,如果都采用这种方式去使用动画,那么项目里就会存在很多的重复代码,所以 Flutter 官方默认提供了一些动画封装,也可以叫做隐式动画(不要纠结名词叫法): + +> **也就是开箱即用**,常见是 **`Animated*`** 开头的 Widget,例如 `AnimatedPositioned` 、`AnimatedContainer` 、 `AnimatedPadding` 、`AnimatedOpacity` 等控件,它们最大的特点就是内部已经完全封装好逻辑,你只需要配置对应参数就可以触发动画。 + +如下代码所示,你只需要改变 `_width` 和 `_height` 就可以触发动画效果,动画中间过程的数据会通过 `Curves` 变化的 “插值” 来得到。 + +```dart +body: Center( + child: AnimatedContainer( + width: _width, + height: _height, + decoration: BoxDecoration( + color: _color, + borderRadius: _borderRadius, + ), + duration: Duration(seconds: 1), + curve: Curves.fastOutSlowIn, + ), +), +``` + +虽然隐式动画挺方便,但真正的项目里可能很多情况还是不满我们的需求,往往业务逻辑会需要更灵活或者更细致的封装,那么这就要聊到本篇后面的内容。 + +# 基础动画 + +说到基础动画的部分,主要就是在隐式动画的基础上进一步的封装,可以通过简单配置快速实现一些更精致的动画效果。 + +## animations + +[animations](https://github.com/flutter/packages/tree/main/packages/animations) 是官方封装的动画库,主要卖点在于 Material 风格的动画过渡,所以**这个库的核心重点是路由过渡动画或者页面切换动画**,例如下图就是 Demo 里利用库里自带的 `OpenContainer` 实现页面跳转的 Material 过渡 效果。 + +![](http://img.cdn.guoshuyu.cn/20220920_Z1/image2.gif) + +另外也可以使用 `PageTransitionSwitcher` 实现一些非路由的页面切换动画,可以理解为是一个变异版本的 `AnimatedSwitcher` ,或者是 `Stack` 版动画 `PageView` ,不过支持自定义进入和退出动画,还可以支持自定义布局,一般默认使用默认 `defaultLayoutBuilder` 的话,就是使用 `Stack` 作为布局。 + +![](http://img.cdn.guoshuyu.cn/20220920_Z1/image3.gif) + +官方 Demo 里也会使用 `PageTransitionSwitcher` 来做步进页面跳转的效果,**所以 [animations](https://github.com/flutter/packages/tree/main/packages/animations) 的核心动画能力主要是在于页面过渡方面**。 + +![](http://img.cdn.guoshuyu.cn/20220920_Z1/image4.gif) + + + +> 总的来说,这个库是官方维护,值得信赖。 + +## simple_animations + +[simple_animations](https://github.com/felixblaschke/simple_animations) 如同它的名字所示,它主要是简化了自定义动画的过程,正如前面所示,在 Flutter 里使用动画,我们一般需要定义 `AnimationController` 、`Animatable` 和 `Animation` 等对象来配置动画效果。 + +而 simple_animations 提供了 `AnimationMixin` 对象,如下代码所示,你只需要通过 `with` 关键字就可以简化接入动画的代码。 + +| ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image5.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image6.png) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image7.gif) | +| ------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------- | + +同时 simple_animations 也提供了各种 Builder 来简化,例如使用 `MirrorAnimationBuilder` 就可以实现一个循环反复的动画效果。 + +![](http://img.cdn.guoshuyu.cn/20220920_Z1/image8.png) + +**所以 simple_animations 针对显式动画进行了优化封装,在更方便的情况下做到能更灵活控制动画效果**,如下图所示,在 [gsy_fluter_demo](https://github.com/CarGuo/gsy_flutter_demo/tree/master/lib/widget/particle) 里就利用了 simple_animations 来实现一些动画效果: + +- `MirrorAnimation` 和 `MovieTween ` 实现渐变的背景 +- `LoopAnimation` 和 `MovieTween` 实现了粒子动画 + +| ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image9.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image10.gif) | +| ---------------------------------------- | ------------------------------------------ | + +> 不过这个库有个小问题,就是它最近几次大版本,每个版本总是会有一些 break change ,但是可能就是改改名字,换换参数位置,如果你一段时间没关注,再升级可能会有些成本。 + +## animate_do + +**[animate_do](https://github.com/Klerith/animate_do_package) 是一个轻量级动画包,它比 simple_animations “更懒”,代码也相对简单很多,内部提供了丰富的动画封装 Widget 可直接使用**。 + +> 你可以理解为更丰富的隐式动画。 + +animate_do 就是通过 `AnimatedBuilder ` 配合 `Transform` 、`Opacity` 进行了封装,然后开发者可以通过 `FlipInX` 、`FadeInDown` 、`ElasticIn` 等对象直接实现动画效果。 + +![](http://img.cdn.guoshuyu.cn/20220920_Z1/image11.png) + +既然 animate_do 十分简单,那为什么会推荐它呢?因为它真的很实用。 + +**首先简单代表着好维护,作者不玩了我们自己也能接**,其次 animate_do 提供了相当丰富的封装,这对于懒人来说它真的很实用,特别是对于一些 “相对复杂” 的动画效果上(如下图3)可以节省很多时间,特别是在 `Tween` 和 `CurvedAnimation`的封装上。 + +| ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image12.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image13.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image14.gif) | +| --------------------------------------- | ----------------------------------------- | --------------------------------------- | + +> 目前有一段时候没更新,但是问题不大~ 同类型的库还有 [spring](https://github.com/KaushickSArgekar/spring) ,可以作为替代项目,也实现了类似的封装。 + +# UI 动画 + +介绍完基础动画效果,接下来推荐一些常用的动画框架,因为经常有人问到`有没有xxx` 的实现可以直接拿来使用,秉承着有做好的就不自己动手原则,下面这些 UI 动画框架,也许在你开发过程中就会需要用到。 + +> UI 动画部分就不介绍实现了,主要就看功能符不符合你需求。 + +## 数字动画 + +首先是数字动画,常见的有 [flutter-animated-counter](https://github.com/h65wang/flutter-animated-counter) ,本身它就提供了丰富的 API 和动画效果,类似的第三方库还有 [odometer](https://github.com/KirsApps/odometer) 和 [animated_digit ](https://github.com/mingsnx/animated_digit) 等,具体可以自己按需选择。 + +| ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image15.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image16.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image17.gif) | +| ------------------------------------------- | ------------------------------------------- | ----------------------------------------- | + +## 跑马灯 + +既然聊到文字动画,就不得不说跑马灯,如下表格所示是 Flutter 里常用到的文字跑马灯的第三方 package ,其中 [marquee_widget](https://github.com/yousifk/marquee_widget) 提供的接口相对更佳丰富。 + +| [marquee_widget](https://github.com/yousifk/marquee_widget) | [text_scroll](https://github.com/yurii-khi/text_scroll) | [marquee_text](https://pub.dev/packages/marquee_text) | +| ----------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image18.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image19.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image20.gif) | + +## 加载动画 + +加载动画是最常见的 UI 动画,如果你没有设计师,如果你不知道 loading 动画用什么好,那你可以考虑下面几个 package,这三个 loading 库都是纯代码实现,提供了丰富的样式选择。 + +| [flutter_spinkit](https://github.com/jogboms/flutter_spinkit) | [loading_animation_widget](https://github.com/watery-desert/loading_animation_widget) | [loading_indicator](https://github.com/TinoGuo/loading_indicator) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image21.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image22.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image23.gif) | + +> 纯代码实现有什么好处?当样式需要做一些简单调整时,作为开发者可以通过代码快速修改,这是我喜欢纯代码实现动画的原因。 + + + +## 指引动画 + +指引动画也是常见的需求之一,基本实现都会通过 `Overlay` 来完成,不同的可能就是动画效果和定位方式的差异,具体也可以按照自己的需要选择。 + +| [feature_discovery](https://github.com/ayalma/feature_discovery) | [flutter_showcaseview](https://github.com/simformsolutions/flutter_showcaseview) | [flutter_intro](https://pub.dev/packages/flutter_intro) | [BubbleShowcase](https://github.com/Skyost/BubbleShowcase) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------- | ---------------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image24.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image25.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image26.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image27.gif) | + + + +## 列表动画 + +列表动画也是常见的 UI 动画能力之一,基本上 Flutter 上最常见用都的第三方列表动画库就是下面表格里这三个,坑还是有的,但是“又不是不能用”。 + +| [flutter_staggered_animations](https://github.com/mobiten/flutter_staggered_animations) | [animation_list](https://github.com/turlvo/flutter_animation_list) | [transformable_list_view](https://github.com/TBR-Group-software/transformable_list_view) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image28.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image29.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image30.gif) | + +## 3D 立体动画 + +这个类目可能关注的人反而不是很多,但是恰好是我近期比较关注的动画实现,这里主要推荐两个利用矩阵变换绘制 3D 视觉效果的第三方库,它们不同在于: + +- zwidget 能力相对比较弱,直接使用的是 `Transform` 的能力 +- zflutter 来源于前端 zdog 项目,是直接对 `Canvas` 的 `path` 进行矩阵变换 + +| [zflutter](https://github.com/jamesblasco/zflutter) | [zwidget](https://github.com/apalala-dev/zwidget) | +| --------------------------------------------------- | ------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220806_N11/image30.gif) | ![](http://img.cdn.guoshuyu.cn/20220920_Z1/image31.gif) | + +在不久前我也写过一篇文章 [《Flutter 实现 “真” 3D 动画效果》](https://juejin.cn/post/7129239231473385503),内容主要分析了 zflutter 的实现逻辑和如何使用 zflutter ,利用纯代码渲染 3D 效果的动画。 + +> 当然,上门两个 3D 动画,它们在使用上相对会比较复杂,如果需要更高级的动画效果,建议看下面的推荐。 + +# 高级动画支持 + +介绍完面向程序员的动画支持之后,最后我们来介绍两个面向设计师的动画支持库,同时也是更好支持复杂动画实现的库。 + +## Lottie + +[Lottie](https://github.com/xvrh/lottie-flutter) 相信大家不会陌生,[airbnb ](https://github.com/airbnb) 最优秀的动画开源库,设计师可以通过 AE 插件导出设计好的动画效果,然后利用平台的 `Canvas` 等能力渲染出 AE 上的动画效果。 + +![](http://img.cdn.guoshuyu.cn/20220920_Z1/image32.gif) + +**使用 Lottie 的好处在于,设计师可以更自由的去尝试更炫酷的动画**,而程序员只需要关心如何控制动画(时长,循环,方向,帧率等),同时因为动画大部分时候都是矢量数据,所以 Lottie 文件相对不大。 + +> 在早期的时候,由于 Lottie 本身只支持原生平台,所以 Flutter 上都是通过外界纹理或者 `PlatformView ` 等形式接入,这样的后果就是导致各种性能和兼容问题,好在现在 [lottie-flutter](https://github.com/xvrh/lottie-flutter) 已经支持 Dart `Canvas` 的原生 API 。 + +当然, Lottie 本身的问题也很明显,那就是你的交互设计师要会 AE ,很遗憾的是,我接触的大部分设计师都不会 AE ,而且会 AE 还不够,还需要会 [Bodymovin](https://github.com/airbnb/lottie-web) 插件相关的兼容,熟悉什么属性可以用,整体开发环境也比较重。 + +## Rive + +对于 **[rive](https://link.juejin.cn/?target=https%3A%2F%2Frive.app)** 可能大家会感觉比较陌生,而做过 Flutter 开发的可能对 rive 会有所耳闻,因为 rive 在此之前叫 flare ,是 2dimensions 公司的开源动画产品,在发布之初由于和 Flutter 团队有深入合作,所以在初期一直是 Flutter 官方推荐的动画框架。 + +| ![](http://img.cdn.guoshuyu.cn/20220731_N10/image3.gif) | ![](http://img.cdn.guoshuyu.cn/20220731_N10/image4.png) | +| ------------------------------------------------------- | ------------------------------------------------------- | + +**[rive](https://link.juejin.cn/?target=https%3A%2F%2Frive.app)** 同样是一个面向设计师的动画框架,只不过他是在 **Web Editor** 里进行 UI 编排和动画绘制,所以他在开发环境上相对会轻量化。 + +| ![](http://img.cdn.guoshuyu.cn/20220731_N10/image18.gif) | ![img](http://img.cdn.guoshuyu.cn/20220731_N10/image9.gif) | ![img](http://img.cdn.guoshuyu.cn/20220731_N10/image10.gif) | +| ------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------- | + +同时 rive 同样是通过导出矢量的动画数据文件(也可以包含一些静态资源),然后利用平台的 `Canvas` 来实现动画效果,所以它的占用提及也不会很大。 + +另外 rive 现在也是全平台支持, **Android、 iOS、Web、Desktop、Flutter 、React、Vue、C++** 等都在支持范围之内。 + +> 如果想深入了解 rive ,也可以看我之前的 [《给掘金 Logo 快速添加动画效果》](https://juejin.cn/post/7126661045564735519) ,其实对于程序员来说,rive 同时很好上手。 + +![](http://img.cdn.guoshuyu.cn/20220731_N10/image1.gif) + +> 目前 rive 的问题是,第二代 rive 和第一代 flare 存在断档不兼容,而且基本可以忽略迁移的可能,同时没有本地化免费开发 IDE 有时候也是一种麻烦。 + +好了,关于 Flutter 动画相关的内容推荐就到这里,如果你还有什么关于 Flutter 工程或者框架的疑问,欢迎留言评论,也许又可以多一期的素材~ + diff --git a/Z10.md b/Z10.md new file mode 100644 index 0000000..ac9acc5 --- /dev/null +++ b/Z10.md @@ -0,0 +1,266 @@ +# Flutter 工程化框架选择 — add-to-app 的指路明灯 + +``` +本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! +``` + +这是 《Flutter 工程化框架选择》 系列的第五篇 ,就像之前说的,这个系列只是单纯告诉你,创建一个 Flutter 工程,或者说搭建一个 Flutter 工程脚手架,应该如何快速选择适合自己的功能模块,或者说这是一个指引系列,所以比较适合新手同学。 + +> **这算是目前 Flutter 上少有关于 add-to-app 支持的指导分析了**。 + +本来没想写这个话题,因为 add-to-app 混合开发一直是 Flutter 的痛,但目前又属于无法避免的场景 ,有时候甚至还有 [RN内嵌 Flutter UI ](https://cloud.tencent.com/developer/article/1896484)这种“鬼畜”需求,**所以既然大家都有这样的需要,那就来聊聊这类工程下有哪些选择**。 + +| ![](http://img.cdn.guoshuyu.cn/20221101_Z10/image1.png) | ![](http://img.cdn.guoshuyu.cn/20221101_Z10/image2.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | + +# add-to-app + +首先为什么 add-to-app 在 Flutter 里很特殊?**因为 Flutter 里的控件和路由等是通过独立的 FlutterEngine 进行绘制和管理,所以 Flutter 脱离会平台的 UI 渲染机制和页面堆栈**。 + +也就是 Flutter 对于原生平台来说是一个“单页面”应用,举一个之前经常说的例子,如下图所示: + +- 在当前 Flutter 端路由堆栈里有 `FlutterA` 和 `FlutterB` 两个页面 Flutter 页面; +- 这时候打开新的 `Activity` / `ViewController`,启动了**原生页面X**,可以看到**原生页面 X** 作为新的原生页面加入到原生层路由后,把默认的 `FlutterActivity` / `FlutterViewController` 给挡住,也就是把 `FlutterA` 和 `FlutterB` 都被挡住; +- **这时候在 Flutter 层再打开新的 `FlutterC` 页面,可以看到依然会被原生页面X挡住**; + + + +![](http://img.cdn.guoshuyu.cn/20221101_Z10/image3.png) + + + +所以通过这部分内容可以看出来,**Flutter 默认情况下作为“单页面”应用,他们的路由堆栈是和原生层存在不兼容的隔离**,其他问题还有如:内存数据互通、UI 控件嵌套等,这些都是 add-to-app 的痛点。 + +那关于 add-to-app 集成方式的选择上,官方在 Android 提供了两种集成方法,在 iOS 提供了三种集成方式,我个人推荐选择以下的集成方式: + +- **Android [本地aar + 远程aar](https://docs.flutter.dev/development/add-to-app/android/project-setup#option-a---depend-on-the-android-archive-aar)** ,这种集成方式 Android 开发团队本地可以不需要安装 Flutter SDK + +- **iOS [生成 xcframework ](https://docs.flutter.dev/development/add-to-app/ios/project-setup#option-b---embed-frameworks-in-xcode)**,这种集成方式 iOS 开发团队本地可以不需要安装 Flutter SDK + +当然以上集成方式最大的问题就是需要分开调试,但从项目耦合上和协调开发上我个人觉得会更符合要求。 + +**另外,如果你对如何使用 add-to-app 还有疑问,那官方的 [put-flutter-to-work](https://github.com/flutter/put-flutter-to-work) 项目就是一个很好的参考例子。** + +> add-to-app 跑不起来?参考这个 Demo 是你的最佳选择,例如配置依赖和通过 `FlutterEngineCache` 和 `executeDartEntrypoint` 预热 Engine 等。 + +# 混合路由 + +这是本篇的重点,通过前面简单的例子,我们可以预见在 add-to-app 里混合路由和数据共享将会是最大的障碍,例如: + +- 打开一个原生页面 A +- 再打开一个 Flutter 页面 B +- 再打开一个原生页面 C +- 再打开一个 Flutter 页面 D + +而这个过程中既要保证混合路由的同步,又要保证数据交互的畅通,那么在这个基础上,**统一路由到原生页面堆栈, “多开 Flutter 页面” 就成为必然的需求**,但是对“多开”的实现又有不同的选择: + +- 每个 Flutter 页面新建一个 Flutter Engine +- 多个 Flutter 页面共享一个 Flutter Engine + +**不同实现各自的利弊,而本篇也是主要介绍它们的实现者各自的利弊**。 + +## FlutterEngineGroup + +`FlutterEngineGroup` 可能对部分人来说比较陌生,它是在 Flutter 2.0 时发布的 add-to-app 的官方解决方案,**`FlutterEngineGroup` 方案使用了多 Engine 混合支持,官方宣称除了一个 Engine 对象之外,后续每个 Engine 对象在 Android 和 iOS 上仅占用 180kB** 。 + +> 以前的方案每多一个Engine ,可能就会多出 19MB Android 和 13MB iOS 的占用。 + +在使用 `FlutterEngineGroup` 之后,`FlutterEngine` 都将由 `FlutterEngineGroup` 去生成,生成的 `FlutterEngine` 可以独立应用于 `FlutterActivity`/`FlutterViewController`、 `FlutterFragment` 和 `FlutterView` 等。 + +其实 `FlutterEngineGroup` 不一定是用于混合路由,如下动图所示,在官方的 [multiple_flutters](https://github.com/flutter/samples/tree/main/add_to_app/multiple_flutters) 例子里也有在一个页面内放置两个 `FlutterFragment` 的实现,**重点还是在于 `FlutterEngineGroup` 可以在多 Engine 混合模式下保持极低的内存占用**。 + +| ![](http://img.cdn.guoshuyu.cn/20221101_Z10/image4.gif) | ![](http://img.cdn.guoshuyu.cn/20221101_Z10/image5.png) | +| ------------------------------------------- | ------------------------------------------------------------ | + +之所以 `FlutterEngineGroup` 能在多 Engine 模式下保持极低的内存占用, **其实得益于通过 `FlutterEngineGroup` 生成的 `FlutterEngine` 可以共享 GPU 上下文, font metrics 和 isolate group snapshot** ,也就是新 Engine 可以通过旧 Engine `spawn` 出来。 + +而 `FlutterEngineGroup` 里主要是通过 `dartEntrypoint` 来制定入口: + +- **`findAppBundlePath` 默认指向的 `flutter_assets` 目录**; +- **`entrypoint` 其实就是 dart 代码里启动方法的名称**;也就是绑定了在 dart 中 `runApp` 的方法,在 dart 可以通过 `@pragma('vm:entry-point')` 来指定 +- dart 层和原生层通过 `MethodChannel` 共享数据 + +| ![](http://img.cdn.guoshuyu.cn/20221101_Z10/image6.png) | +| ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20221101_Z10/image7.png) | +| ![](http://img.cdn.guoshuyu.cn/20221101_Z10/image8.png) | + +接入 `FlutterEngineGroup` 之后: + +- 在 dart 层面可以通过 `MethodChannel` 打开原始页面; +- 在原生层可以通过新建 `FlutterEngine` 打开新的 Flutter 页面; +- 甚至你还可以在原生层打开一个 `FlutterView` 的 Dialog; + +当然,到这里你可能已经注意到了,因为每个 Flutter 页面都是一个独立的 Engine ,由于 dart isolate 的设计理念,**每个独立 Engine 的 Flutter 页面内存是无法共享的**。 + +也就是说,当你需要共享数据时,只能在原生层持有数据,然后注入或者传递到每个 Flutter 页面中,就像官方所说的,**每个 Flutter 页面更像是一个独立 Flutter 模块**。 + +> 当然这也造成了一些不必要的麻烦,比如:**同一张图片,在原生层、不同 Flutter Engine 会出现多次加载的问题**,这种问题可能就需要你针对 Flutter 的图片加载使用外界纹理,来实现在原生层统一的内存管理等。 + +**而 `FlutterEngineGroup` 的好处也很直观:官方维护,不需要第三方框架,轻量级**。 + +其实 FlutterEngineGroup 不只是在多页面下的场景有价值,就算你只有一个 Engine 也可以考虑它,例如当你在 Service 里去创建 Flutter Engine 并构建独立的 `FlutterView` 效果,但是页面在静止 20 分钟左右后就可能会出现: + +> E/MessageQueue-JNI: java.lang.RuntimeException: Cannot execute operation because FlutterJNI is not attached to native. + +这个时候如果将 Engine 的创建方式换成 FlutterEngineGroup ,你会发现 Engine 因为多次创建和被回收的问题将得到极大程度的缓解。 + +> **这里为什么要介绍那么长的 `FlutterEngineGroup` ,因为它是后面的框架有很大关系。** + +## flutter_boost + +在 Flutter add-to-app 这个领域里, [flutter_boost ](https://github.com/alibaba/flutter_boost)相信大家肯定不会陌生,作为最早开源并且支持混合开发路由的框架,**flutter_boost 采用的是单 Engine 内存共享的方式** 。 + +![](http://img.cdn.guoshuyu.cn/20221101_Z10/image9.png) + +这种实现方式的好处很明显:**那就是 Dart 层面数据状态支持共享**,因为只有一个 Engine ,但是问题也很明显,如下图是 flutter_boost 在 2.0 时的渲染流程,维护一个 Engine 渲染多个 Surface 这种非官方实让 flutter_boost 在很长一段时间没能快速推进项目。 + +![](http://img.cdn.guoshuyu.cn/20221101_Z10/image10.png) + +不过 flutter_boost 几乎是早期 add-to-app 的不二之选,**但如果你现在要使用 flutter_boost, 那么最好你的 Flutter SDK 是从 3.0 开始**。 + +因为在 3.0 之前 flutter_boost 自己拷贝并维护了一套 Engine Embedding 层的自定义代码,这部分代码导致 flutter_boost 更新速度慢并且入侵性更强,而 **flutter_boost 3.0 开始采用继承的方式扩展,后期兼容性更好**。 + +> 虽然 flutter_boost 也表示 flutter_boost 3.0 会兼容 Flutter 2.0 ,但是你懂的。 + +从目前的 flutter_boost 3.0 上来看,新版本优势主要有: + +- 和 flutter_boost 2.0 对比 Android 和 iOS 两端 API 得到对齐,统一生命周期,代码优化后更好阅读 + +- 不侵入 Engine 代码,兼容更多 Flutter 版本,避免因为 flutter_boost 而导致的无法升级 Flutter SDK 等问题 +- 目前在仍在维护 + +当然,如果是对比 `FlutterEngineGroup` flutter_boost 的优势在于: + +- 支持页面间数据传递 +- 统一的混合路由调用接口 +- Dart 层面数据支持共享 +- 支持跨端事件传递 + +那缺陷可能是什么?flutter_boost 采用的单 Engine 实现,最大问题就是可能遇到渲染切换上的时机问题,例如: + +- [动画区域会有不停地闪烁出现](https://github.com/alibaba/flutter_boost/issues/1740) +- [后台停留出现假死](https://github.com/alibaba/flutter_boost/issues/1671) +- [路由跳转或返回可能出现白屏](https://github.com/alibaba/flutter_boost/issues/1719) + +![](http://img.cdn.guoshuyu.cn/20221101_Z10/image11.gif) + +可以看到在 Flutter 的实现机制上维护一套机制外的逻辑缺失不容易,更不容易的是 flutter_boost 现在还在积极推进和维护,从目前来看 flutter_boost 3.0 会是一个不错的选择。 + + + +## flutter_thrio + +[flutter_thrio](https://github.com/flutter-thrio/flutter_thrio) 一开始是哈罗单车开源的 add-to-app 集成方案,**后来哈罗不再维护之后,由 flutter_thrio 组织社区开源进行维护**。 + +flutter_thrio 采用多 Engine 模式,同时又支持 Engine 复用的逻辑,从目前的代码([bf16529](https://github.com/flutter-thrio/flutter_thrio/commit/2e0f53861a7c91ba13c0b2c262e4733dfbf16529))上看,大概逻辑就是: + +> 所有路由操作都通过原生端支持维护,而一个 `ThrioFlutterActivity` / `FlutterViewController` 可以承载多个 Dart 页面,如果 last 页面不是 Flutter 容器,就 `spawn` 一个新的 `FlutterEngine` 并构建新的 Flutter 容器页面。 + +![](http://img.cdn.guoshuyu.cn/20221101_Z10/image12.png) + +flutter_thrio 的内部实现的多 Engine 模式和 `FlutterEngineGroup` 那套基本一样,不过它是通过反射拿到 `FlutterEngine` 里的 `flutterJNI` ,然后通过 `spawn` 构建新的 `FlutterEngine` 。 + +> PS,目前 flutter_thrio 里的 `isMultiEngineEnabled `标识位感觉有些具备迷惑性,其实它更多是控制 `entrypoint` ,通过 `entrypoint` 来决定是否启动新的 `FlutterEngine` 容器。 + +![](http://img.cdn.guoshuyu.cn/20221101_Z10/image13.png) + + + +另外 flutter_thrio 通过封装,在三端利用统一的 `notify` 接口来实现页面通知进行数据同步,这也是目前 add-to-app 里数据联通的常规实现方案。 + +那 flutter_thrio 有什么优势? + +- `FlutterEngine` 可以按需启动 +- 更低的内存占用 +- 统一的路由堆栈和数据接口 +- 轻量级代码,更简便接入 + +那这个库有什么问题? + +- 不兼容 `Fragment` 级别支持 +- 不提供 iOS 中存在的 `present` 功能 +- 官方不再维护,社区库维护投入有限,理解项目迭代纯靠源码:”**不打算好好看看源码的使用者可以放弃这个库了,因为很多设定是比较死的,而我本人不打算花时间写太多文档**“ + +> flutter_thrio 很多 [Feat ](https://github.com/flutter-thrio/flutter_thrio/issues/4)和想法还是很不错的,只是维护的资源有限,希望未来项目还能够继续持续推进。 + +## mix_stack + +[mix_stack](https://github.com/yuewen/mix_stack) 是个人开源的混合路由库,从设计上看它类似于早期更轻量级的 boost ,采用的是单 Engine 模式,所以也是通过跳转 Flutter 容器时切换 Surface 来实现路由混合。 + +![](http://img.cdn.guoshuyu.cn/20221101_Z10/image14.png) + +在 mix_stack 里每一个 Native Flutter Container 都会包含了一个独立的 Navigator 用于维持 Flutter 内栈管理,通过 Flutter 内 Stack 控制当前渲染 Native Container 所属的 Flutter 页面堆栈。 + +而 mix_stack 的特点就是**支持多 Tab Flutter View 和 Flutter 端控制 Native 界面隐藏显示** ,其中最有意思的就是 Flutter 端控制 Native 界面隐藏显示的 `NativeOverlayReplacer` 。 + +举个例子,如下图所示,此时 `FlutterView` 上面有两个 Native 的控件 navigationBar 和 tabBar ,如果此时我们直接弹出新的 Flutter Route 肯定会被这两个 Native 控件遮挡,但是因为需要 `Hero` 效果,所以又不希望用新的原生容器去承载,这时候就可以用 `NativeOverlayReplacer` 。 + +| ![](http://img.cdn.guoshuyu.cn/20221101_Z10/image15.png) | ![](http://img.cdn.guoshuyu.cn/20221101_Z10/image16.gif) | +| ------------------------------------------------------------ | ------------------------------------------- | + +如上图右侧动态,在页面内通过 `NativeOverlayReplacer` 指定 `autoHidesOverlayNames` ,然后在弹出 Flutter popup 路由之前调用 `registerAutoPushHiding` ,最终就可以实现 Flutter 控件“渲染在 Native” 之上的效果。 + +```dart +NativeOverlayReplacer( + autoHidesOverlayNames: ["tabBar", "navigationBar"], +···· +NativeOverlayReplacer.of(context).registerAutoPushHiding +``` + +这里面的原理其实是在页面打开时,通过在原生层利用 `createBitmap` 将两个 Native 控件进行截图,并将 bitmap `toByteArray` 传递到 Flutter 层,之后只需要在 Flutter 控件展示时对原生控件进行隐藏,并通过 `Stack` 在相应位置绘制出 native 控件的 Bitmap ,就可以达到类似 Flutter 控件“渲染在 Native” 之上的效果。 + +所以针对 mix_stack 的优势在于: + +- 支持 View 级别调用 +- 更加灵活 +- 拥有 `NativeOverlayReplacer` 特性 +- 支持多 Tab Flutter View + + mix_stack 的劣势也很明显: + +- mix_stack 相对入侵性较强, 例如它在 android 上通过各种反射获取 `FlutterView` 里的 `FlutterEngine` 、 `FlutterTextureView` 等 ,同时还利用反射获取了 `embedding` 的各种内部变量和方法,这对持续维护和稳定性有一定的影响 +- 个人维护,目前用户不多,可踩坑程度未知。 + +> PS:如果你跑 Demo 发现 Android Install Failed ,可以将 `android/app` 目录下的 `debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'` 屏蔽 + + + +## fusion + +[fusion](https://pub.dev/packages/fusion) 采用的是 `FlutterEngineGroup` 方案,默认在 Flutter 与 Native 页面多次跳转情况下,APP 始终仅有一份 FlutterEngine 实例,也就是对应 fusion 里的 `REUSE_MODE` 。 + +> 当然, fusion 针对 `FusionFragment` 和 `Dialog` 等场景也提供了不复用场景,可以通过 `FlutterEngineGroup` 底成本构建独立 Engine 的支持。 + +如果从代码层面看,fusion 代码相对会简洁不少,比如 `FusionActivity` 在继承 `FlutterActivity` 之后,主要做的两件事: + +- 找到当前 activity 下的 `FlutterView` ,调用 `detachFromFlutterEngine` 停用 +- 在 `onResume` 里调用 `engine.activityControlSurface.attachToActivity` 和 `flutterView?.attachToFlutterEngine` 复用引擎。 + +fusion 的设计理念就是尽可能简洁地去融合对应逻辑,所以侵入性不高,能复用 `embedding ` 相关的逻辑就不自定义,从目前 Demo 运行的情况下内存占用问题也还可以。![](http://img.cdn.guoshuyu.cn/20221101_Z10/image17.png) + +fusion 的优势其实在于作者对一些细节处理比较上心,比如: + +- Flutter 容器与 Native 容器跳转时状态栏图标颜色可能出现显示不正确的问题; +- 混合开发时当栈顶是 Flutter 页面时进入到任务界面其应用名称不显示的问题; +- 混合开发时 Android APP 在后台被系统回收后再次进入 Flutter 页面不能恢复的问题 +- 支持生命周期调用 + +当然 fusion 的劣势也很明显: + +- 作者 [gtbluesky ]( https://github.com/gtbluesky/fusion) 好像并没有在 Github 开源,所以目前交流反馈存在问题 +- 存在某些场景下闪动问题 +- 暂不知道是否还有什么坑 + +> PS:如果 demo 跑不起来,先把 compileSdkVersion 改为 32 ,所有 ext.kotlin_version 改为 '1.7.10' 就可以了 + + + +# 最后 + +通过上面分享关于 add-to-app 的现状和框架支持,相信大家对于相关的实现应该都有了一定的了解,采用什么方案和框架具体还是取决于你的需求场景,不管是哪个框架目前都有坑和局限,重点还是在于它未来是否持续维护,或者不维护了我们自己能否继续维护下去。 + +这里说一个题外话,其实开源更多是提供解决思路,有效的沟通和 PR 才能推进项目的健康发展,如果社区内基本都是一味的 issue 等待解决,那基本项目都很难长久,所以项目是不是 KPI 并不重要,重要的是它提供的思路是否有用,这才是我认为的开源里最大的价值。 + + + diff --git a/Z2.md b/Z2.md new file mode 100644 index 0000000..f4f3b2e --- /dev/null +++ b/Z2.md @@ -0,0 +1,332 @@ + + +# Flutter 工程化框架选择 — 状态管理何去何从 + +``` +本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! +``` + +这是 《Flutter 工程化框架选择》 系列的第六篇 ,就像之前说的,这个系列只是单纯告诉你,创建一个 Flutter 工程,或者说搭建一个 Flutter 工程脚手架,应该如何快速选择适合自己的功能模块,或者说这是一个指引系列,所以比较适合新手同学。 + +> **其实这是我最不想写的一个篇**。 + +状态管理是 Flutter 里 ♾️ 的话题,**本质上 Flutter 里的状态管理就是传递状态和基于 `setState` 的封装,状态管理框架解决的是如何更优雅地共享状态和调用 `setState`** 。 + +*那为什么我不是很想写状态管理的对比内容*? + +首先因为它很繁,繁体的煩,从 Flutter 发布到现在,`scoped_model` 、`BLoC` 、`Provider` 、 `flutter_redux` 、`MobX`、 `fish_redux` 、`Riverpod` 、`GetX` 等各类框架“百花齐放”,**虽然这对于社区来说是这是好事,但是对于普通开发者来说很容易造成过度选择困难症**,特别早期不少人被各种框架“伤害过”。 + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image1.png) + + + +其次,状体管理在 Flutter 里一直是一个“敏感”话题,每次聊到状态管理就绕不开 `GetX` ,但是一旦聊 `GetX` 又会变成“立场”问题,所以一直以来我都不是很喜欢写状态管理的内容。 + +| ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image2.png)![image-20221109095950845](http://img.cdn.guoshuyu.cn/20221109_Z2/image3.png) | ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image4.png) | +| ------------------------------------------------------------ | ----------------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image5.png) | ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image6.png) | + +所以本来应该在第一篇就出现的内容,一直被拖到现在才放出来,这里提前声明一些,本篇不会像之前一样从大小和性能等方面去做对比,因为对于状态管理框架来说这没什么意义: + +- **集成后对大小的影响可能还不如一张图片** +- **性能主要取决于开发者的习惯,在状态管理框架上对比性能其实很主观** + +当然,如果你对集成后对大小的影响真的很在意,那可以在打包时通过 `--analyze-size` 来生成 analysis.json 文件用于对比分析: + +```shell +flutter build apk --target-platform android-arm64 --analyze-size +``` + +上诉命令在执行之后,会在 `/Users/你的用户名/.flutter-devtools/` 目录下生成一个 `apk-code-size-analysis_01.json` 文件,之后我们只需要打开 Flutter 的 DevTools 下的 `App Size Tooling` 就可以进行分析。 + +| ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image7.png) | ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image8.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +例如这里是将 `Riverpod` 和 `GetX` 在同一项项目集成后导出不同 json 在 Diff 进行对比,可以看到此时差异也就在 78.5kb ,这个差异大小还不如一张 png 资源图片的影响大。 + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image9.png) + +**所以本次主要是从这些状体管理框架自身的特点出发,简单列举它们的优劣,至于最后你觉得哪个适合你,那就见仁见智了**~ + +> 本篇只是告诉你它们的特点和如何去选择,并不会深入详细讲解,如果对实现感兴趣的可以看以前分享过的文章: +> +> >- [Flutter Riverpod 全面深入解析](https://juejin.cn/post/7063111063427874847) +> > +> >- [全面理解 State 与 Provider](https://juejin.cn/post/6844903866706706439) +> > +> >- [全面深入理解状态管理设计](https://juejin.cn/post/6844904035439345671) +> + + + +# Provider + +2019 年的 Google I/O 大会 [Provider](hhttps://github.com/rrousselGit/provider) 成了 Flutter 官方新推荐的状态管理方式之一,它的特点就是: **不复杂,好理解,代码量不大的情况下,可以方便组合和控制刷新颗粒度** , 其实一开始官方也有一个 [flutter-provide](https://github.com/google/flutter-provide) ,不过后来宣告GG , [Provider](https://github.com/rrousselGit/provider) 成了它的替代品。 + +> ⚠️注意,`provider` 比 `flutter-provide` 多了个 `r`,所以不要再看着 provide 说 Provider 被弃坑了。 + +**简单来说,Provider 就是针对 `InheritedWidget` 的一个包装工具**,他让 `InheritedWidget` 的使用变得更简单,在往下共享状态的同时,可以通过 `ChangeNotifier` 、 `Stream` 、`Future` 配合 `Consumer*` 组合出多样的更新模式。 + +所以使用 Provider 的好处之一就是简单,同时你可以通过 `Consumer*` 等来决定刷新的颗粒度,其实也就是 `BuildContext` 在 `of(context)` 时的颗粒度控制。 + +> 登记到 `InheritedWidget` 里的 context 决定了更新是 rebuild 哪个` ComponentElement` ,感兴趣的可以看 [全面理解 State 与 Provider](https://juejin.cn/post/6844903866706706439) + +*当然,虽然一直说 Provider 简单,但是其实还是有一些稍微“复杂”的地方,例如 `select`* 。 + +Provider 里 `select` 是对 `BuildContext` 做了 “二次登记” 的行为,就是以前你用 context 是 `watch` 的时候 ,是直接把这个 Widget 登记到 Element 里,有更新就通知。 + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image10.png) + +但是 `select` 做了二次处理,就是用 `dependOnInheritedElement` 做了颗粒化的判断,如果是不等于了才更新,所以它对 context 有要求,如下图对就是对 context 类型进行了判断。 + +| ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image11.png) | ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image12.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +所以 `select` 算是 Provider 里的“小魔法“之一,总的来说 **Provider 是一个符合 Flutter 的行为习惯,但是不大符合前端和原生的开发习惯的优秀状态管理框架**。 + +**优点:** + +- 简单好维护 +- read、watch、select 提供更简洁的颗粒度管理 +- 官方推荐 + +**缺点:** + +- 相对依赖 Flutter 和 Widget +- 需要依赖 Context + +最后顺带辟个谣,之前有 “传闻” Provider 要被弃坑的说法,作者针对这个也有相应对澄清,所以你还是可以继续安心使用 Provider。 + +| ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image13.png) | ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image14.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + + + + + +# Riverpod + +[Riverpod](https://github.com/rrousselGit/riverpod) 和 Provider 是同个作者,**因为 Provider 存在某些局限性,所以作者根据 Provider 这个单词重新排列组合成 Riverpod**。 + + + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image15.png) + +如果说 Provider 是 `InheritedWidget` 的封装,那 Riverpod 就是在 Provider 的基础上重构出更灵活的操作能力,最直观的就是 **Riverpod 中的 Provider 可以随意写成全局,并且不依赖 `BuildContext` 来编写我们需要的业务逻**。 + +> 注意: Riverpod 中的 Provider 和前面的 [Provider](hhttps://github.com/rrousselGit/provider) 没有关系。 + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image16.png) + +在 Riverpod 里基本是每一个 “Provider” 都会有一个自己的 “Element” ,然后通过 `WidgetRef` 去 Hook 后成为 `BuildContext` 的替代,所以这就是 Riverpod 不依赖 Context 的 “魔法” 之一 + +> ⚠️**这里的 “Element” 不是 Flutter 概念里三棵树的 `Element`,它是 Riverpod 里 `Ref` 对象的子类**。`Ref` 主要提供 Riverpod 内的 “Provider” 之间交互的接口,并且提供一些抽象的生命周期方法,所以它是 Riverpod 里的独有的 “Element” 单位。 + +另外对比 Provider ,Riverpod 不需要依赖 Flutter ,所以也不需要依赖 `Widget`,也就是不依赖 `BuildContext `,所以可以支持全局变量定义 “Provider” 对象。 + +**优点:** + +- 在 Provider 的基础上更加灵活的实现, +- 不依赖 `BuildContext ` ,所以业务逻辑也无需注入 `BuildContext` +- Riverpod 会尽可能通过编译时安全来解决存在运行时异常问题 +- 支持全局定义 +- `ProviderReference` 能更好解决嵌套代码 + +**缺点:** + +- 实现更加复杂 +- 学习成本提高 + +**目前从我个人角度看,我觉得 Riverpod 时当前之下状态管理的最佳选择,它灵活且专注,体验上也更符合 Flutter 的开发习惯**。 + +> 注意,很多人一开始只依赖 `riverpod` 然后发现一些封装对象不存在,因为 `riverpod` 是不依赖 flutter 的实现,所以在 flutter 里使用时不要忘记要依赖 `flutter_riverpod` 。 + + + +# BLoC + +[BLoC](https://github.com/felangel/bloc) 算是 Flutter 早期比较知名的状态管理框架,它同样是存在 `bloc` 和 `flutter_bloc` 这样的依赖关系,**它是基于事件驱动来实现的状态管理**。 + + + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image17.png) + + + +**`flutter_bloc` 基于事件驱动的核心就是 `Stream` 和 Provider** , 是的, `flutter_bloc` 依赖于 Provider,然后在其基础上设计了基于 `Stream` 的事件响应机制。 + +所以严格意义上 BLoC 其实是 Provider + `Stream` ,如果你一直很习惯基于事件流开发模式,那么 BLoC 就很适合你,**但是其实从我个人体验上看,BLoC 在开发节奏上并不是快,相反还有点麻烦**,不过优势也很明显,基于 `Stream` 的封装可以更方便做一些事件状态的监听和转换。 + +```dart +BlocSelector( + selector: (state) { + // return selected state based on the provided state. + }, + builder: (context, state) { + // return widget here based on the selected state. + }, +) + +MultiBlocListener( + listeners: [ + BlocListener( + listener: (context, state) {}, + ), + BlocListener( + listener: (context, state) {}, + ), + BlocListener( + listener: (context, state) {}, + ), + ], + child: ChildA(), +) +``` + + + +**优点:** + +- 代码更加解耦,这是事件驱动的特性 +- 把状态更新和事件绑定,可以灵活得实现状态拦截,重试甚至撤回 + +**缺点:** + +- 需要写更多的代码,开发节奏会有点影响 +- 接收代码的新维护人员,缺乏有效文档时容易陷入对着事件和业务蒙圈 +- 项目后期事件容易混乱交织 + +> 类似的库还有 [rx_bloc](https://github.com/Prime-Holding/rx_bloc) ,同样是基于 `Stream` 和 Provider , 不过它采用了 rxdart 的 `Stream` 封装。 + + + +# flutter_redux + +[flutter_redux](https://github.com/brianegan/flutter_redux) 虽然也是 pub 上的 Flutter Favorite 的项目,但是现在的 Flutter 开发者应该都不怎么使用它,而恰好我在刚使用 Flutter 时使用的状态管理框架就是它。 + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image18.png) + +> 其实前端开始者对 redux 可能会更熟悉一些,当时我恰好用 RN 项目切换到 Flutter 项目,在 RN 时代我就一直在使用 redux,flutter_redux 自然就成了我首选的状态管理框架。 + +其实这也是 Flutter 最有意思的,很多前端的状态管理框架都可以迁移到 Flutter ,例如 flutter_redux 里就是利用了 `Stream`特性,通过 `redux` 单向事件流的设计模式来完成解耦和拓展。 + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image19.png) + +在 flutter_redux 中,开发者的每个操作都只是一个 `Action` ,而这个行为所触发的逻辑完全由 `middleware` 和 `reducer` 决定,这样的设计在一定程度上将业务与UI隔离,同时也统一了状态的管理。 + +当然缺陷也很明显,你要写一堆代码,开发逻辑一定程度上也不大符合 Flutter 的开发习惯。 + +**优点:** + +- 解耦 +- 对 redux 开发友好 +- 适合中大型项目里协作开发 + +**缺点:** + +- 影响开发速度,要写一堆模版 +- 不是很贴合 Flutter 开发思路 + +说到 redux 就不得不说 [fish_redux](https://pub.flutter-io.cn/packages/fish_redux) ,如果说 redux 是搭积木,那闲鱼最早开源的 fish_redux 可以说是积木界的乐高,闲鱼在 `redux` 的基础上提出了 `Comoponent` 的概念,这个概念下 `fish_redux` 是从 `Context` 、`Widget` 等地方就开始全面“入侵”你的代码,从而带来“超级赛亚人”版的 `redux` 。 + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image20.png) + +**所以不管是 flutter_redux 还是 fish_redux 都是很适合团队协作的开发框架,但是它的开发体验和开发过程,注定不是很友好**。 + + + +# GetX + +[GetX](https://pub.flutter-io.cn/packages/get) 可以说是 Flutter 界内大名鼎鼎,**Flutter 不能没有 GetX 就像程序员不能没有 PHP ,GetX 很好用,很具备话题,很全面同时也很 GetX**。 + + + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image21.png) + +严格意义上说现在 GetX 已经不是一个简单的状态管理框架,它是一个统一的 Flutter 开发脚手架,在 GetX 内你可以找到: + +- 状态管理 +- 路由管理 +- 多语言支持 +- 页面托管 +- Http GetConnect +- Rx GetStream +- 各式各样的 extension + +可以说大部分你想到的 GetX 里都有,甚至还有基于 GetX 的 [get_storage ](https://github.com/jonataslaw/get_storage) 实现纯 Dart 文件级 key-value 存储支持。 + +所以很多时候使用 GetX 开发甚至不需要关心 Flutter ,当然这也导致经常遇到的奇怪情况:*大家的问题集中在 GetX 里如何 xxxx,而不是 Flutter 如何 xxxx* ,**所以 GetX 更像是依附在 Flutter 上的解决方案**。 + +| ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image3.png) | ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image6.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +**当然,使用 GetX 最直观的就是不需要 `BuildContext ` ,甚至是你在路由跳转时都不需要关心 Context ,这就让你的代码看起来很“干净”,把整个开发过程做到“面向 GetX 开发”的效果** 。 + +另外 GetX 和 Provider 等相比还具备的特色是: + +- ` Get.put` 、`Get.find` 、`Get.to` 等操作完全无需 Widget 介入 +- 内置的 `extension` 如各类基础类似的 `*.obs` 通过 `GetStream` 实现了如 `var count = 0.obs;` 和 `Obx(() => Text("${controller.name}"));` 这样的简化绑定操作 + +那 GetX 是如何脱离 Context 的依赖?说起来也不复杂,例如 : + +- `GetMaterialApp` 内通过一个会有一个 `GlobalKey` 用于配置 `MaterialApp` 的 `navigatorKey` ,这样就可以通过全局的 `navigatorKey` 获取到 `Navigator` 的 `State` ,从而调用 `push` API 打开路由 + +- `Get.put` 和 `Get.find` 是通过一个内部全局的静态 `Map` 来管理,所以在传递和存放时就脱离了 `InheritedWidget` ,结合 `Obx` ,在对获取到的 `GetxController` 的 value 时会有个 `addListener` 的操作,从而实现 ` Stream` 的绑定和更新 + +| ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image22.png) | ![](http://img.cdn.guoshuyu.cn/20221109_Z2/image23.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +**可以说 GetX 内部有很多“魔法”,这些魔法或者是对 Flutter API 的 Hook、或者是直接脱离 Flutter 设计的自定义实现,总的来说 GetX “有自己的想法”**。 + +这也就带来一个了个问题,很多人新手一上手就是 GetX ,然后对 Flutter 一知半解,特别是深度解绑了 Context 之后,很多 Flutter 问题就变成了 GetX 上如何 xxxx,例如前面的: *Flutter GetX 如何调用谷歌地图这种问题*。 + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image24.png) + +> 如果使用 GetX 而不去思考和理解 GetX 的实现,就很容易在 Flutter 的路上走歪,比如上面各种很基础的问题。 + +这其实也是 GetX 的最大问题:**GetX 做的很多,它入侵到很多领域,而且它拥有很多“魔法”**,这些“魔法”让 Flutter 开发者不知布局的脱离了本来应有的轨迹。 + + + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image25.png) + +当然,你说我就是想完成需求,好用就行,何必关心它们的实现呢?从这个角度看 GetX 无疑是非常不错的选择,只要 GetX 能继续维护下去并把“魔法”继续兼容。 + +> 大概就是:**GetX “王国” 对初级开发者友好,但是“魔法全家桶”其实对社区的健康发展很致命**。 + +**优点:** + +- 瑞士军刀式护航 +- 对新人友好 +- 可以减少很多代码 + +**缺点:** + +- 全家桶,做的太多对于一些使用者来说是致命缺点,需要解决的 Bug 也多 +- “魔法”使用较多,脱离 Flutter 原本轨迹 +- 入侵性极强 + +总的来说,GetX 很优秀,他帮你都写好了很多东西,省去了开发者还要考虑如何去组合和思考的过程,从我个人的角度我不喜欢这种风格,但是它总归是可以帮助你提高开发效率。 + +**另外还有一个状态管理库 [Mobx](https://github.com/mobxjs/mobx.dart) ,它库采用了和 GetX 类似的风格,虽然 Mobx 的知名度和关注度不像 GetX 那么高,但是它同样采用了隐式依赖的模式,某种意义上可以把 [Mobx](https://github.com/mobxjs/mobx.dart) 看成是只有状态管理版本的 GetX**。 + +![](http://img.cdn.guoshuyu.cn/20221109_Z2/image26.png) + + + +# 最后 + +通过上面分享的内容,相信大家对于选哪个状态管理框架应该有自己的理解了,**还是那句废话,采用什么方案和框架具体还是取决于你的需求场景,不管是哪个框架目前都有坑和局限,重点还是在于它未来是否持续维护,或者不维护了你自己能否继续维护下去**。 + +最后,如果你还有什么疑问,或者针对 Flutter 工程选择上还有哪些茫然,欢迎留言评论。 + + + + + + + + + + + diff --git a/Z3.md b/Z3.md new file mode 100644 index 0000000..5cf11cc --- /dev/null +++ b/Z3.md @@ -0,0 +1,233 @@ +# Flutter 工程化框架选择 — 搞定 UI 生产力 + + + +``` +本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! +``` + +这是 《Flutter 工程化框架选择》 系列的第二篇 ,就像之前说的,这个系列只是单纯告诉你,创建一个 Flutter 工程,或者说搭建一个 Flutter 工程脚手架,应该如何快速选择适合自己的功能模块,或者说这是一个指引系列,所以比较适合新手同学。 + +本篇主要介绍 UI 相关的,**但是完全单纯介绍 UI 好像又有点水,那就是加一些小知识来吸吸水分吧**。 + +做为前端开发,我们的大部分职责就是开发 UI ,但是如果有人帮我们提前把 UI 做好,那岂不美哉?事实上很多时候 UI 确实是可以模版化,而前端领域也一直是这样,例如 `Ant Design` 、`Element-UI` 等 ,那 Flutter 上是否也有这样的支持? + +答案肯定是有的,但是在介绍它们之前,**我们先聊一个 Flutter UI 的问题:嵌套**。 + +> 为了不太水,我们前言聊技术,后半部分推荐项目。 + +# 前言- UI 嵌套 + +谈到 Flutter 肯定就有人说嵌套,是的, Flutter 本身就是通过直接嵌套 `Widget` 来渲染 UI , 所以大家可能就会吐槽类似下面的代码,虽然这段代码没有意义,但是这里我们要先思考两个问题: + +- **Flutter 怕不怕 `Widget `嵌套影响性能**? +- **Flutter 有没有办法解决嵌套**? + +![](http://img.cdn.guoshuyu.cn/20220923_Z3/image1.png) + +**首先第一点就是,Flutter 一般情况下不怕嵌套影响性能,因为 “众所周知” 的原因,Flutter 里的 `Widget` 并不是真正的控件,我更愿意说 `Widget` 是配置文件,真正的绘制和布局对象是它背后的 `RenderObejct` 等相关逻辑**。 + +> 嵌套层级看起来很多的原因,是因为 `Widget` 颗粒度太细。 + +当然这个还要看你嵌套的 `Widget` 做了什么?或者说是嵌套的 `Widget` 的 `RenderObject` 做了什么,例如: + +- `Padding` 的 `RenderPadding` 就是在 layout 时多了一个 `Size` 和 `childParentData.offset` 计算 +- `ColoredBox` 的 `_RenderColoredBox` 就是多一句 `drawRect` +- `Align` 的 `RenderPositionedBox` 就是多计算一个 `childParentData.offset` + +所以这些 `Widget` 的颗粒度很细,但是也很轻,我们直接用的时候可能会一层一层嵌套,看起来不美观,但是实际渲染时对性能影响不大。 + +> **当然,并不是所有的 `Widget` 都很轻不怕嵌套**,例如 `Clip` 、`Transform` 和 `Opacity ` 等,如果涉及到 `pushLayer` 等操作时,在需要做图层合成的时候,那确实对性能影响还是比较大的。 + +**那第二个问题,如何解决嵌套?这时候你就需要 “配置模版” ,通过封装来优化代码结构**。 + +“配置模版”是什么意思?举个例子 : `Container` 应该用过吧? `Container` 其实就是官方给大家准备的 “模版” ,它本身只是一个 `StatelessWidget` ,也就是一个没有 `RenderObject` 的 `Widget` ,它靠的就是把各种功能的 `Widget ` 组合起来使用,如下图就是使用 `Container` 的对比情况。 + +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image2.png) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image3.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +所以在使用 Flutter 构建 UI 时,就可以谈及到两个点: + +- `Widget` 是配置文件,它一般很轻,不怕嵌套,真正绘制渲染的是它背后的 `RenderObject` +- 通过各种 UI 配置模版来解决嵌套,特别是抽象出适合自己业务逻辑的 UI 模版 + +举个例子,如下方的第三方开源布局包 [dashboard](https://github.com/Mehmetyaz/dashboard) ,在这种复杂的 UI 布局上难道就靠直接一级一级嵌套 `Column` 和 `Row` 来解决?答案肯定不是! + +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image4.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image5.png) | +| --------------------------------------------- | ----------------------------------------------------------- | + +[dashboard](https://github.com/Mehmetyaz/dashboard) 的是通过 `Stack` + `Positioned` 组成模版,在手势移动时计算,通过 `AnimatedBuilder` 实现动画偏移,自动计算控件位置。 + +> 其实也可以直接用 `AnimatedPositioned` ,具体可见 [《Flutter 小技巧之有趣的动画技巧》](https://juejin.cn/post/7111071430292275213) + +所以可以看到, **Flutter 里你其实可以不那么嵌套,具体还是看你的封装方式**,除了这样,还有就是直接在 `RenderObject` 上进行自定义布局,比如下方这两个例子: + +| [cloud](https://github.com/CarGuo/gsy_flutter_demo/tree/master/lib/widget/cloud) | [custom_multi_render](https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/custom_multi_render_demo_page.dart) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![image-20220922120414245](http://img.cdn.guoshuyu.cn/20220923_Z3/image6.png) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image7.png) | + +你说上面的布局用 `Stack` + `Positioned` 的模式能不能做?肯定是可以,但是在 `RenderObject` 层实现不是更优雅吗?**把脏活累活写在 `RenderObject` , 通过 `Widget` 提供配置接口,这样就不会一上来就向用户露 “底裤” 不是么**。 + +> 所以,把眼界打开,不要什么都盯着 `Widget` 嵌套,往 `RenderObject` 层面探索,你会发现 Flutter 其实不像表面那么浮躁,再不济来尝试下 `CustomMultiChildLayout` ,它也可以帮助你解决一些复杂布局下的嵌套问题。 + +当然,还有一些项目另辟蹊径,**比如 [niku](https://github.com/SaltyAom/niku) ,这个项目通过 `typedef` 和抽象拓展,利用语法对官方控件进行二次封装**,实现创建了一个 “非正道” 的 UI 配置效果,具体如下图所示,喜不喜欢就看个人爱好了,但是它确实一定程度解决了嵌套可视化的问题。 + +![](http://img.cdn.guoshuyu.cn/20220923_Z3/image8.png) + + + +> 作者是个二次元,但是看地址他应该是泰国哥们 + +当然,我们这一期的关键是提高 UI 生产力,单说源码实现就没劲了,所以重点让我们看后半部分。 + +# UI 套件 + +在前端领域,**使用统一的 UI 套件可以加快开发的节奏,减少开发和设计之间的摩擦,而且风格统一**。一般情况下,在企业内部都是在不知不觉中沉淀下来各种组件,最后形成组件池,从而落地成 UI 套件。 + +比如**贝壳的 [bruno](https://github.com/LianjiaTech/bruno) ,我愿意称它为 Flutter 界的 `Element-UI`** ,目前已经支持到 Flutter 3 ,作为少有国内大厂维护的 Flutter UI 项目,甚至它还提供了 [sketch 设计指引](https://bruno.ke.com/page/guide/sketch) 和 [设计物料下载](https://bruno.ke.com/download/sketch) 。 + +| [bruno](https://github.com/LianjiaTech/bruno) | [getwidget](https://github.com/ionicfirebaseapp/getwidget) | [fsuper](https://github.com/Fliggy-Mobile/fsuper) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image9.png) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image10.png) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image11.png) | + +当然,除了 bruno 之后,像 [getwidget](https://github.com/ionicfirebaseapp/getwidget) 、 [fsuper](https://github.com/Fliggy-Mobile/fsuper) 也提供了日常开发中常用的 UI 套件,虽然风格风格上可能并没有 bruno 统一,但是还是可以在一定程度提高开发的生产力。 + +> 事实上对于个人开发者来说,这种套件可以解决很多设计上的问题。 + +**另外聊到 Flutter UI 套件就要一定要介绍国内的 [fluttercandies](https://github.com/fluttercandies)** 组织,fluttercandies 是由大佬们共同维护的一系列 Flutter 开源项目,记住,是大佬们,并且一直在持续更新: + +- [extended_image](https://pub.flutter-io.cn/packages/extended_image) +- [extended_nested_scroll_view](https://pub.flutter-io.cn/packages/extended_nested_scroll_view) +- [extended_text](https://pub.flutter-io.cn/packages/extended_text) +- [extended_text_field](https://pub.flutter-io.cn/packages/extended_text_field) +- [extended_list](https://pub.flutter-io.cn/packages/extended_list) +- [extended_sliver](https://pub.flutter-io.cn/packages/extended_sliver) +- [extended_tabs](https://pub.flutter-io.cn/packages/extended_tabs) +- [wechat_assets_picker](https://pub.flutter-io.cn/packages/wechat_assets_picker) +- [waterfall_flow](https://pub.flutter-io.cn/packages/waterfall_flow) + +> 举个例子,如果 flutter framework 短期不能解决的问题,那就大佬就会 cv 一份控件自己维护,这就是 fluttercandies 的节奏和优势。 + +## PC + +既然介绍 Flutter UI ,就不得不介绍 PC 相关的风格的 UI ,因为 Flutter 不只是 Android 和 iOS ,它还支持 Web 和 PC, 所以类似 PC 的 UI 风格也值得推荐,**比如 ant_design_flutter 就是一个很有意思的项目**。 + +| [fluent_ui](https://github.com/bdlukaa/fluent_ui) | [macos_ui](https://github.com/GroovinChip/macos_ui) | [ant_design_flutter](https://github.com/CalsRanna/ant_design_flutter) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image12.png) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image13.png) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image14.png) | + +## Responsive + +那有的人可能就说,**我想要一套代码适配多平台的屏幕尺寸**行不行?答案肯定是可以的,下面这几个 package 就提供了不同屏幕尺寸下一套代码的动态适配方案,我个人可能会比较喜欢 ResponsiveFramework 。 + +| [ResponsiveFramework](https://github.com/Codelessly/ResponsiveFramework) | [responsive_sizer](https://github.com/CoderUni/responsive_sizer/) | [flutter_adaptive_ui](https://github.com/mohammadtaherri/flutter_flexible_ui/tree/main/packages/flutter_adaptive_ui) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image15.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image16.png) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image17.png) | + +## Appbar + +这类 Appbar 的实现其实是我被问过最多的,其实它的核心实现都是 Sliver ,严格意义上我觉得并不需要第三方库,自己用 Sliver 就可以实现,但是本着能不动手就不动手原则,也推荐几个库吧: + +| [draggable_home](https://github.com/4-alok/draggable_home) | [extended_sliver](https://github.com/fluttercandies/extended_sliver) | [scroll_app_bar](https://github.com/edsonbonfim/scroll_bars/tree/master/scroll_app_bar) | +| ---------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image18.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image19.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image20.gif) | + +> [gsy_flutter_demo](https://github.com/CarGuo/gsy_flutter_demo) 里也提供了几种实现思路,其实并不复杂。 + +## Drawer + +可能有人会觉得,不会吧不会吧, Drawer 也需要第三方库? + +还真有,因为有时候可能需要不一样的动画效果,另外这里的 `sidebarx` ,也和官方提供的 `NavigationRail`有异曲同工之妙,能在 UI 上适配多平台的操作习惯。 + +| [sidebarx](https://github.com/Frezyx/sidebarx) | [flutter_advanced_drawer](https://github.com/alex-melnyk/flutter_advanced_drawer) | [curved_drawer](https://github.com/undrbridge/curved_drawer) | +| ---------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image21.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image22.png) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image23.GIF) | + + + +## Tarbar + +既然都说到 `Drawer `,那 `Tabbar` 也提供几个花里胡哨的动画效果,主要是切换时的动画效果,另外 `tab_container` 可能算是比较有意思的库,用的 `Path` 来编绘背景动画效果。 + +| [flutter-cupertino-tabbar](https://github.com/aliyigitbireroglu/flutter-cupertino-tabbar) | [tab_indicator_styler](https://github.com/adar2378/tab_indicator_styler) | [tab_container]( https://github.com/sourcemain/tab_container) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image24.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image25.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image26.gif) | + +## BottomBar + +说到 `Tabbar` 相对应的还有 BottomBar 相关,这里也提供几个库,主要是动画效果很有趣,我个人还是挺喜欢这种曲线的动画效果。 + +| [curved_navigation_bar](https://github.com/rafalbednarczuk/curved_navigation_bar) | [salomon_bottom_bar](https://github.com/lukepighetti/salomon_bottom_bar) | [bubble_bottom_bar](https://github.com/westdabestdb/bubble_bottom_bar) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image27.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image28.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image29.gif) | + +## 指引 + +启动指引这个需求,正常情况下一个 `PageView` 就可以满足产品经理的场景,但是有时候可能会需要你来“亿”点点动画效果来增加 KPI,所示拿着也许就对你有用了,当然你也可以把它当作 `PageView` 动画来使用。 + +| [concentric_transition](https://pub.dev/packages/concentric_transition) | [nice_intro](https://github.com/Ethiel97/nice_intro) | [intro_views_flutter](https://github.com/aagarwal1012/IntroViews-Flutter) | +| ------------------------------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image30.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image31.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image32.gif) | + +## 角标 + +这个应该无需多言了,基本上 App 都会需要用到,这两个库基本覆盖了 90% 的场景 + +| [flutter_badges](https://github.com/yadaniyil/flutter_badges) | [corner_decoration](https://github.com/kalaganov/corner_decoration) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image33.png) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image34.png) | + + + +## 动画按键 + +这个可能一般情况下大家都不需要这么花里胡哨的效果,但是万一呢?Material 风格上这种交互还是挺多的,不过国内对 Material 确实不是很感冒。 + +| [flutter_animated_button](https://github.com/NikhilVadoliya/FlutterAnimatedButton) | [progress_state_button](https://github.com/slm/progress-state-button) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image35.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image36.gif) | + +## 头像 + +没想到吧?头像为什么还需要库? + +其实就是下面的这个场景,相信这个场景可能大家都不会陌生,有社交需求的时候,经常会存在这样的 UI ,掘金沸点不也有类似 UI 么? + +| [avatar_stack](https://github.com/cyrax111/avatar_stack) | [overflow_view](https://github.com/letsar/overflow_view) | | +| -------------------------------------------------------- | -------------------------------------------------------- | ---- | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image37.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image38.gif) | | + + + +## swipe 卡片 + +这个需求可能一般人不会需要,推荐它是因为我还记得几年的时候,收了 1000 给人做了这样的一个外包,就是做一个这样的控件。 + +| [swipe_deck](https://github.com/retroportalstudio/swipe_deck.git) | [swipeable_card_stack](https://github.com/codetoart/cta-flutter-tinder-card-animation) | [appinio_swiper](https://github.com/appinioGmbH/flutter_packages/tree/main/packages/appinio_swiper) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image39.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image40.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image41.gif) | + + + +## bottom sheet + +这其实更多是一个 Route 相关的动画效果,感觉好像国内也不常用到,但是之前确实有好几次咨询有没有类似的实现。 + +| [we_slide](https://github.com/luciano-work/we_slide) | [sliding_up_panel](https://github.com/akshathjain/sliding_up_panel) | +| ---------------------------------------------------- | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image42.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image43.gif) | + + + +## 时间轴 UI + +这是我在群里被问过好多次的一个需求场景,我也不知道为什么那么多应用会需要用到这样的 UI ?不过这类需求自己从头实现确实会比较费事。 + +| [timeline_tile](https://github.com/JHBitencourt/timeline_tile) | [timelines](https://pub.dev/packages/timelines) | +| ------------------------------------------------------------ | ----------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image44.gif) | ![](http://img.cdn.guoshuyu.cn/20220923_Z3/image45.gif) | + +好了,关于 Flutter UI 相关的内容推荐就到这里,**本篇主要还是提供给大家如何理解 Flutter 的 UI 布局,并且尽可能去解决嵌套,同时提供一些有意思的第三方 package ,进一步提高大家开发 UI 的生产力**。 + +最后,如果你还有什么关于 Flutter 工程或者框架的疑问,欢迎留言评论,也许新的素材又有了~ \ No newline at end of file diff --git a/Z5.md b/Z5.md new file mode 100644 index 0000000..09248f6 --- /dev/null +++ b/Z5.md @@ -0,0 +1,408 @@ +# Flutter 工程化框架选择 — 搞定数据存储选型 + +``` +本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! +``` + +这是 《Flutter 工程化框架选择》 系列的第三篇 ,就像之前说的,这个系列只是单纯告诉你,创建一个 Flutter 工程,或者说搭建一个 Flutter 工程脚手架,应该如何快速选择适合自己的功能模块,可以说这是一个指引系列,所以比较适合新手同学。 + +> **Flutter 上关于数据存储或者数据库详细选型介绍的内容很少,也算是一个补全吧**。 + +本篇主要介绍数据存储相关,可能就有人会觉得,数据存储有什么好说的?不就是写个 Plugin 接个原生数据库就好了吗?这都能水? + +确实,最简单快捷的方法就是写个 Plugin ,通过 Dart 直接调用原生平台的数据存储能力,这样实现成本最低,但是对于 Flutter 来说可能不够优雅: + +- 通过 `MethodCallHandler` 调用的方式,中间过程存在一定程度的性能消耗,特别是读写数据量比较大的时候 +- Flutter 需要多平台支持,通过 `MethodCallHandler` 调用原生平台,就需要在每个平台的使用不同的代码和适配逻辑,会提高维护成本 + +**所以针对 Flutter 平台,现在社区的数据存储工具实现都默契的采用了另外一种方式**,当然不是说使用 `MethodCallHandler` 的 Plugin 实现不行,具体选型还是需要看你所需要的场景。 + +> Let's go 💗 ~ + +# Plugin 调用原生平台实现 + +首先简单介绍一下大家比较熟悉的两个数据存储的库,它们都是通过 Plugin 直接调用原生平台 API 进行数据存储: + +- **[shared_preferences](https://github.com/flutter/plugins/tree/main/packages/shared_preferences) key-value 的简单数据存储**,相信 Android 开发对这个名称会很亲切,官方维护,实现简单,支持全平台,适合对性能要求不高和数据量不大的场景 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image1.png) + +> 不过也是因为 `MethodCallHandler` 调用的方式,从维护和调用路径上会显得比较复杂,**federated plugin 的写法和版本管理方式容易出现问题**,已经不止一次在使用官方的 federated plugin 因为内部版本问题踩坑。 + +- [sqflite](https://github.com/tekartik/sqflite/tree/master/sqflite) 可以说是最早的第三方 Flutter 数据库之一,支持 iOS、 Android 和 MacOS ,为什么不支持全平台,**这其实也是 Plugin 直接调用多平台的问题之一,你需要重复在每个平台上实现同一个逻辑,虽然初期开发成本低,但是后续维护成本并不低**,同时 [sqflite](https://github.com/tekartik/sqflite/tree/master/sqflite) 提供的能力也比较弱,封装程度不高,主要还是解决了早期对数据库迫切支持的需求。 + +**既然说到 [sqflite](https://github.com/tekartik/sqflite/tree/master/sqflite) ,这里推荐一个 [flutter-sqlite-viewer](https://github.com/frgmt/flutter-sqlite-viewer) 的第三方工具**,相信大家在操作数据的时候都有可视化查看的需求,虽然开发过程中可以如下左图一样在 Android Studio 里通过 App Inspection 查看,但是有一些场景你没办法提供直连 Debug 调试,这时候 flutter-sqlite-viewer 就可以很方便提供本地化数据库查看的能力。 + +| ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image2.png) | ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image3.gif) | +| ----------------------------------------------------------- | -------------------------------------- | + + + +# sqlite3 + +从这里开始我们介绍不大一样的,[sqlite3](https://github.com/simolus3/sqlite3.dart/tree/master/sqlite3) 采用的是 `dart:ffi` 实现直接通过 Dart 访问数据库的能力,那这里的区别是什么? + +- dart 和 c/c++ 的相关代码可以跨平台,不需要维护多份同样的平台逻辑(虽然需要维护多份编译产物) +- dart 和数据库直接交互,省去中间过程的转换需要 +- **有问题了你不好调试** + +> 其实从 Flutter 2.0 和 Dart 2.12 提供 FFI 支持开始,到现在的 Dart 3.0 正式版发布,官方一直都在完善 ffi 还有和平台语言直接交互的能力,例如在 [Dart 2.18 里 Dart 就支持与 Objective-C 和 Swift 直接交互](https://juejin.cn/post/7137874832988831751)。 + +目前 sqlite3 支持 Android、iOS、Linux、MacOS、Window 平台,并且在特定平台还提供了切换到 [SQLCipher](https://www.zetetic.net/sqlcipher/) 的支持: + +- Android 平台可以**依赖 `sqlite3_flutter_libs` 来集成最新的 sqlite3 版本,也可以通过依赖 `sqlcipher_flutter_libs` 来切换到 SQLCipher** +- iOS 平台和 macOS 平台与 Android 类似 +- Windows 和 Linux 平台可以依赖 `sqlite3_flutter_libs` 来集成最新的 sqlite3 版本 +- Web 平台其实也支持,只是只支持在 WASM 模式下,也就是 `--web-renderer canvaskit ` 的时候使用,同时还需要一些额外的操作,例如把 `sqlite3.wasm` 放到 `web/` 目录下 + +> **当然其实 sqlite3 不只是支持 Flutter ,它的 ffi 也是 Dart 的能力,所以它也可以直接用在 dart server 上**。 + +所以在 sqlite3 里 Dart 就是通过 `dart:ffi` 直接和数据库交互,而 Plugin 里的内容更多只是一个初始化的作用,例如 `sqlcipher_flutter_libs` 的 Plugin 就是加载对应的 `sqlcipher.so` 。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image4.png) + + + +# Drift + +**可能是直接使用 sqlite3 还不够优雅,所以作者针对 sqlite3 又封装了一套 [Drift](https://github.com/simolus3/drift)** ,Drift 同样可以脱离 Flutter 运行,因为它底层 sqlite3 依然基于 `dart:ffi` ,支持 Android、iOS、macOS、Linux 、 Windows 和 web , **不同之处是它对事务、 migrations、复杂过滤、表达式、批量更新和 joins 等操作做了封装**,例如: + +- 根据注解生成映射代码 +- 对查询结果根据 Stream 实现自动更新 +- 更方便的管理 schema migrations 和 `CREATE TABLE` + +例如你可以通过如下所示进行聚合查询,将多个数据结果合并到一起: + +```dart +Future countTodosInCategories() async { + final amountOfTodos = todos.id.count(); + + final query = select(categories).join([ + innerJoin( + todos, + todos.category.equalsExp(categories.id), + useColumns: false, + ) + ]); + query + ..addColumns([amountOfTodos]) + ..groupBy([categories.id]); + + final result = await query.get(); + + for (final row in result) { + print('there are ${row.read(amountOfTodos)} entries in' + '${row.readTable(categories)}'); + } +} + +Stream averageItemLength() { + final avgLength = todos.content.length.avg(); + final query = selectOnly(todos)..addColumns([avgLength]); + return query.map((row) => row.read(avgLength)!).watchSingle(); +} + +``` + +当然,这并不是最有趣的,**Drift 里最有意思的是提供了 sql 解析器和分析器**,所以你可以通过 sql 语句来生成 API,并且还会在构建时提供错误警告。 + +| ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image5.png) | ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image6.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +这里顺便提一嘴,**在 Flutter 里通过状态管理工具往下传递 `database` 大家应该不陌生吧**, drift 官方同样建议使用 `provider` 或者 `riverpod` 往下传递 `database` 对象,例如: + +```dart +void main() { + runApp( + Provider( + create: (context) => MyDatabase(), + child: MyFlutterApp(), + dispose: (context, db) => db.close(), + ), + ); +} +``` + +同时,针对 sqlite,作者还提供了相关的选择建议: + +- 如果你不怕麻烦,想更轻量级,那么可以直接使用 [sqlite3](https://github.com/simolus3/sqlite3.dart/tree/master/sqlite3) +- 如果你只需要 Stream 实现自动更新,不需要自动生成 dart 查询映射,那可以选择 [sqlcool]([Homepage](https://github.com/synw/sqlcool)) +- 如果你需要和 Drift 功能类似,但是需要更灵活的自定义能力且不介意麻烦的话,可以选择 [floor](https://github.com/vitusortner/floor) + +最后,如下图所示,你还可以用 [db_viewer ](https://github.com/vanlooverenkoen/db_viewer) 来预览数据库内容数据。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image7.gif) + + + + + +# Realm + +可能不少人对 [realm](https://github.com/realm/realm-dart) 还并不了解,第一次接触 realm 是在 2016 年开发 React Native 的时候,那时候 realm 几乎就是我首选的数据库,它作为 SQLite 的替代,采用的是 MongoDB 的数据库方案。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image8.png) + +如今 realm 也开始支持 Flutter ,虽然还在 Beta(已经 Beta 挺久了),但是它同样采用了基于 `dart:ffi` 的实现,所以 realm 同样支持脱离 Flutter 纯 Dart 运行,并且支持 Android、iOS 、Linux、MacOS 、Windows 等平台运行。 + +> 关于 MongoDB 和 SQL 的差异对比这里就不说,主要就是关系型数据库与非关系型数据库的区别 + +那 realm 最大的特别之处是什么?**那就是它除了提供本地数据库能力之外,它还提供数据同步和后端存储能力**。 + +简单来说,就是 realm 的数据库支持实时同步,在此之前我设计的 React Native 项目里,就有基于 realm 很快就开发出一套支持数据备份和同步的聊天应用的场景。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image9.png) + +简单介绍一下,就是在 realm 的后台服务上,主要需要通过定义 Schema Table 和 sync 权限,就可以通过 realm SDK 在启动时同步数据,并对数据进行实时同步,而在 realm 上,你可以通过 `Credentials` 和 `User` 来定义角色的读写权限和管理数据。 + +| ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image10.png) | ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image11.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +如下图所示,开启 sync 之后,在 iPhone 模拟器上点击添加的任务,在 Android 模拟器上就会实时同步 iPhone 上对数据库的操作更改,并且 realm 的 sync 后台默认就支持集群服务,如果用户量不是特别大的情况下,拿来做聊天或者客服场景还是很可行的。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image12.gif) + + + +> [realm-dart-samples ](https://github.com/realm/realm-dart-samples)里同样提供了对应的例子,但是它的例子有问题,所以你需要自己注册 realm.io 上的服务,并且获取到 appId 后,替换 appId 并在 realm 后台创建自己的 Task 表,开启 sync 服务。 + +同时 realm 也提供 [realm-studio](https://github.com/realm/realm-studio) 支持数据库可视化的能力,当然,如果你使用了 realm 的数据同步服务,那么在 Atlas 上也可以实时看到对应的数据更新。 + +| ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image13.png) | ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image14.png) | +| ------------------------------------------------ | ----------------------------------------------------------- | + +不过还是那句话,目前 realm 还在 beta ,例如: + +- 很多 API 上可以看到文档紧缺 +- 不支持 reload ,sync 模式下一 reload 就crash + +| ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image15.png) | ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image16.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +另外,比如我不说,你翻阅文档也很难找到 Flutter 上如何监听和同步查询结果变化,而其实这部分代码其实你只需要通过 `stream` 就可以接入实现。 + +```dart +StreamBuilder>( + stream: MyApp.allTasksRealm.all().changes, + builder: (c, s) { + return Text((s.data != null) ? s.data!.results.length.toString() : "null", + style: Theme.of(context).textTheme.headline4); + }) +``` + +所以目前在 Flutter 上,如果在其他平台之前用过 realm ,那可以试试;但是如果没有,建议先不躺坑,等正式版吧。 + +> 其实和 Realm 一样具有实时同步能力,同样是基于文件的非关系数据库,并且更稳定更可靠的数据库是 [firebase_database](https://github.com/firebase/flutterfire/tree/master/packages/firebase_database/firebase_database) ,不过周所周知的欢迎,国内基本不会选择它。 + +# ObjectBox + + [ObjectBox ](https://github.com/objectbox/objectbox-dart) 其实和 Realm 类似,也是 NoSQL 类型的数据库,同样是基于 `dart:ffi` ,支持 Android 、iOS 、Linux 、MacOS 和 Window,**号称在能耗和速度上有绝对的优势,同时因为是纯 Dart API,所以它完全不需要你熟悉或者学习 SQL 语法** 。 + +```dart +@Entity() +class Person { + int id; + + String firstName; + String lastName; + + Person({this.id = 0, required this.firstName, required this.lastName}); +} + +final store = await openStore(); +final box = store.box(); + +var person = Person(firstName: 'Joe', lastName: 'Green'); + +final id = box.put(person); // Create + +person = box.get(id)!; // Read + +person.lastName = "Black"; +box.put(person); // Update + +box.remove(person.id); // Delete + +// find all people whose name start with letter 'J' +final query = box.query(Person_.firstName.startsWith('J')).build(); +final people = query.find(); // find() returns List +``` + +> 目前 objectbox 支持 dart 、java、kotlin 、swift 甚至还支持 GO 和 Python + +在使用体验上,ObjectBox 可能会更贴近 NoSQL 的操作习惯, 另外它也可以直接在服务端被使用,从官方提供的基准测试中看,ObjectBox 的整体性能确实很优秀(其中的 Hive 后面介绍)。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image17.png) + +> 对测试感兴趣的可以看 [objectbox-dart-performance](https://github.com/objectbox/objectbox-dart-performance) ,不要问我它是不是真的这么好用,因为我也没在生产项目上使用它。 + +ObjectBox 另外一个特点就是支持离线数据同步:[ObjectBox Sync](https://objectbox.io/sync/) ,和 realm 还有 firebase 类似,这其实已经成为 NoSQL 服务商都会提供的特色之一。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image18.png) + +ObjectBox Sync 离线同步的支持主要在:**当设备离线时数据操作会被保存在本地,当设备连接上网络时,数据会恢复同步**。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image19.png) + +> 抛开 sync 能力不谈,ObjectBox 作为本地数据库在性能和开发体验上真的很不错。 + +# Hive + +前面介绍的都是比较重的数据库类型,那接下来介绍个轻量型的存储框架: [Hive ](https://github.com/hivedb/hive/tree/master/hive)。 + +**Hive 是一个纯 Dart 实现的轻量 key-value 数据库,主要通过 dart/io 对文件进行读写**,支持 Android 、iOS、Linux、MacOS、Window、Web 平台。 + +作为 key-value 数据库,它其实很适合用来替代 shared_preferences ,并且它支持使用 AES 进行加密,目前官方提供的基准测试数据上性能表现还不错(虽然数据一大可能就拉胯)。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image20.png) + +Hive 的使用介绍这里就不赘述了,因为真的很简单直接,在 Hive 里: + +- Box 就类似 SQL 里的表 + +- Object 就类似于数据库中的实体对象 + +- Adapter 可以用来做自定义对象的适配器,可以用于实现一些`read` / `write` 操作 + +```dart +import 'package:hive/hive.dart'; + +void main() async { + Hive.registerAdapter(PersonAdapter()); + var persons = await Hive.openBox('persons'); + + var person = Person() + ..name = 'Lisa'; + + persons.add(person); // Store this object for the first time + + print('Number of persons: ${persons.length}'); + print("Lisa's first key: ${person.key}"); + + person.name = 'Lucas'; + person.save(); // Update object + + person.delete(); // Remove object from Hive + print('Number of persons: ${persons.length}'); + + persons.put('someKey', person); + print("Lisa's second key: ${person.key}"); +} + +@HiveType() +class Person extends HiveObject { + @HiveField(0) + String name; +} + +class PersonAdapter extends TypeAdapter { + @override + final typeId = 0; + + @override + Person read(BinaryReader reader) { + return Person()..name = reader.read(); + } + + @override + void write(BinaryWriter writer, Person obj) { + writer.write(obj.name); + } +} +``` + +**另外 Hive 也支持如 `Hive.openLazyBox` 进行懒加载和 `compactionStrategy` 压缩数据来针对大数据量进行优化**,但是正如作者在 [#782 ](https://github.com/hivedb/hive/issues/782) 介绍的,当数据超过一定数量时,比如 50,000 ,那从性能上还是使用 SQLite 更好。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image21.png) + +当然,在 [#170](https://github.com/hivedb/hive/issues/170#issuecomment-573874355) 里有通过对 `isolate` 提供共享内存的支持来优化 Hive ,但是很明显这条路是走不通的。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image22.png) + +另外目前针对 **Hive 好像没有比较合适的可视化工具,这也许会影响部分人在选型上的考量**,不过 [hivedb](https://docs.hivedb.dev/#/more/vscode-snippets) 在 VSCode 上提供了模版代码片段的支持,也算是具备另外一种“微弱”的优势。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image23.gif) + +> PS:其实你可以拿 hivedb 当简单状态管理用,并且 Hive 脱离 Flutter 放在 dart server 端也是可以的运行。 + +**同样支持 key-value 高效存储的还有 [MMKV](https://github.com/Tencent/MMKV/tree/master/flutter ) ,MMKV 同样是利用 `dart:ffi` 支持了 Flutter ,相信 Android 开发对它不会陌生, 另外 [GetStorage](https://github.com/jonataslaw/get_storage) 也是基于二进制文件的键值对存储,不过更“低能”一些**。 + +# isar + +**也许是因为觉得 Hive 不够优秀,或者是 Hive 不支持高级查询,所以作者后续新推了新的数据库框架: [isar](https://github.com/isar/isar/tree/main/packages/isar)** ,同样基于 `dart:ffi` ,支持 Android 、iOS、Linux、MacOS、Window、Web 平台。 + +**如果说 Hive 可以简单代替 shared_preferences ,那 isar 就更像是平替 sqlite 的数据库**,所以 isar 更像是为了解决 query 问题而做的 Hive2.0 ,例如: + +- 支持复合和多索引、查询修饰符、JSON等 +- 多 isolate 支持 +- 支持十万条记录存储在单个 NoSQL 数据库 + +和 Hive 相比 isar 功能更丰富,不再只是单纯的 key-value 操作,可以支持更复杂的 filter 等查询条件,从使用体验上更符合 NoSQL 的数据库能力。 + +```dart +await isar.writeTxn(() async { + final idsOfUnstarredContacts = await contacts.filter() + .isStarredEqualTo(false) + .idProperty() + .findAll(); + + contacts.deleteAll(idsOfUnstarredContacts); +}); +``` + +同时 isar 提供了强大的可视化工具 ,通过 Isar Inspector 也是在一定程度补全了 Hive 的不足,使用 Inspector 只需要在 open 时设置 `inspector: true` 就可以看到 ws 地址进行绑定访问。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image24.gif) + +其实 isar 另外的好处就是完全开源,从构建脚本到逻辑代码都是开源,例如其中通过 [`Cargo.toml` ](https://github.com/isar/isar/blob/main/packages/isar_core_ffi/Cargo.toml) 编排 `isar_core_ffi `,然后在 github action 会在发布时通过 shell 脚本执行如何构建 isar 的动态库等。 + +| ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image25.png) | ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image26.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +例如 isar 在 iOS 上最重接入的是 `isar.xcframework` ,在 Android 上是 `isar.so` + +| ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image27.png) | ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image28.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +目前集成 isar 库本身并不是很大,例如在 Android 上集成之后,大小只增加了大概 600 多k ,所以作为 Hive 的升级版本,isar 确实值得一试,。 + +| ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image29.png) | ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image30.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +> 目前由于 Isar Web 依赖于 IndexedDB ,所以可能和其他平台相比较存在一定限制。 + +最后我们看来自 [guide-to-isar](https://itnext.io/a-minimalist-guide-to-isar-ee43c1e51a85) 和 [flutter-db-benchmarks](https://github.com/msxenon/flutter-db-benchmarks) 的一份数据对比: + +- 左边的数据对比提供了在 50,000 条数据下 + - 插入耗时 isar < ObjectBox < Realm + - 删除耗时 isar < Realm < ObjectBox + - 数据库大小 isar < Realm < ObjectBox + - 条件查询 isar < Realm < ObjectBox +- 右边的数据对比了在 10,000 条数据下 + - 插入耗时 ObjectBox < isarAsync < isarSync < Hive + - 读取耗时 Hive < ObjectBox < isarAsync < isarSync + - 更新耗时 ObjectBox < isarAsync < isarSync < Hive + - 删除耗时 ObjectBox < isarAsync < isarSync < Hive + +| ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image31.png) | ![](http://img.cdn.guoshuyu.cn/20220928_Z5/image32.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +这两份数据虽然看起来有矛盾点,但是这其实和测试环境有关系: + +- 左侧的图片时在较为正常设备上的测试,可以视为一般情况 + +- 右侧的数据测试的是在低端设备上的表现,可以视为最坏的情况 + +就像 [#211](https://github.com/isar/isar/discussions/211) 里讨论的,同样的基准测试,作者在它的设备上测试反而 isar 表现更好,讨论的结论上看也是,**除了速度之外,在是否导致 UI 卡顿上 isar 的表现也更好,所以基准测试的覆盖范围也是一种考量**。 + +![](http://img.cdn.guoshuyu.cn/20220928_Z5/image33.png) + + + + + +# 最后 + +最后,本篇介绍了 [shared_preferences](https://github.com/flutter/plugins/tree/main/packages/shared_preferences) 、 [sqlite3](https://github.com/simolus3/sqlite3.dart/tree/master/sqlite3) 、 [Drift](https://github.com/simolus3/drift) 、 [realm](https://github.com/realm/realm-dart) 、 [ObjectBox ](https://github.com/objectbox/objectbox-dart) 、 [Hive ](https://github.com/hivedb/hive/tree/master/hive) 和 [isar](https://github.com/isar/isar/tree/main/packages/isar) 等数据存储框架,简略带过的还有 [sqlcool]([Homepage](https://github.com/synw/sqlcool)) 、 [floor](https://github.com/vitusortner/floor) 、 [MMKV](https://github.com/Tencent/MMKV/tree/master/flutter ) 和 [GetStorage](https://github.com/jonataslaw/get_storage) 等,可以看到,**这份数据也侧面体现了目前 Flutter 生态的健全,至少在数据存储框架上就可以让人产生“选择困难症”** 。 + +如果你还有什么关于 Flutter 工程或者框架的疑问,欢迎留言评论,这个系列是否更新就取决于是否还有新的素材~ \ No newline at end of file diff --git a/Z6.md b/Z6.md new file mode 100644 index 0000000..33e9184 --- /dev/null +++ b/Z6.md @@ -0,0 +1,164 @@ +# Flutter 工程化框架选择 — 混合开发的摸爬滚打 + +``` +本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! +``` + +这是 《Flutter 工程化框架选择》 系列的第四篇 ,就像之前说的,这个系列只是单纯告诉你,创建一个 Flutter 工程,或者说搭建一个 Flutter 工程脚手架,应该如何快速选择适合自己的功能模块,可以说这是一个指引系列,所以比较适合新手同学。 + +> **本篇将着重介绍混合开发里的一些技术选型,顺便额外科普一些坑,看到后面你就会明白为什么要做这一期分类,算是相对偏技术的一期**。 + +在 Flutter 里进行混合开发一直都是“痛点”,其中最主要的原因就是:**Flutter 有自己独立的渲染引擎,因为 Flutter 控件渲染脱离了平台控件的 render tree,这也造成了 Flutter 在混合开发上的实现相对复杂了不少**。 + +> 比较形象的理解:类似于把原生控件塞到 WebView 里渲染。 + +事实上在 Flutter 里混合开发也分为两种:**在 Flutter 里混合原生平台控件**(`PlatformView`)和**将 Flutter 混合到原生平台项目(`add-to-app `)**,今年我们的主题是 `PlatformView` ,这其中又以 Android 的 `PlatformView` 混合原生控件“故事最多”,当然今天我们不是从头去讲解它的技术实现,对历史技术实现感兴趣的可以看之前的文章,今天主要是聊如何去“选“。 + +> - [Flutter 3.0下的混合开发演进](https://juejin.cn/post/7113655154347343909) +> - [Flutter 深入探索混合开发的技术演进](https://juejin.cn/post/7093858055439253534) +> - [混合开发下 HybridComposition 和 VirtualDisplay 的实现与未来演进](https://juejin.cn/post/7071549421116194847) +> - [《1.20 下的 Hybrid Composition 深度解析》](https://juejin.cn/post/6858473695939084295) +> - [《 Android PlatformView 和键盘问题》](https://juejin.cn/post/6844904070906380296) + + + +# PlatformView + +在 Flutter 里混合原生平台的控件需要通过 `PlatformView` 进行接入实现,**在这方面 iOS 的实现方式一直变化不大,但是在 Android 上不同版本到目前就出现了三种实现逻辑**,所以本篇主要介绍 Android 平台如何选择合适的 `PlatformView` 方案。 + +抛开最早期如 [flutter_webview_plugin](https://pub.dev/packages/flutter_webview_plugin) 这种直接进行覆盖原生控件的实现不谈, `PlatformView` 在 Android 上目前官方提供的就有三种实现方式: + +- **`VirtualDisplays`** :类似于一个虚拟显示区域,将虚拟显示区域的内容渲染在一个内存 `Surface`上,然后同步纹理到 Engine ,事实上控件并不在渲染位置。 + +![](http://img.cdn.guoshuyu.cn/20221011_Z6/image1.png) + +- **`HybridComposition`** : 通过 `addView` 直接添加原生平台控件,需要层级覆盖时利用 `FlutterImageView` 承载 Flutter 控件的渲染进行堆叠,原生控件会在渲染位置。 + +![](http://img.cdn.guoshuyu.cn/20221011_Z6/image2.png) + + + +- **`TextureLayer`** :在渲染位置通过原生的透明 ` PlatformViewWrapper` 做容器,然后通过替换 Canvas 将原生控件的纹理渲染到特定 Surface 上,控件不在渲染位置,但是可以通过父容器做事件拦截。 + +![](http://img.cdn.guoshuyu.cn/20221011_Z6/image3.png) + +> 是不是看完有点懵?不怕后面会有对应的例子解释,不理解也不要紧,这一篇是教你如何选,所以也并不需要真的理解内部是如何实现。 + +但是上面介绍的这三种实现也是有前提条件的,比如: + +- Flutter 1.2 之前,你只能用 `VirtualDisplays` +- Flutter 3.0 之前,你只能用 ` VirtualDisplays` 和 `HybridComposition` +- Flutter 3.3 之前,你只能用 `TextureLayer` 和 `HybridComposition` +- Flutter 3.3 开始,你能用 `VirtualDisplays`、 `TextureLayer` 和 `HybridComposition` + +是不是觉得有些奇怪,为什么会有奇怪的排列方式?这里面的故事线大概是这样的: + +- 因为 `VirtualDisplays` 存在触摸事件和键盘等问题,所以通过 `addView` 进行原生堆叠的 `HybridComposition` 出现,解决了手势和键盘等问题 +- 因为 `HybridComposition ` 存在一些场景缺陷,比如列表卡顿,渲染线程不同步导致闪动等问题,所以 `VirtualDisplays` 得以继续服役 +- 3.0 开始默认 `VirtualDisplays` 和 `HybridComposition` 被替换为 `TextureLayer` ,但是 `HybridComposition` 实现依旧保留,在使用时需要显式执行 **`PlatformViewsService.initExpensiveAndroidView`** 才能使用,因为 `TextureLayer` 存在没办法替代原生控件 Canvas 的场景,同时 `TextureLayer` 要求 Flutter Mini SDK API 23 +- 3.3 开始 `VirtualDisplays` 实现又回来了,因为需要支持 Mini SDK API 23 以下的场景,同时比如 `SurfaceView` 机制原因,这类场景 `TextureLayer` 实现没办法直接兼容,所以 3.3 开始在遇到 `SurfaceView` 实现时 `TextureLayer` 会切换为 `VirtualDisplays` 模式 + +> **这里面固然有实现成本的问题,同时也有社区把控的问题,才造成了这样“混乱”的局面**。 + +上面这么说可能有些抽象,我们再看具体例子,首先看官方的 [webview_flutter](https://pub.dev/packages/webview_flutter) 实现, 如下表格所示,最初的时候 WebView 插件默认是使用了 `AndroidWebView` ,也就是 `VirtualDisplays` 相关的实现;后来有了 `HybridComposition` 之后就新增了 `SurfaceAndroidWebView` ,用户在需要时可以根据场景自行切换。 + +| 默认判断 | VirtualDisplays | HybridComposition | +| ----------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image4.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image5.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image6.png) | + +这里就不得不提以前的 issue ,如果你的 WebView 存在触摸或者键盘问题就可以通过下述代码来解决,因为正如前面说过, `HybridComposition` 是通过 `addView` 的方式添加原生控件,所以能尽可能保存控件本身的特性。 + +```dart +if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); +``` + +而后来 webview_flutter 在更新迭代里默认实现里也修改为了 `SurfaceAndroidWebView` ,因为 `HybridComposition` 确实更适合 WebView 的场景,比如 H5 的详情页。 + +| 默认判断 | TextureLayer | HybridComposition | +| ----------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image7.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image8.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image9.png) | + +上面的例子可能还不够典型,接着我们再看一个典型的例子: MapView ,如下所示,目前国内 Flutter 里常见的地图实现有: + +- 华为:[huawei_map](https://pub.dev/packages/huawei_map) +- 高德:[amap_flutter_map](https://pub.flutter-io.cn/packages/amap_flutter_map) +- 百度: [flutter_baidu_mapapi_map ](https://pub.flutter-io.cn/packages/flutter_baidu_mapapi_map) + +虽然他们都是采用了 `PlatformView` 的实现,但是选择的实现方式却存在一定差异,而通过这些差异也可以很好比对 `PlatformView` 实现的区别: + +- [高德 Flutter 插件](https://developer.amap.com/api/flutter/guide/map-flutter-plug-in/map-flutter-info)采用的是 `AndroidView` 的实现方式,也就是在 3.0 以下是 `VirtualDisplays` ,在 3.0 以上是 `TextureLayer` , 从表中的图 2 也可以看到在 Flutter 3.0 MapView 父容器对应的 `TextureLayer` 的 ` PlatformViewWrapper` 。 + +| dart 实现 | TextureLayer | +| ----------------------------------------------------------- | ----------------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image10.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image11.png) | + +这里额外需要注意的是:**由于隐私政策等原因,现在基本地图框架都需要做一定的隐私相关适配**,高德也不例外,所以如果想正常使用 SDK ,就需要注意初始化时的适配问题。 + +![](http://img.cdn.guoshuyu.cn/20221011_Z6/image12.png) + +> 对隐私和权限问题感兴趣可以看 [《2022 年 App 上架审核问题集锦,全面踩坑上线不迷路》](https://juejin.cn/post/7142363251911688222) + +- [华为 Flutter 地图插件](https://pub.dev/packages/huawei_map)选择的则是 `HybridComposition` 的实现方式,注意下图代码里的 `initExpensiveAndroidView ` ,因为**现在使用 `PlatforViewLink` 已经不能作为是否使用 `HybridComposition` 的判定标准**,3.0 开始必须显式配置了 `initExpensiveAndroidView ` 才是 `HybridComposition` 。 + +![](http://img.cdn.guoshuyu.cn/20221011_Z6/image13.png) + + + +| 3.0之前 `PlatforViewLink` | 3.0之后 `PlatforViewLink` + `initSurfaceAndroidView` | 3.0之后 `PlatforViewLink` + `initExpensiveAndroidVie` | +| -------------------------- | ------------------------------------------------------ | ------------------------------------------------------- | +| `HybridComposition` | `TextureLayer` | `HybridComposition` | + +> 对 `HybridComposition` 详细实现感兴趣的可以了解:[混合开发下 HybridComposition 和 VirtualDisplay 的实现与未来演进](https://juejin.cn/post/7071549421116194847) 和 [《1.20 下的 Hybrid Composition 深度解析》](https://juejin.cn/post/6858473695939084295) + +- [百度 Flutter 地图插件](https://lbsyun.baidu.com/index.php?title=flutter/loc/create-project/configure)采用的是 `AndroidView` 的实现方式,但是原生端提供了 `TextureView` 和 `SurfaceView` 两种实现选择,这又产生了不同的组合结果。 + +![](http://img.cdn.guoshuyu.cn/20221011_Z6/image14.png) + + + +| 3.0之前 | 3.0 | 3.0之后 | +| ----------------- | ---------------------------------------------- | ------------------------------------------------------------ | +| `VirtualDisplays` | `TextureLayer` ( `SurfaceView` 方案无法工作) | `VirtualDisplays`(SurfaceView)/ `TextureLayer`(TextureView) | + +从下方的 Layout Inspector 可以看到,在 Flutter 3.3 下,**百度地图插件如果使用了 `MapSurfaceView` 就会采用 `VirtualDisplays` ,而如果使用了 `MapTextureView` 就会采用 `TextureLayer` ,这是为什么呢**? + +| SurfaceView | TextureView | +| ----------------------------------------------------------- | ----------------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image15.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image16.png) | + +前面我们不是说过,Flutter 在 3.0 的时候通过 `TextureLayer` 取代了 `VirtualDisplays` ,而 `TextureLayer` 的实现方式就是通过 ` PlatformViewWrapper` 来替换 child 的 Canvas 从而提取纹理,但是在某些场景下就会有问题,例如 `SurfaceView` 。 + +> 简单来说,`SurfaceView` 是比较特殊的场景,本身它具备双缓冲机制,类似于两个 `Canvas` ,同时在 WMS 有独立的 `WindowState `, 所以渲染逻辑其实是脱离了原本的 View hierachy ,因此 Flutter 3.0 下的 `TextureLayer` 的 hook Canvas 实现没办法兼容。 + +所以在 Flutter 3.3 中 `VirtualDisplays` 又回来了,作为多年服役的"老将",**当运行平台低于 23 或者原生控件是 `SurfaceView` 时,就会继续降级采用 `VirtualDisplays` 的相关实现,这也算是折中的实现**。 + +| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image17.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image18.png) | +| ----------------------------------------------------------- | ----------------------------------------------------------- | + +> 如果对这部分感兴趣的可以查阅 [Flutter 3.0下的混合开发演进](https://juejin.cn/post/7113655154347343909) / [Flutter 深入探索混合开发的技术演进](https://juejin.cn/post/7093858055439253534) + +所以现在知道为什么要做这一期了吧?**Flutter 的 `PlatformView` 实现因为历史包袱留下了很多“坑”,不理清楚这些坑,很可能就会让你在遇到问题时找不到方向**,总结一下: + +- `VirtualDisplays` 老骥伏枥,采用的是虚拟显示提取纹理的方式,可能存在手势和键盘问题,适合列表和小控件场景,在 3.0 版本中缺席 +- `HybridComposition` 在 1.2 版本之后出现,因为是通过 `addView` 和层级堆叠实现混合,能尽可能保证控件的“原汁原味”,但是性能开销会比较大,不适合列表里使用,在 3.0 之后需要显式调用 `initExpensiveAndroidView` +- `TextureLayer` 在 3.0 版本开始出现,通过 Hook 住控件 Canvas 的方式实现纹理提取,但是需要 miniSDK 23 ,适用于替代 `VirtualDisplays` 的场景,但是在 3.3 开始遇到 `SurfaceView` 时会降级为 `VirtualDisplays` 。 + +> 其实我有预感,在之后的版本这部分逻辑还会出现变化,但是可能 3.0 和 3.3 的发布一样,并不会将这种变更放在 release note 里。 + +最后,这里额外介绍一个关于地图的小知识点,**现在国内三大地图 SDK 厂商都有商用 5 万起步的授权费用要求**,如果你打算用在公司的项目里,那么这个是逃不掉的,因为迟早平台会打电话联系你缴费,所以在评估时最好将这个费用问题和产品沟通下。 + +| 高德 | 百度 | 腾讯 | +| ----------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image19.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image20.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image21.png) | +| ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image22.png) | ![](http://img.cdn.guoshuyu.cn/20221011_Z6/image23.png) | ![image-20221009093013634](http://img.cdn.guoshuyu.cn/20221011_Z6/image24.png) | + +当然,华为的地图服务目前看起来没有商业授权的要求,但是它打包配置麻烦,特别是需要 HMS 的场景,所以如果为了省点钱又不怕麻烦,也可以考虑华为地图。 + +![](http://img.cdn.guoshuyu.cn/20221011_Z6/image25.png) + +那有人就要说了,我自己做一个行不行?我比较牛,有数据有能力自己做个简易版地图不就免费了?答案时不行,如下所示,应用平台会有关于地图相关的资质要求,如果没有你做出来了也上不去。 + +> **审核意见:我们发现您的应用内含有互联网地图服务相关内容,依据《关于加强互联网地图管理工作的通知》互联网地图服务单位应当依法取得相应的互联网地图服务测绘资质,并在资质许可的范围内提供互联网地图服务。 ­修改建议:请补充提供《测绘资质证书》并在专业范围中注明互联网地图服务;若接入第三方开放平台地图服务的,可提供双方合作协议(授权证明),同时需在地图显著位置补充标明合作单位。** + +好了,本篇就到这里结束了,可能相对而言会比之前的内容更“技术性”一些,但是本质还是希望可以帮助大家搞清楚 `PlatformView` 在选型上的区别,最后,如果你还有什么关于 Flutter 工程或者框架的疑问,欢迎留言评论, +