diff --git a/Dart-320.md b/Dart-320.md new file mode 100644 index 0000000..0bb2a1e --- /dev/null +++ b/Dart-320.md @@ -0,0 +1,101 @@ +# Dart 3.2 更新,Flutter Web 的未来越来越明朗 + +> 参考原文:https://medium.com/dartlang/dart-3-2-c8de8fe1b91f + +本次跟随 [Flutter 3.16 发布](https://juejin.cn/post/7301574930869321779) 的 Dart 3.2 ,包含有:私有 final 字段的非空改进、新的 interop 改进、对 DevTools 中的扩展支持、以及对 Web 路线图的更新,包括对 Wasm 的Web 组件支持。 + +> 最重要的就是 Wasm 的Web 组件支持。 + +# private final 的非空类型提升 + +自 Dart 2.12 发布 sound null safety 以来,类型提升一直是空安全的核心部分之一,但仅限于局部变量里,字段和顶级变量无法处理,例如在这样的情况下会报错: + +```dart +class Container { + final int? _fillLevel; + Container(this._fillLevel); + check() { + if (_fillLevel != null) { + int i = _fillLevel; // Prior to Dart 3.2, causes an error. + } + } +} +``` + +这种限制是由于几个复杂的情况造成的,在这些情况下,flow analysis 无法确定字段何时会发生什么变化,例如,在字段提升的情况下,如果子类使用 getter 覆盖字段(有时会返回 null),这就可能会出现问题。 + +> 在 Dart 3.2 开始,Dart 改进了 flow analysis ,现在能够归类出 **private Final fields**。 + +现在 3.2 里上面的代码片段可以顺利通过检测:对于 private & final 的字段,它的值在初始分配后永远不会改变,因此仅检查一次就被认为是安全的。 + +# 包中的新代码分析选项:lints 3.0 + +3.2 还对 [package:lints](https://pub.dev/packages/lints) 中的标准代码分析规则进行了一些改进,package 包含了默认和推荐的静态分析规则集,这些规则随 `dart create` 或 `flutter create` 创建的新项目一起提供 (通过 [package:flutter_lints](https://pub.dev/packages/flutter_lints) )。 + +该 lint 集的主要版本(3.0)目前已经发布,其中向核心集添加了 6 个 lint,向推荐集添加了 2 个 lint,它具有用于验证 pubspec URL、验证是否使用正确参数调用集合方法等相关的 lints 。 + +> 有关更改的完整列表,请查看 https://github.com/dart-lang/lints/blob/main/CHANGELOG.md#300, 3.0 版本将成为即将发布的新项目的默认版本。 + +# Dart interop 更新 + +目前正在努力扩展 Dart interop 以全面支持与 [Java 和 Kotlin](https://dart.dev/guides/libraries/java-interop) 和 [Objective C 和 Swift](https://dart.dev/guides/libraries/objective-c-interop) 的直接调用支持,从 Dart 3.2 开始进行了许多改进: + +- 引入了 C FFI 的构造函数 `NativeCallable.isolateLocal` ,它可以从任意 Dart 函数创建一个 C 函数指针,这是 `Pointer.fromFunction` 提供的功能的扩展,它只能从顶级函数创建函数指针。 + +- 更新了 Objective-C 利用`NativeCallable.listener` 绑定生成器,生成器现在可以自动处理包含异步回调的 API,例如 [Core Motion](https://developer.apple.com/documentation/coremotion) 这种以前需要手动绑定的代码。 + +- 改进 [package:jnigen](https://dart.dev/guides/libraries/java-interop) 实现 Java 和 Kotlin 的直接调用支持,现在我们能够将 [package:cronet_http](https://pub.dev/packages/cronet_http)(Android Cronet HTTP 客户端的包装器)从手写绑定代码迁移到[自动生成的](https://github.com/dart-lang/http/blob/master/pkgs/cronet_http/jnigen.yaml)包装器。 + +- 在 [Native Assets](https://github.com/dart-lang/sdk/issues/50565) 功能上取得了重大进展,该功能旨在解决与依赖于 Native 代码的 Dart 包分发相关的许多问题,它通过提供统一的钩子来与构建 Flutter 和独立 Dart 应用所涉及的各种构建需要,详细可见 :http://dart.dev/guides/libraries/c-interop#native-assets + + > Native Assets 目前是一个**实验性的**功能,它可以让 Dart 包更无缝依赖和使用 Native 代码,通过 `flutter run`/`flutter build ` 和 `dart run`/`dart build` 构建并捆绑 Native 代码 。 + > + > 备注:可通过 `flutter config --enable-native-assets` 和 `flutter create --template=package_ffi [package name]` 启用。 + + ![](http://img.cdn.guoshuyu.cn/20231116_Dart32/image1.png) + + Demo [`native_add_library`](https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/native_add_library) 展示了相关使用,当 Flutter 项目依赖 `package:native_add_library` 时, 脚本会自动在 `build.dart` 命令上调用: + + ```dart + import 'package:native_add_library/native_add_library.dart'; + + void main() { + print('Invoking a native function to calculate 1 + 2.'); + final result = add(1, 2); + print('Invocation success: 1 + 2 = $result.'); + } + ``` + + + +# Dart 包的 DevTools 扩展 + +在 Dart 3.2 和 Flutter 3.16 中发布了一个新的[扩展框架](https://pub.dev/packages/devtools_extensions),该框架让包作者能够为它的 package 构建自定义工具,并直接在 DevTools 中显示。 + +它允许包含框架的 pub.dev 包提供特定用例的自定义工具,例如 [Serverpod](https://pub.dev/packages/serverpod) 的作者一直在努力为它的 package 构建开发人员工具,并且很高兴在即将发布的 [1.2 版本](https://github.com/orgs/serverpod/projects/4) 中提供 DevTools 扩展。 + +![](http://img.cdn.guoshuyu.cn/20231116_Dart32/image2.png) + +# Dart Web 和 Wasm 更新 + +从 Chrome 119 开始,Chrome 会默认启用 [Wasm 垃圾收集支持(称为 Wasm-GC)](https://developer.chrome.com/blog/wasmgc/) ,Wasm-GC 支持也出现在 Firefox 120(他们的下一个稳定版本)中被支持,那么 Dart、Flutter 和 Wasm-GC 的现状如何? + +Dart-to-Wasm 编译器的功能几乎已经完全实现,团队对性能和兼容性非常满意,现在的重点是边缘情况,以确保在广泛的场景中能同样完美运行。 + +对于 Flutter Web,这里类似于完成了一个全新的 “Skwasm” 渲染引擎。为了最大限度地提高性能,Skwasm 通过 wasm-to-wasm 绑定将编译后的应用代码,直接连接到自定义 [CanvasKit Wasm 模块](https://skia.org/docs/user/modules/canvaskit/) ,这也是 Flutter Web 多线程渲染支持的第一次迭代,进一步提高了帧时间。 + +在 Wasm 的 Flutter web 准备脱离当前的实验状态之前,还有一些事情要做: + +- **双编译**:生成 Wasm 和 JavaScript 输出,并在运行时启用功能检测,以支持支持和不支持 Wasm-GC 的浏览器。 +- **JavaScript interop**:一种基于[扩展类型](https://github.com/dart-lang/language/issues/2727)的新 JS 互操作机制,当针对 JavaScript 和 Wasm 时,可以在 Dart 代码、浏览器 API 和 JS 库之间进行简洁、类型安全的调用。 +- **支持 Wasm 的浏览器 API**:一个新的 `package:web`,基于现代 JS 互操作机制,取代了 dart:html (和相关库),这将提供对浏览器 API 的更轻松访问,并支持 JS 和 Wasm 目标。 + +目前已经开始将一些内部项目迁移到 `package:web` 和新的 JS 互操作机制,并期望在下一个稳定版本中有更多更新。 + +> 可以在 https://flutter.dev/wasm 了解更多。 + + + +# 最后 + +本次更新最重要有两个点,第一就是 Dart interop 越来越成熟,相信以后直接通过 flutter run 就可以完成所有 interop 的绑定和编译,第二就是 Web 路线随着 Dart Wasm 支持的进展,越来越值得期待了。 \ No newline at end of file diff --git a/FWREADME.md b/FWREADME.md index 4791865..f940cbf 100644 --- a/FWREADME.md +++ b/FWREADME.md @@ -90,6 +90,8 @@ * [Flutter 最优秀动画库「完全商业化」,Rive 2 你全面了解过吗?](Flutter-Rive.md) * [Harmony 开始支持 Flutter ,聊聊 Harmony 和 Flutter 之间的因果](Flutter-HF.md) * [Flutter 与 Dart 的市场应用](Flutter-WH.md) +* [Flutter 小技巧之不一样的思路实现炫酷 3D 翻页折叠动画](Flutter-GLSL.md) +* [Flutter 小技巧之 3.16 升级最坑 M3 默认适配技巧](Flutter-M3D.md) diff --git a/Flutter-316.md b/Flutter-316.md new file mode 100644 index 0000000..ff0779a --- /dev/null +++ b/Flutter-316.md @@ -0,0 +1,416 @@ +# Flutter 3.16 发布,快来看有什么更新吧 + +> 参考原文:https://medium.com/flutter/whats-new-in-flutter-3-16-dba6cb1015d1 + +Flutter 又又又发布新季度更新啦,同时随着而来的还有 Dart 3.2,本次 3.16 开始 Material 3 会成为新的默认主题,另外 Android 也迎来了 Impeller 的预览支持,另外还有 [Flutter Casual Games Toolkit ](https://medium.com/flutter/building-your-next-casual-game-with-flutter-716ef457e440) 的重大更新。 + +> **最重要的是,Impeller 的 Android 支持来了。** + +# Framework + +## Material default + +现在,从 3.16 开始,`MaterialApp` 里的 `useMaterial3` 默认会是 true,如果你还希望使用 M2,可以使用 `useMaterial3: false` 来使用 M2 的主题效果,**不过 Material 2 相关的东西未来会被弃用并删除**。 + +另外在 M3 上其实有的 Widget 和 M2 并不是完全兼容,所以更新到 3.16 后一些 UI 你可以需要做手动迁移适配,例如 [NavigationBar](https://api.flutter.dev/flutter/material/NavigationBar-class.html) 的 UI 效果。 + +> 更多迁移问题可见:https://github.com/flutter/flutter/issues/91605 ,另外通过 https://flutter.github.io/samples/material_3.html 你可以比较两种主题下的不同效果。 + +M3 下的主题主要由 `ThemeData.colorScheme` 和 `ThemeData.textTheme` 来决定,创建 Material 3 配色首选是使用 `ColorScheme.fromSeed()` ,另外也可以通过 `ColorScheme.fromImageProvider` 来从图像下获取配色方案支持。 + +> 木已成舟,建议大家及早适配。 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image1.gif) + +另外对于 M3 motion 的改进还包括添加 `Easing` 和 `Durations` 类,而 Material 2 的 curves 现在被重命名,会包含 “legacy” 的警告,表示它最终将被弃用和删除。( [#129942](https://github.com/flutter/flutter/pull/129942) ) + +> 简单来说就是,新增加了 motion.dart 来替代老的 [curves.dart#L26 ](https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/material/curves.dart#L26)。 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image2.png) + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image3.png) + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image4.gif) + +## 在编辑菜单中添加附加选项 + +在 iOS 上,用户现在可以选择文本并启动提供多种标准服务的共享菜单,在 3.16 的版本中本次添加了查找、搜索和共享选项。 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image5.gif) + +## 增加 TextScaler + +为了支持 Android 14 的[非线性字体缩放功能](https://blog.google/products/android/android-14/#:~:text=Also%2C you can improve readability,rate than smaller font size.)来帮助视力障碍,新 `TextScaler` 类替换了`Text.textScaleFactor ` 属性。( [#128522](https://github.com/flutter/flutter/pull/128522) ) + +## SelectionArea 更新 + +`SelectionArea` 现在支持鼠标单击、双击以及长按触摸设备相关的原生手势,默认情况下,这些新手势可通过`SelectionArea ` 和 `SelectableRegion` 来支持: + +- 单击:在单击的位置设置折叠选区 +- 双击:选择单击位置的单词 +- 双击 + 拖动:扩展单词块中的选择范围 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image6.gif) + + + +- 长按+拖动:扩展单词块中的选择范围。 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image7.gif) + +## 对焦点 Widget 进行操作的菜单项 + +3.16 开始增加清除使用菜单项时焦点更改的功能: `FocusManager` 的 `applyFocusChangesIfNeeded` 现在支持恢复菜单焦点 ,当用户单击菜单项时,焦点将返回到打开菜单之前具有焦点的项目。( [#130536](https://github.com/flutter/flutter/pull/130536) ) + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image8.gif) + +## iOS、macOS 的菜单项快捷方式自动重新排序 + +Mac 平台上的 Flutter 应用现在可以对菜单中的快捷方式修饰符进行排序,以遵循 Apple 人机界面指南。( [#129309](https://github.com/flutter/flutter/pull/129309) ) + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image9.png) + +## MatrixTransition 动画 + +新的 `MatrixTransition` 允许在创建动画过渡时进行矩阵变换,根据当前动画值,可以提供 child widget 的矩阵变换。( [#131084](https://github.com/flutter/flutter/pull/131084) ) + +```dart + +class MatrixTransitionExampleApp extends StatelessWidget { + const MatrixTransitionExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: MatrixTransitionExample(), + ); + } +} + +class MatrixTransitionExample extends StatefulWidget { + const MatrixTransitionExample({super.key}); + + @override + State createState() => + _MatrixTransitionExampleState(); +} + +class _MatrixTransitionExampleState extends State + with TickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + )..repeat(); + _animation = CurvedAnimation( + parent: _controller, + curve: Curves.linear, + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: MatrixTransition( + animation: _animation, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: FlutterLogo(size: 150.0), + ), + onTransform: (double value) { + return Matrix4.identity() + ..setEntry(3, 2, 0.004) + ..rotateY(pi * 2.0 * value); + }, + ), + ), + ); + } +} + +``` + + + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image10.gif) + + + +## PaintPattern 添加到 flutter_test + +在 `flutter_test `包中,新 `PaintPattern` 类允许开发者验证 `CustomPainter` 和 `Decoration` (在单元测试中使用)等 Widget 对画布进行的绘制调用。 + +以前需要一个文件来验证是否绘制了正确的颜色和矩形,但现在可以使用 `PaintPattern` ,例如以下示例验证了 `MyWidget `在画布上绘制了一个圆圈: + +```dart +expect( + find.byType(MyWidget), + paints + ..circle( + x: 10, + y: 10, + radius: 20, + color: const Color(0xFFF44336), + ), +); +// Multiple paint calls can even be chained together. +expect( + find.byType(MyWidget), + paints + ..circle( + x: 10, + y: 10, + radius: 20, + color: const Color(0xFFF44336), + ), + ..image( + image: MyImage, + x: 20, + y: 20, + ), +); +``` + +## 滚动更新 + +继 Flutter 3.13 中首次发布二维滚动基础之后,3.16 带来了更多功能和完善, 2D foundation 现在支持 `KeepAlive` Widget,以及默认焦点遍历和隐式滚动。 + +3.13 版本发布后不久,[two_Dimension_scrollables](https://pub.dev/packages/two_dimensional_scrollables) 包就发布了,该包由 Flutter 团队维护,包含第一个构建在该框架基础上的 2D 滚动 widget - `TableView`,目前已添加了丰富的装饰和样式支持以及其他错误修复。 + +# Engine + +## Impeller Android + +在 3.16 版本中,Android 上的 Impeller 已准备好在 stable 上提供预览,该预览版包括有关支持 Vulkan 的设备上的 Impeller 特性。 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image11.png) + +> 图表显示了过去一年中在 Impeller 的 Vulkan 后端上运行的 Flutter Gallery 转换性能基准测试改进,用户将观察到卡顿更少且稳态帧速率更高。 + +目前 Impeller 在没有 Vulkan 支持的设备上会表现不佳,单在未来几个月内还会将 Impeller 的 OpenGL 后端功能继续完善。 + +Flutter 开发者现在可以在支持 Vulkan 的 Android 设备上试用 Impeller,方法是将标志 `— enable-impeller 传递`给 `flutter run`,或者将以下设置添加到 `AndroidManiest.xml`文件中的 ``: + +```xml + +``` + +> 通常,Impeller 在运行 Android API 29 或更高版本的 64 位操作系统的设备上会使用 Vulkan 。 + +目前 Android Vulkan 预览版已知问题有: + +- platform view 尚未实现支持,包含 platform view 的框架性能会有些差。 +- 自定义着色器还未实现。 + +因为 Android 硬件生态系统更加多样化,预计 Android 的预览期将比 iOS 更长,这是无法避免的,另外,Impeller 的 Vulkan 在“调试”构建中启用了超出 Skia 使用的功能的额外调试功能,并且这些功能会产生额外的运行时开销,**所以有关 Impeller 性能的反馈来自最好来自 profile 或发布版本**。 + +## Impeller 性能, 保真度和稳定性 + +3.16 还对 Impeller 中的文本性能进行了多项改进,这对 Android 和 iOS 都是同样的。特别是改进了 Impeller 字形图集的管理,以及在引擎的 UI 和光栅线程之间划分文本工作负载的方式,所以 3.16 上用户会注意到文本繁重工作负载中的卡顿现象减少。 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image12.png) + +> 图表显示,在使用 Impeller 的 iPhone 11 上进行的一项文本密集型基准测试中平均帧光栅化时间(以毫秒为单位)有所减少。 + +本次 3.16 已经对 flutter/engine 存储库做出了 209 个 Impeller 相关承诺,解决了 217 个问题,其中包括 42 个有关保真度、稳定性或性能问题的用户报告。 + +## Engine 性能 + +为了在具有异构多处理功能的移动设备上支持更好的性能,本次 [修改了](https://github.com/flutter/engine/pull/45673) Engine 对性能敏感的线程(例如 UI 和光栅线程)与设备更强大的内核的支持。 + +本次修改在某些情况下改进非常显着,预计在 Android 上的 Skia 和 Impeller 进行本次更改后,用户将注意到卡顿现象减少,而在 iOS 设备上,这种影响不太明显,因为在 iOS 设备上,功能较强大的内核与功能较弱的内核之间的差异较小。 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image13.png) + + + +## Impeller 性能 overlay + +在之前的版本中,Flutter 的[性能 overlay ](https://docs.flutter.dev/perf/ui-performance#the-performance-overlay)功能并未与 Impeller 一起发布,本次版本修复了该问题。现在在启用 Impeller 的情况下,性能 overlay 可以正确显示。 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image14.png) + +## 现在可以正确显示 Dithering + +在 3.16 版本中, `Paint.enableDithering` 属性默认设置为 true,不再支持开发人员配置,在此之前,渐变在所有设备上都有很多色带,并且在使用某些动画时看起来也很奇怪,而解决方案是使渐变不透明,并使用 Skia 的Dithering 渐变。 + +> 而为了简化迁移过程, Impeller 永远不会支持除梯度之外的任何内容的 Dithering。 + +- 3.16 之前 + + ![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image15.png) + +- 3.16 之后 + + ![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image16.png) + +# 游戏 + +## Flutter 游戏工具包 + +在过去的几年里 Flutter 发布了数以万计的游戏,从简单但有趣的谜题到更复杂的街机游戏,其中包括: + +- Etermax 的[Trivia Crack](https://triviacrack.com/) +- Lotum 的[4 Pics 1 Word](https://flutter.dev/showcase/lotum)(猜词游戏) +- Dong Digital 的 [Brick Mania](https://play.google.com/store/apps/details?id=net.countrymania.brick&hl=en)(街机游戏) +- Onrizon 的[StopotS](https://play.google.com/store/apps/details?id=com.gartic.StopotS&hl=en)(类别游戏) +- Flutter for I/O 的 [复古弹球游戏](https://pinball.flutter.dev/) +- [PUBG](https://flutter.dev/showcase/pubg-mobile) 移动版在社交和菜单屏幕中使用 Flutter +- ···· + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image17.gif) + +为了帮助游戏开发者提高工作效率,Flutter 今天推出了休闲游戏工具包的重大更新,它是一系列新资源的集合,可帮助开发者从概念转向推出更多特定类型的游戏模板,例如纸牌游戏、无尽跑酷游戏,以及 Play 游戏服务、应用内购买、广告、成就、crashlytics 等服务集成和多人游戏支持。 + +> 要了解更多信息,可以查看 https://medium.com/flutter/building-your-next-casual-game-with-flutter-716ef457e440 + + + +# Web + +## Chrome DevTools 上的 Flutter 时间轴事件 + +Flutter 时间轴事件现在显示在 Chrome DevTools 的性能面板中。( [#130132](https://github.com/flutter/flutter/issues/130132) ) + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image18.png) + +# Android + +## 鼠标滚轮支持 + +为了适配鼠标在平板电脑或可折叠设备的效果,3.16 版本中 flutter 支持对鼠标滚动与 Android 设备上的滚动速度相匹配。( [44724](https://github.com/flutter/engine/pull/44724) ) + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image19.gif) + +## 预测性后退导航 + +Android 14 版本包含预测性后退手势功能,3.16 更新为 Flutter 带来了预测性返回手势。 + +```dart +PopScope( + canPop: _myCondition, + child: ... +), + +PopScope( + canPop: true, + onPopInvoked (bool didPop) { + _myHandleOnPopMethod(); + }, + child: ... +), + +NavigatorPopHandler( + onPop: () => _nestedNavigatorKey.currentState!.pop(), + child: Navigator( + key: _nestedNavigatorKey, + … + ), +) +··· +``` + + + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image20.gif) + +# iOS系统 + +## 应用扩展 + +Flutter 现在可以支持针对某些 [iOS 应用扩展](https://developer.apple.com/app-extensions/),这意味着可以使用 Flutter Widget 为某些类型的 iOS 应用绘制 UI,当然这并不适用于所有类型的应用扩展,因为 API(例如主屏幕空间)或内存可能存在限制。 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image21.png) + +由于应用扩展的内存限制,仅建议使用 Flutter 为内存限制大于 100MB 的扩展类型构建应用扩展 UI。 + +此外,Flutter 在调试模式下会使用额外的内存,因此当用于构建扩展 UI 时,Flutter 并不完全支持在物理设备上以调试模式运行应用扩展。 + +> 详细可见: https://docs.flutter.dev/platform-integration/ios/app-extensions + + + +# 生态 + +目前 [Flutter Favorite](https://docs.flutter.dev/packages-and-plugins/favorites) 已经重新启动,在本周期中 Flutter 生态系统委员会将 [Flame](https://pub.dev/packages/flame)、[flutter_animate](https://pub.dev/packages/flutter_animate)、[flutter_rust_bridge](https://pub.dev/packages/flutter_rust_bridge)、[Riverpod](https://pub.dev/packages/riverpod)、[video_player](https://pub.dev/packages/video_player)、[macos_ui](https://pub.dev/packages/macos_ui) 和 [fpdart](https://pub.dev/packages/fpdart) 包指定为新的 Flutter Favorite。 + +## Camera X 改进 + +在 3.10 稳定版本中, Flutter 相机插件中添加了对 Camera X 的初步支持,而 CameraX 解决了该插件的 Camera 2 实现中存在的许多问题。 + +```yaml +dependency: + camera: ^0.10.4 + camera_android_camerax: ^0.5.0 +``` + +## macOS 视频播放器 + +[video_player](https://pub.dev/packages/video_player) 中添加了 macOS 支持。 + +# 开发工具 + +## 开发工具扩展 + +新的 [DevTools 扩展框架](https://pub.dev/packages/devtools_extensions) 支持: + +- 包作者为其直接在 DevTools 中显示的包构建自定义工具。 +- 软件包作者可以编写强大的工具,利用 DevTools 中的现有框架和实用程序。 +- 使用 DevTools 调试应用以访问特定于其用例的工具的 Dart 和 Flutter 开发人员(由应用的依赖项以及哪些依赖项提供 DevTools 扩展决定)。 + +感谢 [Provider](https://pub.dev/packages/provider)、[Drift](https://pub.dev/packages/drift) 和 [Patrol](https://pub.dev/packages/patrol) 的软件包作者,这个生态系统已经建立起来,现在就可以使用这些软件包的 DevTools 扩展! + +| ![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image22.png) | ![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image23.png) | ![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image24.png) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | + +## 开发工具更新 + +此版本的 DevTools 的一些亮点包括: + +- 添加了对 DevTools 扩展的支持 +- 添加了一个新的“主”屏幕,显示连接的应用的摘要 + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image25.png) + +其他改进包括: + +- 整体表现 +- 热重启 robustness +- 文本选择和复制行为 +- viewer polish 网络分析响应 + +## VS Code UI 的可发现性 + +感谢 Flutter 社区成员 [DanTup](https://github.com/DanTup) ,Flutter VS Code 扩展现在拥有一个 Flutter 侧边栏,可让轻松访问: + +- 打开 Flutter DevTools 屏幕 +- 查看活动的调试会话 +- 查看可用设备 +- 创建新项目 +- 热重载并重启 +- 运行 Flutter Doctor -v +- ···· + +![](http://img.cdn.guoshuyu.cn/20231116_Flutter316/image26.png) + +# 最后 + +本次更新的还是属于比较“低调”的更新,最大的变化应该就是 M3 的默认主题和 Android Impeller ,其他的其实影响并不是很大,其中 M3 主题还是建议大家及早适配,因为 M2 的控件效果未来确实会慢慢剔除。 + +另外可以看到本次更新的核心还是集中在 Android 和 iOS ,PC 更新节奏看起来受到“某些影响”后慢了不少?同时关于 Jetbrains 的插件更新也没体现,核心 IDE 的资源都投入到 VSCode 了,只能说且行且珍惜。 + +好了,勇敢的少年,开始吃螃蟹了。 \ No newline at end of file diff --git a/Flutter-GLSL.md b/Flutter-GLSL.md new file mode 100644 index 0000000..75e09cb --- /dev/null +++ b/Flutter-GLSL.md @@ -0,0 +1,364 @@ +# Flutter 小技巧之不一样的思路实现炫酷 3D 翻页折叠动画 + +今天聊一个比较有意思的 Flutter 动画实现,如果需要实现一个如下图的 3D 折叠动画效果,你会选择通过什么方式? + +![](http://img.cdn.guoshuyu.cn/20231031_GLSL/image1.gif) + +相信可能很多人第一想法就是:**在 Dart 里通过矩阵变换配合 Canvas 实现**。 + +因为这个效果其实也算「常见」,在目前的小说阅读器场景里,类似的翻页效果基本都是通过这个思路完成,而这个思路以前我也「折腾」过不少,比如 [《炫酷的 3D 卡片和帅气的 360° 展示效果》](https://juejin.cn/post/7124064789763981326) 和 [用纯代码实现立体 Dash 和 3D 掘金 Logo](https://juejin.cn/post/7129239231473385503) ,就是在 Dart 里利用矩阵变换实现的视觉 3D 效果。 + +![](http://img.cdn.guoshuyu.cn/20231031_GLSL/image2.gif) + +但是今天通过一个叫 [riveo_page_curl](https://github.com/Rahiche/riveo_page_curl) 的项目,提供了不一样的实现方式,**那就是通过自定义 Fragment Shaders 实现动画** ,使用自定义 shaders 可以直接使用 GLSL 语言来进行编程,最终达到通过 GPU 渲染出更丰富图形效果。 + +![](http://img.cdn.guoshuyu.cn/20231031_GLSL/image3.gif) + +解释这个项目之前,我们先聊聊 Fragment Shader ,**Flutter 在 3.7 开始提供 Fragment Shader API** ,顾名思义,它是一个作用于片段的着色器,也就是通过 Fragment Shader API ,开发者可以直接介入到 Flutter 渲染管道的渲染流程中。 + +![](http://img.cdn.guoshuyu.cn/20231031_GLSL/image4.png) + +**那么直接使用 Fragment Shader 而不使用 Dart 矩阵变换的好处是什么**?简单来说就是可以减少 CPU 的耗时,直接通过图形语言(GLSL)直接给 GPU 发送指令,从性能上无疑可以得到提升,并且实现会更简洁。 + +> 不过加载着色器这个行为的开销可能会比较大,所以必须在运行时将它编译为适当的特定于平台的着色器。 + +当然,在 Flutter 里使用 Fragment Shader 也是有条件限制的,例如一般都需要引入 `#include ` 这个头文件,因为在编写着色器代码时,我们都需要知道当前片段的局部坐标的值,而 `flutter/runtime_effect.glsl` 里就提供了 `FlutterFragCoord().xy;` 来支持访问局部坐标,而这并不是标准 GLSL 的 API 。 + +另外, Fragment Shader 只支持 `.frag` 格式文件, 不支持顶点着色文件 `.vert` ,同时还有以下限制: + +- 不支持 UBO 和 SSBO +- sampler2D 是唯一受支持的 sampler 类型 +- texture 仅支持( sampler 和 uv)的两个参数版本 +- 不能声明额外的可变输入 +- 不支持无符号整数和布尔值 + +所以如果需要搬运一些已有的 GLSL 效果,例如 [shadertoy](https://www.shadertoy.com/) 上的代码时,那么一些必要的「代码改造」还是逃不掉的,例如下方代码是一段渐变动画的着色器: + +```glsl +void mainImage( out vec4 fragColor, in vec2 fragCoord ){ + float strength = 0.4; + float t = iTime/3.0; + + vec3 col = vec3(0); + vec2 fC = fragCoord; + + for(int i = -1; i <= 1; i++) { + for(int j = -1; j <= 1; j++) { + + fC = fragCoord+vec2(i,j)/3.0; + vec2 pos = fC/iResolution.xy; + pos.y /= iResolution.x/iResolution.y; + pos = 4.0*(vec2(0.5) - pos); + for(float k = 1.0; k < 7.0; k+=1.0){ + pos.x += strength * sin(2.0*t+k*1.5 * pos.y)+t*0.5; + pos.y += strength * cos(2.0*t+k*1.5 * pos.x); + } + col += 0.5 + 0.5*cos(iTime+pos.xyx+vec3(0,2,4)); + } + } + col /= 9.0; + col = pow(col, vec3(0.4545)); + fragColor = vec4(col,1.0); +} +``` + +![](http://img.cdn.guoshuyu.cn/20231031_GLSL/image5.gif) + +而在 Flutter 里,就需要转化为如下代码所示: + +- 首先就是必不可少的 `flutter/runtime_effect.glsl` +- 其次定义 `main() ` 函数 +- 然后我们需要将 `mainImage` 里定义的 `out vec4 fragColor;` 移到全局声明 +- 因为在 GLSL 里 iResolution 用于表示画布像素高宽,iTime 是程序运行的时间,而这里通过 `uniform` 定义 `resolution` 和 `iTime` 直接用于接受 Dart 端的输入,其余逻辑不变 +- 对应 `fragCoord` 可以在 Flutter 里通过 `FlutterFragCoord ` 获取坐标 + +```glsl +#version 460 core +#include + +out vec4 fragColor; + +uniform vec2 resolution; +uniform float iTime; + +void main(){ + float strength = 0.25; + float t = iTime/8.0; + vec3 col = vec3(0); + vec2 pos = FlutterFragCoord().xy/resolution.xy; + pos = 4.0*(vec2(0.5) - pos); + for(float k = 1.0; k < 7.0; k+=1.0){ + pos.x += strength * sin(2.0*t+k*1.5 * pos.y)+t*0.5; + pos.y += strength * cos(2.0*t+k*1.5 * pos.x); + } + col += 0.5 + 0.5*cos(iTime+pos.xyx+vec3(0,2,4)); + col = pow(col, vec3(0.4545)); + fragColor = vec4(col,1.0); +} + +``` + +> 第一行 `#version 460 core` 指定所用的 OpenGL 语言版本。 + +可以看到转换一段 GLSL 代码并不特别麻烦,主要是坐标和输入参数的变化,而通过这些已有的片段着色器,却可以给我们提供极其丰富的渲染效果,如下代码所示: + +- 在 `pubspec.yaml` 里引入上面的 shaders 代码 + +- 通过 `ShaderBuilder` 加载对应 `'shaders/warp.frag'` 文件,获得 `FragmentShader` +- 利用 `FragmentShader` 的 `setFloat` 传递数据 +- 通过 `Paint()..shader ` 添加着色器进行绘制,就可以完成渲染 + +```dart +flutter: + shaders: + - shaders/warp.frag + +············· + + late Ticker _ticker; + + Duration _elapsed = Duration.zero; + + @override + void initState() { + super.initState(); + _ticker = createTicker((elapsed) { + setState(() { + _elapsed = elapsed; + }); + }); + _ticker.start(); + } + + @override + Widget build(BuildContext context) => ShaderBuilder( + assetKey: 'shaders/warp.frag', + (BuildContext context, FragmentShader shader, _) => Scaffold( + appBar: AppBar( + title: const Text('Warp') + ), + body: CustomPaint( + size: MediaQuery.of(context).size, + painter: ShaderCustomPainter(shader, _elapsed) + ), + ), + ); + +class ShaderCustomPainter extends CustomPainter { + final FragmentShader shader; + final Duration currentTime; + + ShaderCustomPainter(this.shader, this.currentTime); + + @override + void paint(Canvas canvas, Size size) { + shader.setFloat(0, size.width); + shader.setFloat(1, size.height); + shader.setFloat(2, currentTime.inMilliseconds.toDouble() / 1000.0); + final Paint paint = Paint()..shader = shader; + canvas.drawRect(Offset.zero & size, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} +``` + +这里唯一需要解释的就是 `shader.setFloat` 流程,因为它其实是通过索引来对应到我们在 `.frag` 文件里的变量,简单来说: + +> 这里我们在 GLSL 里定义了 `uniform vec2 resolution;` 和 `uniform float iTime;` ,那么 vec2 resolution 就占据了索引 0 和 1 ,float iTime 就占据了索引 2 。 + +大概理解就是,vec2 就是两个 float 类型的值保存在了一起的意思,所以先声明的 vec2 resolution 就占据了 索引 0 和 1 ,举个例子,如下图所示,此时的 vec2 和 vec3 分了就占据了 0-4 的索引。 + +![](http://img.cdn.guoshuyu.cn/20231031_GLSL/image6.png) + +而通过 `uniform ` 在 GLSL 着色器中定义值,然后在 Dart 中就可以通过 `setFloat` 的索引来传递对应数据过去,从而形成了数据交互的完整闭环。 + +> 这里的渐变动画在 Flutter 的完整代码可以参考 Github https://github.com/tbuczkowski/flutter_shaders 里的 [warp.frag](https://github.com/tbuczkowski/flutter_shaders/blob/master/shaders/warp.frag) , + +同时针对前面整个渐变动画,作者在仓库内还提供了对应纯 Dart 代码实现一样效果的对比,通过数据可以看到,利用着色器的实现在性能上得到了巨大的提升。 + +![image-20231031175152699](http://img.cdn.guoshuyu.cn/20231031_GLSL/image7.png) + +那么回过头来, [riveo_page_curl](https://github.com/Rahiche/riveo_page_curl) 的项目里的折叠着色器如下所示,除了一堆不懂的矩阵变化,如 `scale` 缩放、`translate` 平移和 `project` 投影转换之外,就是各种看不明白的三角函数计算,简单的核心就是在矩阵变化时计算弯曲部分的弧度,以及增加阴影投影来提高视觉效果。 + +```glsl +#include + +uniform vec2 resolution; +uniform float pointer; +uniform float origin; +uniform vec4 container; +uniform float cornerRadius; +uniform sampler2D image; + +const float r = 150.0; +const float scaleFactor = 0.2; + +#define PI 3.14159265359 +#define TRANSPARENT vec4(0.0, 0.0, 0.0, 0.0) + +mat3 translate(vec2 p) { + return mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, p.x, p.y, 1.0); +} + +mat3 scale(vec2 s, vec2 p) { + return translate(p) * mat3(s.x, 0.0, 0.0, 0.0, s.y, 0.0, 0.0, 0.0, 1.0) * translate(-p); +} + +vec2 project(vec2 p, mat3 m) { + return (inverse(m) * vec3(p, 1.0)).xy; +} + +struct Paint { + vec4 color; + bool stroke; + float strokeWidth; + int blendMode; +}; + +struct Context { + vec4 color; + vec2 p; + vec2 resolution; +}; + + +bool inRect(vec2 p, vec4 rct) { + bool inRct = p.x > rct.x && p.x < rct.z && p.y > rct.y && p.y < rct.w; + if (!inRct) { + return false; + } + // Top left corner + if (p.x < rct.x + cornerRadius && p.y < rct.y + cornerRadius) { + return length(p - vec2(rct.x + cornerRadius, rct.y + cornerRadius)) < cornerRadius; + } + // Top right corner + if (p.x > rct.z - cornerRadius && p.y < rct.y + cornerRadius) { + return length(p - vec2(rct.z - cornerRadius, rct.y + cornerRadius)) < cornerRadius; + } + // Bottom left corner + if (p.x < rct.x + cornerRadius && p.y > rct.w - cornerRadius) { + return length(p - vec2(rct.x + cornerRadius, rct.w - cornerRadius)) < cornerRadius; + } + // Bottom right corner + if (p.x > rct.z - cornerRadius && p.y > rct.w - cornerRadius) { + return length(p - vec2(rct.z - cornerRadius, rct.w - cornerRadius)) < cornerRadius; + } + return true; +} + +out vec4 fragColor; + +void main() { + vec2 xy = FlutterFragCoord().xy; + vec2 center = resolution * 0.5; + float dx = origin - pointer; + float x = container.z - dx; + float d = xy.x - x; + + if (d > r) { + fragColor = TRANSPARENT; + if (inRect(xy, container)) { + fragColor.a = mix(0.5, 0.0, (d-r)/r); + } + } + + else + if (d > 0.0) { + float theta = asin(d / r); + float d1 = theta * r; + float d2 = (3.14159265 - theta) * r; + + vec2 s = vec2(1.0 + (1.0 - sin(3.14159265/2.0 + theta)) * 0.1); + mat3 transform = scale(s, center); + vec2 uv = project(xy, transform); + vec2 p1 = vec2(x + d1, uv.y); + + s = vec2(1.1 + sin(3.14159265/2.0 + theta) * 0.1); + transform = scale(s, center); + uv = project(xy, transform); + vec2 p2 = vec2(x + d2, uv.y); + + if (inRect(p2, container)) { + fragColor = texture(image, p2 / resolution); + } else if (inRect(p1, container)) { + fragColor = texture(image, p1 / resolution); + fragColor.rgb *= pow(clamp((r - d) / r, 0.0, 1.0), 0.2); + } else if (inRect(xy, container)) { + fragColor = vec4(0.0, 0.0, 0.0, 0.5); + } + } + else { + vec2 s = vec2(1.2); + mat3 transform = scale(s, center); + vec2 uv = project(xy, transform); + + vec2 p = vec2(x + abs(d) + 3.14159265 * r, uv.y); + if (inRect(p, container)) { + fragColor = texture(image, p / resolution); + } else { + fragColor = texture(image, xy / resolution); + } + } + +} + +``` + +![](http://img.cdn.guoshuyu.cn/20231031_GLSL/image8.png) + +其实我知道大家并不关心它的实现逻辑,更多是如何使用,这里有个关键信息就是 `uniform sampler2D image` ,通过引入 `sampler2D` ,我们就可以在 Dart 通过 `setImageSampler(0, image); ` 将 `ui.Image` 传递到 GLSL 里,这样就可以对 Flutter 控件实现上述的折叠动画逻辑。 + +对应在 Dart 层,就是除了 `ShaderBuilder` 之外,还可以通过 [flutter_shaders](https://pub.dev/packages/flutter_shaders) 的 `AnimatedSampler` 来实现更简洁的 `shader` 、`image` 和 `canvas` 的配合,其中 `AnimatedSampler` 的最大作用,就是将整个 child 通过 `PictureRecorder` 进行截图,转化成 `ui.Image` 传递给 GLSL,完成 UI 传递交互效果。 + +```dart + Widget _buildAnimatedCard(BuildContext context, Widget? child) { + return ShaderBuilder( + (context, shader, _) { + return AnimatedSampler( + (image, size, canvas) { + _configureShader(shader, size, image); + _drawShaderRect(shader, size, canvas); + }, + child: Padding( + padding: EdgeInsets.symmetric(vertical: cornerRadius), + child: widget.child, + ), + ); + }, + assetKey: 'shaders/page_curl.frag', + ); + + void _configureShader(FragmentShader shader, Size size, ui.Image image) { + shader + ..setFloat(0, size.width) // resolution + ..setFloat(1, size.height) // resolution + ..setFloat(2, _animationController.value) // pointer + ..setFloat(3, 0) // origin + ..setFloat(4, 0) // inner container + ..setFloat(5, 0) // inner container + ..setFloat(6, size.width) // inner container + ..setFloat(7, size.height) // inner container + ..setFloat(8, cornerRadius) // cornerRadius + ..setImageSampler(0, image); // image + } + + void _drawShaderRect(FragmentShader shader, Size size, Canvas canvas) { + canvas.drawRect( + Rect.fromCenter( + center: Offset(size.width / 2, size.height / 2), + width: size.width, + height: size.height, + ), + Paint()..shader = shader, + ); + } + +``` + +> 完整项目可见:https://github.com/Rahiche/riveo_page_curl + +所以可以看到,**相比起在 Dart 层实现这样的 3D 翻页折叠,利用 `FragmentShader` 实现的代码会更简洁,并且性能体验上会更优于纯 Dart 实现**,最重要的是,类似 [ShaderToy](https://www.shadertoy.com/) 里的一些着色器代码,通过简单的移植适配,就可以在直接被运用到 Flutter 里,这对于 Flutter 在游戏场景的实现来无疑说非常友好。 + +最后,Flutter 3.10 之后, Flutter Web 同样支持了 fragment shaders,所以着色器在 Flutter 的实现目前已经相对成熟,那么如果是之前的我通过 Flutter 实现的《[霓虹灯文本的「故障」效果的实现](https://juejin.cn/post/7214858677173289017?searchId=202310311754299E224DB054AADBBC6AE2)》的逻辑转换成 fragment shaders 来完成,是不是性能和代码简洁程度也会更高? \ No newline at end of file diff --git a/Flutter-M3D.md b/Flutter-M3D.md new file mode 100644 index 0000000..50f1eb9 --- /dev/null +++ b/Flutter-M3D.md @@ -0,0 +1,280 @@ +# Flutter 小技巧之 3.16 升级最坑 M3 默认适配技巧 + +如果要说 Flutter 3.16 升级里是最坑的是什么?那我肯定要说是 Material 3 default (M3)。 + +倒不是说 M3 bug 多,也不是 M3 在 3.16 上使用起来多麻烦,因为**虽然从 3.16 开始,`MaterialApp` 里的 `useMaterial3` 默认会是 true,但是你是可以直接 使用 `useMaterial3: false` 来关闭**。 + +那为什么还收坑?因为未来 **Material 2 相关的东西会被弃用并删除**,所以 Material 3 default(M3) 是一个警告,你可以通过 `useMaterial3: false` 来关闭无视,但是这个技术债未来会很坑。 + +> 难道你还能一直苟着不更新? + +为什么说它很坑?因为适配它纯纯是一个体力活,而且还是一个细节工作,M3 是一套配色方案,**一套和 M2 「毫不相关」的配色方案**: + +- 配色方案代表着它已经帮你默认确定了什么地方应该用什么颜色 +- M2 毫不相干,代表着你之前用这 M2 的 Widget 默认的 UI 效果,用了 M3 会完全不一样 + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image1.gif) + +如上图所示,看起来好像是就是: + +- `AppBar` 配色发生了变化 +- `FloatingActionButton` 从圆的变成方的,颜色发生变化 +- 默认 Button 按照风格发生了变卦 +- ······ + +似乎看起来也没什么,但是你知道有多少地方用了 `FloatingActionButton` ?每个地方的 `AppBar` 难道都要手动去调整?`ElevatedButton` 和 `TextButton` 有没有办法全局配置?本篇就是为了让你少走适配弯路,提供适配思路的角度。 + +> 核心还是国内的产品有谁愿意使用 Material Design ? 像这种 M2 到 M3 的变化,对于开发者来说纯粹就是负优化。 + +# 开始 + +首先,**官方 Material 3 配色首推是使用 `ColorScheme.fromSeed()` 来生成配色**,当然你也可以通过 `ColorScheme.fromImageProvider` 的图片来生成配色,不过一般人应该不会这么干,另外还有 `ColorScheme.fromSwatch` ,不过这个的灵活适配程度不如 fromSeed,所以使用 fromSeed 是比较好的选择。 + +> 因为 M3 默认从蓝色系列变成紫色系统,所以如果你用的是默认色系,那就更需要配置来恢复,本篇的目的就是,**让 App 在 M3 下恢复到 M2 的 UI 效果,因为它真的不是仅仅一个颜色变化而已。** + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image2.png) + + + +如果你以前的 `ThemeData` 是如下所示代码,那么运行之后你会看到,原本应该是 M2 效果的正常列表,现在变成了 M3 那种「无法言喻」的效果,可以看到此时 M3 下 `primarySwatch` 其实并没有起到作用。 + +```dart +ThemeData( + primarySwatch: Colors.blue, + //// +) + +``` + +| M2 | M3 | +| ------------------------------------------------------ | ------------------------------------------------------ | +| ![](http://img.cdn.guoshuyu.cn/20231123_M3/image3.png) | ![](http://img.cdn.guoshuyu.cn/20231123_M3/image4.png) | + +那么首先我们要做的就是增加 `colorScheme` ,但是你在加完会发现并没有什么变化,这是因为此时控件还是处于 M3 的色系下,所以接下来我们要首先全局恢复 `Appbar`。 + +```dart +ThemeData( + primarySwatch: Colors.blue, + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), +), +``` + +> Do it。 + +# AppBar + +如下代码所示,我们先添加 `AppBarTheme` ,可以看到 AppBar 的背景这样就变回了蓝色,但是这时候 Appbar 的文本和图标还是黑色。 + +```dart +ThemeData( + primarySwatch: Colors.blue, + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), + + appBarTheme: AppBarTheme( + backgroundColor: Colors.blue, + ), +), +``` + +| ![](http://img.cdn.guoshuyu.cn/20231123_M3/image5.png) | ![](http://img.cdn.guoshuyu.cn/20231123_M3/image6.png) | +| ------------------------------------------------------ | ------------------------------------------------------ | + +为了让图标和文本恢复到 M2 的白色,我们可以在 `AppBarTheme` 里配置 `iconTheme` 和 `titleTextStyle` ,可以看到配置后如下图所示,UI 上 `AppBar` 已经恢复到 M2 的效果,那么此时你可以会疑惑,为什么修改的配置是 `size: 24.0` 和 `Typography.dense2021.titleLarge` ? + +```dart +AppBarTheme( + iconTheme: IconThemeData( + color: Colors.white, + size: 24.0, + ), + backgroundColor: Colors.blue, + titleTextStyle: Typography.dense2014.titleLarge, +) +``` + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image7.png) + +其实这就是本篇的核心:**在 M2 控件还没被剔除的时候,通过参考源码将 M3 UI 恢复到 M2** 。 + +例如在 3.16 的源码里,`theme.useMaterial3 ?` 这样的代码目前随处可见,而此时 `AppBar` 里: + +- `_AppBarDefaultsM3` 下 icon 的颜色是通过 `onSurface` 字段,大小是 24 +- `_AppBarDefaultsM2` 下 icon 是直接使用 theme 下默认的样式,也就是 size 24, 颜色白色。 + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image8.png) + +| M2 | M3 | +| ------------------------------------------------------------ | ------------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20231123_M3/image9.png)![](http://img.cdn.guoshuyu.cn/20231123_M3/image10.png) | ![](http://img.cdn.guoshuyu.cn/20231123_M3/image11.png) | + +所以我们可以在上面的 `IconThemeData` 里可以直接配置 `color: Colors.white, size: 24.0,` 来恢复到 M2 的效果。 + +> 当然你也可以配置 `ColorScheme` 的 `onSurface` 来改变颜色,但是这个影响返回太大,还是推荐配置 `AppBarTheme` 的 `IconThemeData` 。 + +另外可以看到,此时还有一个 `Typography.dense2014.titleLarge` ,这又是哪里来的?还是回到`_AppBarDefaultsM3` 里,在 M3 下, AppBar 使用的是 `ThemeData `下的 `textTheme.titleLarge` ,而默认字体样式配置,基本来自 `Typography` 对象。 + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image12.png) + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image13.png) + +`Typography` 里默认配置了大量字体配置,例如 `Typography.dense2014` 对应就是如下所示配置,从上面代码可以看到**默认情况下 M2 用的是 `Typography.material2014 `,对应就是 `Typography.dense2014`**,也就是在 AppBar 上 `Typography.dense2014.titleLarge` 就可以让 M3 的 AppBar 文本恢复到 M2 的样式。 + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image14.png) + +看到这里你是否已经学会了大概的思路? + +**通过 `theme.useMaterial3 ` 去检索控件,然后在源码里找到 M2 的实现,然后将其修改到全局的主题设置里**,比如 AppBar 的就通过 `AppBarTheme` 配置,如果是 M2 的实现又引用了某些默认配置,就去检索这些默认配置的起源,所以说 M3 这个坑是一个体力活。 + +当然,这个思路下,有一些控件适配起来还是会有坑,因为它的变化确实有点大,例如 Card 控件。 + +# Card + +如图所示,这是 `Card` 控件在 M2 和 M3 下的变化,除了默认弧度之后,最主要就是颜色发生了改变,从默认白色变成了带着浅蓝色的效果,但是这里有个坑,就是,**此时就算你给 Card 设置 `color: Colors.white,` ,它也依旧会带着这个浅蓝色的效果**。 + +| M2 | M3 | +| ------------------------------------------------------- | ------------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20231123_M3/image15.png) | ![](http://img.cdn.guoshuyu.cn/20231123_M3/image16.png) | + +那么这个颜色如何去除?其实只要 `ColorScheme` 下设置 `surfaceTint` 为透明色就可以了,如下图所示,因为 `Card` 的效果是通过封装 `Material` 控件实现,而 `Material` 在 M3 下会通过 `elevation` 和 `surfaceTint` 去合成一个覆盖色。 + +```dart +ColorScheme.fromSeed( + seedColor: Colors.blue, + + ///影响 card 的表色,因为 M3 下是 applySurfaceTint ,在 Material 里 + surfaceTint: Colors.transparent, +), +``` + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image17.png) + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image18.png) + +所以根据判断,**将 `surfaceTint` 设置成透明就可以去除 `Card `这个覆盖色,这个逻辑在 `BottomAppBar` 里同样存在**,所以如果你需要把它们都恢复都 M2 效果,那么就只需要把 `surfaceTint` 设置成透明色即可。 + +![image-20231123172627998](http://img.cdn.guoshuyu.cn/20231123_M3/image19.png) + +所以类似的变动才是 M3 里最坑的点,如果你不了解他们的底层实现,那么在升级之后,发现明明代码给了白色,为什么它还是会有浅蓝色效果?这对于开发者来就是一个找🐛的天坑,所以在这里也用 `Card` 提供一个解决问题的典型思路。 + +另外还有一个典型的控件,那就是 `FloatingActionButton`(FAB) 。 + +# FloatingActionButton + +从 M2 到 M3, `FloatingActionButton`(FAB) 控件最大的变化就是变成了方形,其次颜色也不跟随之前和主题蓝色,我们不说 M3 这个「优化」如何,就说如何恢复到 M2 的效果。 + +| M2 | M3 | +| ------------------------------------------------------- | ------------------------------------------------------- | +| ![](http://img.cdn.guoshuyu.cn/20231123_M3/image20.png) | ![](http://img.cdn.guoshuyu.cn/20231123_M3/image21.png) | + +首先按照惯例,肯定有一个叫 `floatingActionButtonTheme` 的参数,可以用于配置 `FloatingActionButtonThemeData` ,所以这里我们首先添加上配置,然后通过 `shape` 先变回原形,并且修改 `backgroundColor` 变成蓝色。 + +```dart +floatingActionButtonTheme: FloatingActionButtonThemeData( + backgroundColor: Colors.blue, + shape: CircleBorder() +), +``` + +那么此时剩下的就是 `Icon` 的颜色,我们当然可以在用到 `Icon` 的地方手动修改为白色,但是我们希望的是全局配置默认恢复到 M2 时代,所以我们就要去找 FAB 下 `Icon` 是如何获取到颜色的。 + +> 而寻找这个颜色的实现,居然就让我开启了一段漫长的旅程····· + +首先 `Icon` 肯定是通过` IconThemeData` 去获取默认颜色,因为 FAB 的主题下没有 `iconTheme` 可以配置,那么首先就想到配置一个全局的 `iconTheme: IconThemeData` ,但是神奇的问题来了,配置之后居然无效。 + +那么就开始往上查找,然后依次返现, FAB 内部是通过 `RawMaterialButton` 实现的点击,而 `RawMaterialButton` 内部就有一个 `IconTheme.merge` 的实现,**那么 FAB 里的 `Icon` 默认应该是使用了 `effectiveTextColor` 这个颜色**。 + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image22.png) + +之后开始经历一番漫长检索关联,最终可以看到: + +- 这个 `effectiveTextColor` 来自从 FAB 传入的 TextSytle 的 color +- 而 `textSytle` 来自 `extendedTextStyle` +- 而 `extendedTextStyle` 来自 `foregroundColor` +- `foregroundColor ` 默认来自 `floatingActionButtonTheme` 的 `foregroundColor` + +![image-20231123174431747](http://img.cdn.guoshuyu.cn/20231123_M3/image23.png) + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image24.png) + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image25.png) + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image26.png) + +所以破案了,**需要全局设置 FAB 下 ` Icon` 的颜色,是要配置 `FloatingActionButtonThemeData` 的 `foregroundColor`** ,这个设定和名称正常情况下谁能想得到呢? + +而且这个传递嵌套如此“隐晦”,只能说, FAB 是 Flutter 样式跟踪里很典型的一个代表:**传递深,theme 引用复杂,类似 `merge`/`copy` 的局部实现太过隐蔽**。 + +```dart +floatingActionButtonTheme: FloatingActionButtonThemeData( + backgroundColor: Colors.blue, + foregroundColor: Colors.blue, + shape: CircleBorder()), +``` + +另外关于 **`IconThemeData` 还有一个冷知识,参数不全的情况下,也就是不满足 `isConcrete` 的情况下,其他的参数在 `of(context) `的时候是会被 `fallback` 覆盖**,这个对于 M3 - M2 的降级适配里也是一个关键信息。 + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image27.png) + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image28.png) + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image29.png) + +# primarySwatch + +最后在聊一个 `ThemeData` 的 `primarySwatch`,为什么聊它,因为如果你的代码里用了 `primaryColorDark` 和 `primaryColorLight` 作为配置,那么使用 ` ColorScheme.fromSeed` 之后,它们会发生一些「奇妙的变化」,所以为了它们可以恢复到 M2 模式,那么设置 `primarySwatch` 可以将它们恢复到原有的效果。 + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image30.png) + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image31.png) + +![](http://img.cdn.guoshuyu.cn/20231123_M3/image32.png) + + + +# 最后 + +如下所示是本次升级适配里的示例代码总和,其实 M3 模式下「降级」到 M2 UI 效果真的是一个体力活,类似上面三个典型的例子,都可以看出来跟踪默认 UI 的实现并不轻松,虽然对于 Flutter 团队来说,升级到 M3 可能是一次正向优化,但是对于不喜欢 Material Design 的国区而言,M3 只能是一个负优化,不知道大家同意不? + +```dart +return ThemeData( + ///用来适配 Theme.of(context).primaryColorLight 和 primaryColorDark 的颜色变化,不设置可能会是默认蓝色 + primarySwatch: color as MaterialColor, + + /// Card 在 M3 下,会有 apply Overlay + + colorScheme: ColorScheme.fromSeed( + seedColor: color, + primary: color, + + brightness: Brightness.light, + + ///影响 card 的表色,因为 M3 下是 applySurfaceTint ,在 Material 里 + surfaceTint: Colors.transparent, + ), + + /// 受到 iconThemeData.isConcrete 的印象,需要全参数才不会进入 fallback + iconTheme: IconThemeData( + size: 24.0, + fill: 0.0, + weight: 400.0, + grade: 0.0, + opticalSize: 48.0, + color: Colors.white, + opacity: 0.8, + ), + + ///修改 FloatingActionButton的默认主题行为 + floatingActionButtonTheme: FloatingActionButtonThemeData( + foregroundColor: Colors.white, + backgroundColor: color, + shape: CircleBorder()), + appBarTheme: AppBarTheme( + iconTheme: IconThemeData( + color: Colors.white, + size: 24.0, + ), + backgroundColor: color, + titleTextStyle: Typography.dense2014.titleLarge, + systemOverlayStyle: SystemUiOverlayStyle.light, + ), +``` \ No newline at end of file diff --git a/README.md b/README.md index da92b09..016f3e4 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ - [Flutter 3.3 正式发布,快来看看有什么新功能吧](Flutter-330.md) - [Flutter 3.7 正式发布,快来看看有什么新功能吧](Flutter-370.md) - [ Flutter 3.10 发布,快来看看有什么更新吧](Flutter-310.md) + - [Flutter 3.16 发布,快来看有什么更新吧](Flutter-316.md) - **Dart** - [Dart 2.12 发布,稳定空安全声明和FFI版本,Dart 未来的计划](Dart-212.md) - [Dart 2.14 发布,新增语言特性和共享标准 lint](Dart-214.md) @@ -103,6 +104,7 @@ - [Dart 2.18 发布,Objective-C 和 Swift interop](Dart-218.md) - [Flutter - Dart 3α 新特性 Record 和 Patterns 的提前预览讲解](Dart-300a.md) - [Dart 3 发布,快来看看有什么更新吧](Dart-300.md) + - [Dart 3.2 更新,Flutter Web 的未来越来越明朗](Dart-320.md) * [番外](FWREADME.md) @@ -194,8 +196,9 @@ * [Flutter 小技巧之 3.13 全新生命周期 AppLifecycleListener ](Flutter-N31.md) * [Flutter 最优秀动画库「完全商业化」,Rive 2 你全面了解过吗?](Flutter-Rive.md) * [Harmony 开始支持 Flutter ,聊聊 Harmony 和 Flutter 之间的因果](Flutter-HF.md) - * [Harmony 开始支持 Flutter ,聊聊 Harmony 和 Flutter 之间的因果](Flutter-HF.md) * [Flutter 与 Dart 的市场应用](Flutter-WH.md) + * [Flutter 小技巧之不一样的思路实现炫酷 3D 翻页折叠动画](Flutter-GLSL.md) + * [Flutter 小技巧之 3.16 升级最坑 M3 默认适配技巧](Flutter-M3D.md) [Flutter 工程化选择](GCH.md) diff --git a/SUMMARY.md b/SUMMARY.md index eecb28a..e904bba 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -61,6 +61,7 @@ - [Flutter 3.3 正式发布,快来看看有什么新功能吧](Flutter-330.md) - [Flutter 3.7 正式发布,快来看看有什么新功能吧](Flutter-370.md) - [ Flutter 3.10 发布,快来看看有什么更新吧](Flutter-310.md) + - [Flutter 3.16 发布,快来看有什么更新吧](Flutter-316.md) - **Dart** - [Dart 2.12 发布,稳定空安全声明和FFI版本,Dart 未来的计划](Dart-212.md) - [Dart 2.14 发布,新增语言特性和共享标准 lint](Dart-214.md) @@ -70,6 +71,7 @@ - [Dart 2.18 发布,Objective-C 和 Swift interop](Dart-218.md) - [Flutter - Dart 3α 新特性 Record 和 Patterns 的提前预览讲解](Dart-300a.md) - [Dart 3 发布,快来看看有什么更新吧](Dart-300.md) + - [Dart 3.2 更新,Flutter Web 的未来越来越明朗](Dart-320.md) * [番外](FWREADME.md) @@ -247,6 +249,10 @@ * [Flutter 与 Dart 的市场应用](Flutter-WH.md) + * [Flutter 小技巧之不一样的思路实现炫酷 3D 翻页折叠动画](Flutter-GLSL.md) + + * [Flutter 小技巧之 3.16 升级最坑 M3 默认适配技巧](Flutter-M3D.md) + * [Flutter 工程化选择](GCH.md) * [Flutter 工程化框架选择——搞定 Flutter 动画](Z1.md) * [Flutter 工程化框架选择 — 搞定 UI 生产力](Z3.md) diff --git a/UPDATE.md b/UPDATE.md index c352d5c..d7ff5ce 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -14,6 +14,7 @@ - [Flutter 3.3 正式发布,快来看看有什么新功能吧](Flutter-330.md) - [Flutter 3.7 正式发布,快来看看有什么新功能吧](Flutter-370.md) - [ Flutter 3.10 发布,快来看看有什么更新吧](Flutter-310.md) +- [Flutter 3.16 发布,快来看有什么更新吧](Flutter-316.md) @@ -29,4 +30,5 @@ - [Dart 2.18 发布,Objective-C 和 Swift interop](Dart-218.md) - [Flutter - Dart 3α 新特性 Record 和 Patterns 的提前预览讲解](Dart-300a.md) - [Dart 3 发布,快来看看有什么更新吧](Dart-300.md) +- [Dart 3.2 更新,Flutter Web 的未来越来越明朗](Dart-320.md)