马上就要春节了,Flutter 3.41 突然来袭,当然,作为每年的第一个版本不会有重大更新,3.41 主要涉及:

  • 继续推进 Material 和 Cupertino 库的解耦
  • 深度集成 Swift Package Manager 并默认支持 iOS 的 UIScene
  • Android 端新增对 AGP 9 和 Kotlin DSL 的支持
  • 新增按平台打包资源(减少应用体积)、同步图像解码(优化着色器性能)以及嵌入式视图自动缩放
  • 升级了 DevTools 性能,改善了 Widget Previews 实验性功能对 dart:ffi 的支持

另外,Flutter 也公布了 2026 四个大版本的发布计划:

  • Flutter 3.41 — 2 月发布,基于 1 月 6 日 分支
  • Flutter 3.44 — 5 月发布 ,基于 4 月 7 日分支
  • Flutter 3.47 — 8 月发布,基于 7 月 7 日分支
  • Flutter 3.50 — 11月发布,基于 10 月 6 日分支

有趣的是,没有看到 4.0 版本规划,官方这是爱上了 3.x ?,天知道我在写 3.41 时打错了多少次 3.14。

Material 和 Cupertino 解耦

目前官方正在持续推动 Material 和 Cupertino 解耦支持,也就是 Flutter 3.41 还没落地,但是计划中应该今年可以落地,对于这个解耦的好处,主要在于:

  • 不需要等待季度 SDK 发布才能发布设计更新
  • 可以支持较旧的 SDK 版本
  • 独立的 “Liquid Glass” 和 “Material 3 Expressive” 可选支持

更多可见之前发布过的 《Flutter UI 设计库解耦重构进度,官方解答未来如何适配》

生态更新

Swift Package Manager 和 UIScene

Flutter 3.41 版本里从 CocoaPods 向 Swift Package Manager 的过渡仍在继续,不过 Flutter 官方强烈建议插件作者采用 Swift Package Manager,因为现在它才是苹果生态系统的标准,另外为了确保与未来 iOS 版本的兼容性,Flutter 现在默认完全支持 UIScene 生命周期

关于 UIScene 支持,可见之前的 《iOS 26 开始强制 UIScene ,你的 Flutter 插件准备好迁移支持了吗?》

AGP 9 和 Kotlin DSL

AGP 9 支持还在推进中,所以,暂时不要将你的项目/插件更新到 AGP 9,因为 3.41 还不支持 AGP 9,目前 #181383 仍在持续推进,至于为什么 AGP 9 那么特别?如果你还没了解,可以看 《Android Gradle Plugin 9.0 发布,为什么这会是个史诗级大坑版本》

另外,新的插件项目现在默认使用 Kotlin DSL 来运行 Gradle,也就是 Flutter 在 Android 平台也全面转向 Kotlin DSL 。

Android 生态现在 Gradle、AGP、JDK、Andriod Studio、Kotlin ,环境和构建版本的耦合还真不少····

Platform-specific assets

这算是 Flutter 3.41 里最实用的更新,从 Flutter 3.41 开始,现在可以在 pubspec.yaml 里指定 assets 应该捆绑在哪些平台,这算是一个非常不错的优化,比如可以在移动端版本中剔除大量桌面资源:

lutter:
  assets:
    - path: assets/logo.png
    - path: assets/web_worker.js
      platforms: [web]
    - path: assets/desktop_icon.png
      platforms: [windows, linux, macos]

片段着色器的改进

从 3.41 版本开始,现在增加了同步图像解码支持,例如在以前,为 shader 创建贴图可能会带来帧延迟,但是现在新增了 decodeImageFromPixelsSync 之后,现在可以生成纹理,并在同一帧中将它们用作采样器。

final Image image = decodeImageFromPixelsSync(pixels, width, height, PixelFormat.rgba8888);

此外还增加了对高码率纹理的支持(最高可达 128 位浮点),解锁了使用高分辨率查找表(LUT)用于 GPU 加速照片滤镜和 SDF 的功能。

void attachTexture(ui.FragmentShader shader) {
  ui.PictureRecorder recorder = ui.PictureRecorder();
  Canvas canvas = Canvas(recorder);
  canvas.drawCircle(const Offset(64, 64), 64, Paint()..color = Colors.red);
  ui.Picture picture = recorder.endRecording();
  ui.Image image = picture.toImageSync(
    128,
    128,
    targetFormat: ui.TargetPixelFormat.rFloat32,
  );
  shader.setImageSampler(0, image);
}

Widget Preview

在 Flutter 3.41 开始,Flutter Inspector 在 Widget Preview 环境可以支持使用,目前是实验阶段,也就是在预览下可以方便查看预览控件的状态,暂时还需要配置额外的包目录,比如点击图标打开 Flutter Inspector 设置,添加一个指向你项目的新包目录:

另外,还有支持带有 dart:ffi 依赖的应用场景,在此之前如果包含对 dart:ffi 库具有传递依赖的 Widget 预览会出现编译错误,这是因为 dart:ffi 不支持 Web 平台,而现在 Widget Preview 现在可以处理依赖特定平台库的预览,包括 dart:ffidart:io

PS :Widget 预览器还是不支持直接调用这些库中的 API 。

Framework

iOS

从 Flutter 3.41 开始增加了新的 “bounded blur” 风格样式支持,之前使用 BackdropFilter 的半透明 Widget 可能会在边缘出现颜色溢出,而得益于 Impeller 渲染引擎的改进,现在消除了这个伪影问题。

额外提一句:Avalonia 也宣布投资 Impeller , 和 Flutter 团队合作将他们的 GPU 优先渲染器 Impeller 移植到 .NET 平台

另外,在 iOS 平台, CupertinoSheet 还支持通过 showDragHandle 添加原生样式拖拽处理的支持:

   Navigator.push<void>(
          scaffoldKey.currentContext!,
          CupertinoSheetRoute<void>(
            showDragHandle: true,
            builder: (BuildContext context) {
              return const CupertinoPageScaffold(child: Text('Page 2'));
            },
          ),
        );

Add-to-App

久违的看到了 Add-to-App 更新,从 Flutter 3.41 开始,向现有的 Android 和 iOS 应用添加 Flutter 视图变得更加简单,因为在现有原生应用中嵌入的 Flutter 视图可以根据内容自动调整大小。

在 3.41 之前,Flutter 视图需要由其原生父节点提供的固定大小,所以如果需要将 Flutter 视图潜入到原生可滚动视图,默认情况下会比较麻烦。

当然,要在 Add-to-App 支持这个能力,你的 Root 控件必须支持 unbounded constraints,也就是避免在树顶端使用需要预定义大小的 Widget(如 ListViewLayoutBuilder),因为它们会与动态大小逻辑冲突:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context)
  => MaterialApp(home: MyPage());
}

class MyPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
        body: UnconstrainedBox(
          // TODO: Edit this line to check if a widget
          // can cause problems with content-sized views.
          child: Text('This works!'),
          // child: Column(children: [Column(children: [Expanded(child: Text('This blows up!'))])]),
          // child: ListView(children: [Text('This blows up!')]),
        )
    );
  }
}

如果想开启这个能力:

  • iOS:设置 FlutterViewController.isAutoResizable 为true

  • Android: Android Manifest 中启用 content sizing,并将 FlutterView 的宽度或高度设置为 content_wrap

    <meta-data
      android:name="io.flutter.embedding.android.EnableContentSizing"
      android:value="true" />
    

导航与滚动

从 3.41 开始,Flutter 引入了 Navigator.popUntilWithResult,可以让开发者在一次调用中弹出多个屏幕并将值传回目的地路由:

Navigator.popUntilWithResult<bool>(
  context,
  (Route<dynamic> route) => route.isFirst,
  true,
);

天啊,2026 了 Flutter 终于想起来内置这个支持。

另外,在 Flutter 3.41 里,官方用从 Android 12 移植过来的基于仿真的方法重新实现了 StretchingOverScrollIndicator,现在可以确保了对高速 flings 的反应更自然:

此外还修复了 NestedScrollViewSliverMainAxisGroup 中的 pinned headers 问题,确保头部正确重叠后续的 slivers 显示。

Accessibility

在 Flutter 3.41 里 Accessibility 也做了一些调整,例如

  • CirCularProgressIndicatorLinearProgressIndicator 的原生无障碍支持
  • Flutter 支持 Web 用户的文字间距覆盖
  • 在 flutter_test 引入了新的匹配器,如 isSemanticsaccessibilityAnnouncement

Material 和 animation

Flutter 3.41 引入了新的原语和属性,从而扩展了对动画和布局的控制,例如 RepeatingAnimationBuilder 引入了一种声明式方式,可以创建连续动画,比如加载指示器、pulsing 按钮或闪烁的占位符效果:

RepeatingAnimationBuilder<Offset>(
  animatable: Tween<Offset>(
    begin: const Offset(-1.0, 0.0), 
    end: const Offset(1.0, 0.0),
  ),
  duration: const Duration(seconds: 1),
  repeatMode: RepeatMode.reverse,
  curve: Curves.easeInOut,
  builder: (BuildContext context, Offset offset, Widget child) {
    return FractionalTranslation(
      translation: offset,
      child: child,
    );
  },
  child: const ColoredBox(
    color: Colors.green,
    child: SizedBox.square(dimension: 100),
  ),
),

另外,官方还用 .builder 构建器更新了 CarouselView,从而支持创建带有动态内容的 carousels 变得更容易,其他控件调整还有:

  • DropdownMenuFormField 同样支持自定义 errorBuilder
  • RawAutoComplete 现在包含了基于可用屏幕空间智能定位的 OptionsViewOpenDirection.mostSpace 选项

PC 端和多窗口

基于 Canonical 的长期合作,现在 Flutter Desktop 的发展路线图基本由 Canonical 负责,目前复杂的桌面 UI 需求差距正在被 Canonical 缩小,同时多窗口也开始实验性可用,而 3.41 开始引入了用于创建弹出窗口和提示窗口的实验性 API,并支持 Linux、macOS 和 Windows 上的跨平台对话框窗口支持,例如:

///点击按钮创建一个“普通新窗口”
OutlinedButton(
  onPressed: () {
    final UniqueKey key = UniqueKey();
    windowManager.add(
      KeyedWindow(
        key: key,
        controller: RegularWindowController(
          delegate: CallbackRegularWindowControllerDelegate(
            onDestroyed: () => windowManager.remove(key),
          ),
          title: 'Regular',
          preferredSize: windowSettings.regularSize,
        ),
      ),
    );
  },
  child: const Text('Regular'),
)
  
///创建“模态/非模态 Dialog 窗口”,并在 Dialog 里继续挂子窗口(parent 关系) 
ElevatedButton(
  onPressed: () {
    final UniqueKey key = UniqueKey();
    windowManager.add(
      KeyedWindow(
        key: key,
        controller: DialogWindowController(
          preferredSize: windowSettings.dialogSize,
          delegate: CallbackDialogWindowControllerDelegate(
            onDestroyed: () => windowManager.remove(key),
          ),
          parent: window,
          title: 'Dialog',
        ),
      ),
    );
  },
  child: const Text('Create Modal Dialog'),
)
///Dialog 内容区域:把自己的子窗口也渲染成 ViewCollection
return ViewAnchor(
  view: ListenableBuilder(
    listenable: windowManager,
    builder: (context, _) {
      final childViews = <Widget>[];
      for (final childWindow in windowManager.getWindows(parent: window)) {
        childViews.add(
          WindowContent(
            controller: childWindow.controller,
            windowKey: childWindow.key,
            onDestroyed: () => windowManager.remove(childWindow.key),
            onError: () => windowManager.remove(childWindow.key),
          ),
        );
      }
      return ViewCollection(views: childViews);
    },
  ),
  child: child,
);


///Tooltip “跟随控件位置”的窗口(锚点矩形 + 每帧更新位置)

final tracker = _ElementPositionTracker(
  element: _tooltipButtonKey.currentContext!,
);
_ElementPositionTrackerManager.instance.add(tracker);

final UniqueKey key = UniqueKey();
final controller = TooltipWindowController(
  anchorRect: tracker.getGlobalRect()!,
  positioner: windowSettings.positioner,
  delegate: _TooltipWindowControllerDelegate(
    onDestroyed: () {
      windowManager.remove(key);
      _ElementPositionTrackerManager.instance.remove(tracker);
      setState(() {
        _tooltipController = null;
        _tooltipTracker = null;
      });
    },
  ),
  parent: widget.parentController,
);

tracker.onGlobalRectChange = (rect) {
  controller.updatePosition(anchorRect: rect);
};

windowManager.add(KeyedWindow(key: key, controller: controller));
setState(() {
  _tooltipController = controller;
  _tooltipTracker = tracker;
});


Demo 可见:https://github.com/flutter/flutter/tree/master/examples/multiple_windows,需要运行时 --enable-windowing flag

另外,Flutter Linux 现在默认支持合并线程,基本上 PC 端也完成和所有线程合并场景支持

DevTools

3.41 开始,对于 Devtools 在性能和稳定性方面也有一些改进,例如:

  • Flutter 的开发工具使用 dart2wasm 编译提升了性能
  • 与 Dart Tooling Daemon(DTD)断开连接时,机器在休眠后恢复时会自动重试

Dart 3.11

Flutter 3.41 开始包含 Dart 3.11 ,对于 Dart 3.11 主要有两个值得关心的改进:Glob support 和 Pub cache gc 。

Pub workspaces 现在支持使用 glob 模式声明包,现在可以将目录中的所有包包含在发布工作区:

# Before
name: workspace
environment:
 sdk: ^3.10.0
workspace:
 - pkg/a
 - pkg/b
 - pkg/c
# After
name: workspace
environment:
 sdk: ^3.11.0
workspace:
 - pkg/* # Adds all packages inside pkg.

此外,Pub 一直以来都将软件包存放在一个全局缓存 PUB_CACHE,确保用户不会重复下载同一个软件包,然而由于 Pub 没有追踪哪些项目使用了缓存,因此无法得知哪些软件包已过时,导致软件包版本号随着时间的推移而不断累积。

而从 Dart 3.9 开始,pub get 已将解析项目的路径存储在缓存中,所以在 Dart 3.11 中引入了一个命令,pub cache gc ,这个命令会遍历所有“活跃”项目,标记它们所依赖的所有包版本,并删除其余的包。

> dart pub cache gc
Found 3 active projects:
* /home/yourusername/projects/pub
* /home/yourusername/projects/pub-dev
* /home/yourusername/projects/pana
All other projects will need to run `dart pub get` again to work correctly.
Will recover 2 GB.
Are you sure you want to continue? (y/N)? y
Deleting unused cache entries... (4.5s)
>

天见犹怜,Dart 终于提供官方的包清理工具了。

最后

可以看到,3.41 更多是一个问题修复版本,没有什么重大更新,不过一些细节改进,例如 Platform-specific assets 和 Navigator.popUntilWithResult 等调整还是挺不错的, Add-to-App 的 UI 自缩放支持也算是难得的更新,PC 多窗口距离稳定版也不远了,那么,你准备好更新 3.41 了吗?

最后,提前祝大家,春节快乐~~~

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐