Flutter 3.29 正式发布,如果不出意外,这将是一个带着“大坑”到来的版本,因为该版本带来了很多「重大调整或者弃用」 ,所以如果你想升级到 3.29,还需要多慎重了解这次升级到底更新了什么。
之所以说带着“大坑”,主要是 3.29 更新带来了太多“意料之外”的东西,例如:
- Dart 代码会直接在 Android/iOS 的主 UI 线程上运行,而不是单独的 Dart UI 线程,此时 Dart 和平台调用直接可以同步执行
- iOS skia 正式被移除
- 没有 Vulkan 驱动的 Android 设备将回退到在 OpenGLES 上运行的 Impeller,而不是使用 Skia
- 移除了 Flutter Gradle 插件,之前没迁移的需要手动迁移适配
- Web 平台 HTML renderer 正式移除
- 全新的 DevTools
- ···
相信大家从上述描述里应该可以感受到 3.29 潜在的“威能”,那么下面就让我们看看 3.29 给我们带来了什么更新吧。
和 3.27 那会一样, Flutter iOS 的 PM 回归后, 每次更新都会包含不少 Cupertino 的身影,而本次 3.29 开始 CupertinoNavigationBar
和 CupertinoSliverNavigationBar
将支持 bottom widget 配置,一般会用于搜索字段或分段控件的场景。
例如在 CupertinoSliverNavigationBar
里现在可以使用 bottomMode 属性配置底部 Widget,从而支持自动调整大小直到隐藏,或者始终在导航栏滚动时显示:
Flutter 在 Cupertino 的高保真支持上力度上,从近来的几个版本都可以明显感受到。
其他导航栏的更新包括:
-
当部分滚动时,
CupertinoSliverNavigationBar
可以在展开和折叠状态之间对齐 -
新的
CupertinoNavigationBar.large
构造函数支持静态导航栏显示大标题 -
Cupertino popups 窗口支持更生动的背景模糊效果,从而提高了 Cupertino 风格的保真度:
-
新的
CupertinoSheetRoute
可以使用拖动手势将其移除,同时还提供了新的showCupertinoSheet
: -
在文本选择时,反转选择后,Flutter 的文本选择手柄在 iOS 上会交换它们的顺序,并且文本选择放大镜的边框颜色现在会和当前主题匹配:
不得不说最近几个版本都有关于文本选择的更新内容,也许不久之后,Flutter 文本处理能力的短板会被补齐。
谷歌对于 Material 设计规范的跟进依然还是「迷之聚焦」,这对于国内开发者而言不知是福是祸, 3.29 提供了新的 Material 3 页面过渡构建器 FadeForwardsPageTransitionsBuilder
,主要是为了匹配 Android 的最新页面过渡行为。
在页面跳转过渡期间,页面会从右向左滑动淡入,退出页面会从右向左滑动淡出,这个新过渡还解决了以前由 ZoomPageTransitionsBuilder
导致的性能问题:
此外,3.29 还更新了 CircularProgressIndicator
和 LinearProgressIndicator
,从而适配 Material 3 规范,如果要使用更新的样式,需要将 year2023
属性设置为 false
,或者将 ProgressIndicatorThemeData.year2023 设置为 false
:
year2023
属于将 Deprecated 的字段。
在 3.29 还引入了 M3 最新的 Slider
设计样式,Slider
默认为以前的 Material 3 样式,如果要启用最新设计,同样需要将 year2023
设置为 false
,或将 SliderThemeData.year2023
设置为 false
:
最后,3.29 里还包含了 Material library 的多个错误修复和功能增强,例如:
- Keyboard navigation 现在可以正确触发
DropdownMenu.onSelected
回调 - 改进了
TabBar
弹性动画 - 改进了
RangeSlider
的 thumb 对齐方式,包括 divisions 、padding 和圆角 - 增强了多个 Material 组件的自定义支持,例如
mouseCursor
属性已添加到Chip
、Tooltip
和ReorderableListView
里面,从而允许在悬停时自定义鼠标光标
近来几个版本 Framework 更新都会包含文本选择的更新,比如之前各种手势和鼠标触发的选择效果等,而 3.29 现在通过 SelectionListener
和 SelectionListenerNotifier
提供了有关 SelectionArea
或 SelectableRegion
下的文本选择信息:
SelectionDetails
(通过 SelectionListenerNotifier
获得)提供了所选内容的开始和结束偏移量(相对于 wrapped 的子树),并提示所选内容是否存在以及是否已折叠:
另外还可以通过 SelectableRegionSelectionStatusScope
获取 SelectionArea
或 SelectableRegion
状态的信息,例如通过使用 SelectableRegionSelectionStatusScope.maybeOf(context)
检查SelectableRegionSelectionStatus
状态:
class MySelectableText extends StatefulWidget {
const MySelectableText({super.key, required this.selectionNotifier, required this.onChanged});
final SelectionListenerNotifier selectionNotifier;
final ValueChanged<SelectableRegionSelectionStatus> onChanged;
@override
State<MySelectableText> createState() => _MySelectableTextState();
}
class _MySelectableTextState extends State<MySelectableText> {
ValueListenable<SelectableRegionSelectionStatus>? _selectableRegionScope;
void _handleOnSelectableRegionChanged() {
if (_selectableRegionScope == null) {
return;
}
widget.onChanged.call(_selectableRegionScope!.value);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_selectableRegionScope?.removeListener(_handleOnSelectableRegionChanged);
_selectableRegionScope = SelectableRegionSelectionStatusScope.maybeOf(context);
_selectableRegionScope?.addListener(_handleOnSelectableRegionChanged);
}
@override
void dispose() {
_selectableRegionScope?.removeListener(_handleOnSelectableRegionChanged);
_selectableRegionScope = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return SelectionListener(
selectionNotifier: widget.selectionNotifier,
child: const Text('This is some text under a SelectionArea that can be selected.'),
);
}
}
3.29 改进了多个 Material 控件里 Accessibility 的用户体验:
- 启用屏幕阅读器后,表单 Widget 仅宣布它遇到的第一个错误。
- 屏幕阅读器现在会读出下拉菜单的正确标签。
在去年发布 wasm 时,Flutter 在 Web 上的 WebAssembly (wasm) 支持要求开发者使用特殊的 HTTP 响应 headers 来托管 Flutter 应用,而现在相关要求在 3.29 开始放宽。
现在使用默认 headers 可以允许应用使用 wasm 运行,但仅限于单个线程,而更新 headers 可以让 wasm 构建的 Flutter Web 应用使用多个线程运行。
同时 3.29 修复了 WebGL 后端上图像的几个问题:
另外补充一点,关于 Flutter Web 在 WebAssembly 上的 SEO 优化支持,官方正在研究一种使用Flutter 语义树来适配的场景 :
Flutter Web 在 3.29 还允许开发者更好地控制图像在 Web 上的显示方式,过去 Image 会在发生 CORS 错误时自动使用 <img>
元素来显示来自 URL 的图像,这可能会导致出现一些不一致的行为。
而现在 webHtmlElementStrategy
标志允许开发者选择何时使用 <img>
元素,虽然默认情况下自动回退处于禁用状态,不过仍然可以标志启用回退,甚至根据应用场景确定 <img>
元素的优先级。
3.29 修复了不少 Vulkan 问题,包活:
- 修复了许多用户在支持 Vulkan 的旧版设备上出现的可重现闪烁和视觉抖动问题
- 禁用了 Android Hardware Buffer swapchains
- 由于在 MediaTek/PowerVR SoC 上使用 Vulkan 会导致的大量黑屏和崩溃报告,目前这些设备现在仅使用 Impeller OpenGLES,注意不是 skia
- Android 模拟器已更新为使用 Impeller GLES 后端,注意不是 skia
在 3.29 里,没有 Vulkan 驱动的 Android 设备将回退到在 OpenGLES 上运行的 Impeller,而不是使用 Skia,默认情况下该行为处于启用状态,无需配置。
这样 Flutter 将实现 100% 支持 Android 上的 Impeller。
和去年提到的 roadmap 一样,现在 iOS 后端已删除 Skia 支持,并且 FLTEnableImpeller
选择退出标志不再有效,随着 Flutter 开始从 iOS 版本中删除 Skia ,预计在未来版本中会进一步减小二进制文件大小。
从 3.29 开始,显示多个背景滤镜的应用现在可以使用新的 BackdropGroup
和新的 BackdropFilter.grouped
,通过这些 Widget 可以提高多个模糊的性能:
Widget build(BuildContext context) {
return BackdropGroup(
child: ListView.builder(
itemCount: 60,
itemBuilder: (BuildContext context, int index) {
return ClipRect(
child: BackdropFilter.grouped(
filter: ui.ImageFilter.blur(
sigmaX: 40,
sigmaY: 40,
),
child: Container(
color: Colors.black.withOpacity(0.2),
height: 200,
child: const Text('Blur item'),
),
),
);
}
),
);
上述代码展示了如何使用 BackdropGroup 让每个列表项都有一个有效的模糊效果,并且引擎将只执行一次背景模糊,但结果在视觉上与多次模糊相同。
在 3.29 里,如果这些 Backdrop filter 控件都共享一个共同的 BackdropKey,那么 Flutter 引擎就可以将多个背景过滤器组合到一个渲染操作中。
Backdrop Key 可以唯一标识背景过滤器的输入,当共享时表示执行一次过滤,这可以显著减少在场景中使用多个背景滤镜的开销。
对应的 Key 可以通过 backdropKey
构造函数参数手动提供,也可以通过 .grouped
构造函数从[BackdropGroup] 中查找。
新的 ImageFilter
构造函数允许将自定义着色器应用于任何子 Widget ,这和 package:flutter_shaders
中的 AnimatedSampler
功能类似,不同之处在于它还适用于 backdrop filters 。
众所周知,一直以来 Flutter 的 Dart UI Thread 和 Android/iOS 平台的 UI Thread 是不同线程,这在 《深入理解 Dart 异步实现机制》 和过去我们聊 background isolate 得时候聊过,而独立的 Dart UI 线程的主要目的之一防止阻塞平台 UI 线程。
但是由于 Flutter 是在与 native 主线程不同的 Thread(UI 线程)上执行 Dart 代码,所以会出现 Dart 和平台互相调用时需要序列化和异步消息传递,这意味着:
Flutter 和 Native 之间需要使用 platform channels 来封装对平台线程的调用,而不是能够直接从 Dart 调用 API(即通过 FFI),这让一些简单的同步调用增加了性能损耗和无意义的异步行为。
而从 3.29 开始,Android 和 iOS 上的 Flutter 将在应用的主线程上执行 Dart 代码,并且不再有单独的 Dart UI 线程。
这是改进移动平台上 Native 和 Dart 互操作系列调整中的第一部分,因为它将允许对平台进行同步调用和从平台进行同步调用,并且不会产生序列化和消息传递的开销。
而当双方处于同一个线程下时,同步响应和调用可以更好处理一些平台事件处理、文本输入、插件调用和辅助功能等。
特别是在对于
PlatformView
混合渲染等场景,如果处于同一线程之上,那么一些场景下的PlatformView
由于不同线程导致的闪烁或者同步问题或者也可以得到改善。
当然, Eric 也在 150525#issuecomment-2652547816 提到合并线程后的忧虑,比如 Dart 和 Native 平台同一线程之后,那么「滚动/动画」是否会因此出现相互影响,特别是第三方插件处理不当的时候,反而更加卡顿的情况。
当然,在整个 Flutter 团队的目标里,完全剔除 platform/message channels 是必然的方向,未来整个异步 channel 肯定会被彻底“消灭”。
默认情况下,3.29 下所有用户都启用了新的 DevTools inspector,新的 inspector 具有一个精简的 Widget Tree 和一个全新的 Widget 属性视图,以及一个自动更新以响应热重载和导航事件的选项。
通过全新的 inspector ,开发者可以更直观地调试布局问题和诊断布局问题:
如果你想关闭它,目前可以从 Inspector settings 对话框中禁用它:
从 DevTools 检查器启用 widget 选择模式后,设备上的任何选择都被视为 widget 选择。
以前在初始 Widget 选择后,开发者需要单击设备上的 Select widget 按钮,然后选择另一个小组件,而现在有一个设备上的按钮,可用于快速退出 Widget 选择模式:
DevTools 中的 Logging 工具在 3.29 更新中得到了一些改进:
- 日志包含并显示更多元的数据,例如日志严重性、类别、区域和 isolate
- 添加了对按日志严重性级别进行筛选的支持
- 性能和初始加载时间的显著改进
本次重大更改和弃用影响范围还是比较大的。
以下过去由官方提供的 package ,计划在 2025 年 4 月 30 日后停止支持,关于这些包后续可由第三方协调建立和维护分叉:
- ios_platform_images planned to be discontinued #162961
- css_colors planned to be discontinued #162962
- palette_generator planned to be discontinued #162963
- flutter_image discontinued #162964
- flutter_adaptive_scaffold planned to be discontinued #162965
- flutter_markdown planned to be discontinued #162966
3.29 移除了 Flutter Gradle 插件,这个在很久之前就提到了,该插件其实自 3.19 起已被弃用,后续将把 Flutter Gradle 插件从 Groovy 转换为 Kotlin,并将其迁移到使用 AGP 公共 API,这个改动有望降低发布新 AGP 版本时损坏的频率,并减少基于构建的回归。
不得不说,现在 Android 平台自己的 AGP 兼容问题越来越麻烦,坑越来越多。
如果是在 3.16 之前创建但尚未迁移的项目可能会受到影响,比如 Flutter 工具在构建项目时会有警告:“You are applying Flutter's main Gradle plugin imperatively
”,则基本可以确定会受到这个变动的影响,开发者需要根据 docs.flutter.dev 上提供的方式进行迁移。
更多可见:https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply
这个在之前的 《Flutter Web 正式移除 HTML renderer,只支持 CanvasKit 和 SkWasm》 已经聊到过,而从 3.29 开始, HTML renderer 就被正式移除了。
同时正如前面所说的,一些由于 WebAssembly 带来的缺失也逐步完善,例如 CORS images 和通过语义树来适配的 SEO 等场景都在补齐:
作为 Material 中正在进行的主题规范化项目的一部分,3.29 弃用 ThemeData.dialogBackgroundColor
并迁移到了 DialogThemeData.backgroundColor
,开发者可以使用 dart fix
命令迁移受影响的代码。
同样在 Material 中,ButtonStyleButton
iconAlignment
属性在添加到 ButtonStyle
和关联的 styleFrom
方法后已被弃用。
最后,不得不提提及 PC 端多窗口支持的进展,在去年的 《Flutter PC 多窗口新进展,已在 Ubuntu/Canonical 展示》 官方已经向我们展示了多窗口的可行性,而从 #142845 看目前推进的进度还可以:
而从 #30701 可以看到,不久之后对应的 PR 草稿将正式开始进行审查,所以相信今年应该可以看到期盼已久的多窗口落地:
关于 Dart 宏支持部份,正如 《Flutter 新春第一弹,Dart 宏功能推进暂停,后续专注定制数据处理支持》 所说,由于宏的性能具体目标还太遥远,Flutter 团队决定把当前的实现回归到编辑(例如静态分析和代码完成)和增量编译(热重载的第一步)上,并且具体在于重新投资Dart 中的数据支持,从聚焦于而优化数据序列化和反序列化问题。
可以看到 ,Flutter 3.29 带来了不少新功能的同时,也引入了不少大变动,所以如果你想将生产项目升级到 3.29 ,那么在「稳定」和「可控」评估上就需要更加谨慎,至少也要等到 3.29.3
再行动不迟。
那么,勇士们准备好吃螃蟹了吗?