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 工程或者框架的疑问,欢迎留言评论, +