Flutter Material Motion 预置动画指南:animations 包四大过渡模式与实战集成
【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages
导读
本文围绕 Flutter 官方维护的animations包展开,系统讲解 Material motion 体系中的四大过渡模式:Container transform(容器变换)、Shared axis(共享轴)、Fade through(淡入淡出切换)与 Fade(淡入)。你将学会通过OpenContainer、SharedAxisTransition、FadeThroughTransition、FadeScaleTransition等开箱即用的组件,把经过设计验证的高质量转场动画直接嵌入自己的 Flutter 应用,并了解PageTransitionSwitcher与showModal等底层基础设施的用法与实现原理。
animations是 Flutter 团队维护的开源包(当前仓库中版本为 3.0.0,SDK 约束^3.12.0、Flutter>=3.44.0,依赖material_ui),它提供了一系列"开箱即用"(pre-canned)的高质量动画,允许你用自定义内容替换动画载体后直接落入应用,以取悦用户并提升交互质感。
一、Material motion:为何需要统一的转场模式
Material motion 是一组帮助用户理解和导航应用的过渡模式。其设计哲学是:转场动画不是装饰,而是传达"元素之间如何关联"的信息。animations包把 Material motion 规范的四个核心模式固化为可直接复用的 Flutter 组件:
- Container transform—— 用于包含容器的 UI 元素之间的转场,在两个元素之间建立可见的联系;
- Shared axis—— 用于具有空间或导航关系的 UI 元素之间的转场,通过在 x、y 或 z 轴上施加共享变换强化元素间的关系;
- Fade through—— 用于彼此没有强关联的 UI 元素之间的转场;
- Fade—— 用于在屏幕边界内进入或退出的 UI 元素(如对话框、菜单、Snackbar、FAB)。
选择哪种模式的核心依据是元素之间的语义关系强度:从强关联(容器变换)到弱关联(fade through)再到无关联(fade),这构成了 Material motion 的完整过渡光谱。
二、快速体验:运行官方演示应用
在真机或模拟器上查看上述动画的实际效果,可进入包自带的示例工程:
cd packages/animations/example/ flutter run --release示例应用入口在 packages/animations/example/lib/main.dart:主页以列表形式列出了四种过渡模式(Container transform / Shared axis / Fade through / Fade),并提供一个Slow animations开关,通过timeDilation = 20.0把动画放慢 20 倍,方便逐帧观察动画曲线与元素运动细节。
三、Container transform:OpenContainer
Container transform模式专为"包含容器"的 UI 元素之间的转场设计,例如:
- 卡片展开为详情页;
- 列表项展开为详情页;
- FAB 展开为详情页;
- 搜索栏展开为全屏搜索。
其核心组件是 OpenContainer,一个"点击后放大填满屏幕以揭示新内容"的容器:关闭状态下显示closedBuilder构建的 Widget;点击后容器平滑放大至整个Navigator尺寸,同时关闭态内容淡出、打开态内容淡入;通过openBuilder提供的回调或 Android 返回键关闭时动画反向播放。
3.1 最小示例
OpenContainer( transitionDuration: const Duration(milliseconds: 500), transitionType: ContainerTransitionType.fadeThrough, openBuilder: (context, action) { return Scaffold( appBar: AppBar(title: const Text('Details Page')), body: const Center( child: Text( 'This page opened with Container Transform animation', style: TextStyle(fontSize: 18), textAlign: TextAlign.center, ), ), ); }, closedBuilder: (context, action) { return Container( width: 200, height: 120, alignment: Alignment.center, decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(16), ), child: const Text( 'Open Details', style: TextStyle(color: Colors.white, fontSize: 18), ), ); }, ),以上示例代码直接取自 open_container.dart 的文档注释,展示了"蓝色圆角卡片 → 全屏详情页"的经典用法。
3.2 完整参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
closedColor | Color | Colors.white | 容器关闭时的背景色 |
openColor | Color | Colors.white | 容器打开时的背景色 |
middleColor | Color? | 主题canvasColor | 仅fadeThrough转场类型使用的中间过渡色 |
closedElevation | double | 1.0 | 关闭时容器的海拔高度 |
openElevation | double | 4.0 | 打开时容器的海拔高度 |
closedShape | ShapeBorder | 圆角 4.0 的RoundedRectangleBorder | 关闭时容器形状 |
openShape | ShapeBorder | 矩形RoundedRectangleBorder() | 打开时容器形状 |
onClosed | ClosedCallback<T?> | null | 容器关闭(pop)时回调,接收 pop 返回的数据;未返回值时为null |
closedBuilder | CloseContainerBuilder | 必填 | 构建关闭状态内容;可通过action回调打开容器 |
openBuilder | OpenContainerBuilder<T> | 必填 | 构建打开状态内容;可通过action回调关闭容器 |
tappable | bool | true | 是否允许点击整个关闭态容器打开;设为false时只能通过closedBuilder的action回调打开 |
transitionDuration | Duration | 300ms | 开/关转换动画时长 |
transitionType | ContainerTransitionType | fade | 新旧内容的淡入淡出方式 |
useRootNavigator | bool | false | 路由推入最近的还是最远的Navigator |
routeSettings | RouteSettings? | null | 提供给新路由的附加数据 |
clipBehavior | Clip | Clip.antiAlias | 关闭态内容的裁剪方式 |
closedShadows/openShadows | List<BoxShadow>? | null | 自定义阴影;提供后对应的closedElevation/openElevation将被忽略 |
3.3 实现原理
从源码看,OpenContainerState.openContainer()会构造一个私有的_OpenContainerRoute推入Navigator,动画期间新旧内容同时存在于 Widget 树中,因此两个 builder 返回的 Widget 不能使用相同的 GlobalKey(open_container.dart)。
转场的核心是一组 Tween:
RectTween驱动容器从关闭位置/尺寸(_rectTween.begin)平滑放大到整个 Navigator 尺寸(_rectTween.end);ShapeBorderTween驱动closedShape→openShape的形状变换;ColorTween(_getColorTween)与透明度 Tween(_getClosedOpacityTween/_getOpenOpacityTween)根据ContainerTransitionType生成不同权重的TweenSequence:fade模式:关闭态内容保持不透明,打开态内容在动画后 1/5 时间段内淡入,同时背景色从closedColor渐变到openColor;fadeThrough模式:关闭态内容先淡出(前 1/5 时间段),背景色经由middleColor过渡,打开态内容随后淡入。
所有动画统一施加Curves.fastOutSlowIn曲线,并通过_FlippableTweenSequence实现打开/关闭动画的精确反向回放;中断转场(如动画进行中再次点击)由_transitionWasInterrupted检测并处理。
四、Shared axis:共享轴过渡
Shared axis模式用于具有空间或导航关系的元素,通过在 x、y、z 轴上共享同一变换方向,强化"上一屏与下一屏位于同一空间"的感知:
- 引导页流程沿 x 轴过渡;
- 分步器(stepper)沿 y 轴过渡;
- 父子级导航沿 z 轴过渡。
4.1 两种接入方式
方式一:作为全局页面转场。通过SharedAxisPageTransitionsBuilder挂到PageTransitionsTheme中,替换所有MaterialPageRoute的默认转场(示例取自 shared_axis_transition.dart):
MaterialApp( theme: ThemeData( pageTransitionsTheme: PageTransitionsTheme( builders: { TargetPlatform.android: SharedAxisPageTransitionsBuilder( transitionType: SharedAxisTransitionType.horizontal, ), TargetPlatform.iOS: SharedAxisPageTransitionsBuilder( transitionType: SharedAxisTransitionType.horizontal, ), }, ), ), // routes: ... );方式二:作为局部内容切换。在PageTransitionSwitcher的transitionBuilder中直接使用SharedAxisTransition(示例取自 shared_axis_transition.dart):
PageTransitionSwitcher( transitionBuilder: ( Widget child, Animation<double> primaryAnimation, Animation<double> secondaryAnimation, ) { return SharedAxisTransition( animation: primaryAnimation, secondaryAnimation: secondaryAnimation, transitionType: SharedAxisTransitionType.horizontal, child: child, ); }, child: Container( key: ValueKey<int>(_selectedIndex), // 切换 key 才能触发过渡 color: _colors[_selectedIndex], child: const Center(child: FlutterLogo(size: 300)), ), ),4.2 轴类型与实现细节
SharedAxisTransitionType枚举定义三种轴:
vertical—— 沿 y 轴垂直过渡,适合注册/登录分步流程;horizontal—— 沿 x 轴水平过渡,适合向导、轮播类页面;scaled—— 沿 z 轴缩放过渡,适合父子级导航(如点击进入详情)。
从 shared_axis_transition.dart 的实现可以看到其动画编排:进入元素使用淡入(_fadeInTransition,在 0.3~1.0 区间内完成)配合 30 像素位移(x/y 轴)或从 0.80 缩放到 1.00(z 轴);退出元素使用淡出(_fadeOutTransition,在 0.0~0.3 区间内完成)配合反向位移或缩放到 1.10,并以fillColor(默认主题canvasColor)作为过渡期间的背景色,保证位移露出的区域颜色一致。通过DualTransitionBuilder分别驱动前进(forwardBuilder)与后退(reverseBuilder)方向的动画。
五、Fade through:淡入淡出切换
Fade through模式用于彼此没有强关联的 UI 元素之间的切换,典型场景:
- 点击底部导航栏的不同目的地;
- 点击刷新图标;
- 切换账号。
它的特征与 Shared axis 一样是"先出后进":旧元素淡出,然后新元素淡入并从 0.92 放大到 1.0。缩放仅施加在进入的元素上,以强调新内容优先于旧内容。
5.1 作为页面转场
FadeThroughPageTransitionsBuilder同样挂载到PageTransitionsTheme(示例取自 fade_through_transition.dart):
MaterialApp( theme: ThemeData( pageTransitionsTheme: PageTransitionsTheme( builders: { TargetPlatform.android: FadeThroughPageTransitionsBuilder(), TargetPlatform.iOS: FadeThroughPageTransitionsBuilder(), }, ), ), // routes: ... );5.2 与 PageTransitionSwitcher 组合
在PageTransitionSwitcher.transitionBuilder中使用FadeThroughTransition,即可为底部导航等场景的视图切换注入 fade through 动画(示例取自 fade_through_transition.dart):
PageTransitionSwitcher( transitionBuilder: ( Widget child, Animation<double> primaryAnimation, Animation<double> secondaryAnimation, ) { return FadeThroughTransition( child: child, animation: primaryAnimation, secondaryAnimation: secondaryAnimation, ); }, child: Container( key: ValueKey<int>(_selectedIndex), // 每次切换必须更换 key color: _colors[_selectedIndex], ), bottomNavigationBar: BottomNavigationBar( // items、currentIndex、onTap ... ), ),5.3 实现细节
源码_ZoomedFadeIn/_FadeOut(fade_through_transition.dart)用两个TweenSequence精确控制时序:淡入阶段在前 6/20 时间段内完全不透明(透明度恒为 0、缩放恒为 0.92),后 14/20 时间段内按Cubic(0.0, 0.0, 0.2, 1.0)曲线完成淡入与放大;淡出阶段在前 6/20 时间段内按Cubic(0.4, 0.0, 1.0, 1.0)曲线淡出,其余时间保持完全透明。两段动画"错峰"进行,从而形成 fadethrough的视觉节奏。
六、Fade:淡入过渡(FadeScaleTransition 与 showModal)
Fade模式用于在屏幕边界内进入或退出的 UI 元素,例如:
- 对话框;
- 菜单;
- Snackbar;
- FAB。
实现上,进入的元素快速淡入并从 80% 缩放到 100%,退出的元素仅淡出——缩放同样只作用于进入元素。注意FadeScaleTransition不同于 Flutter 自带的FadeTransition(后者只动画化子 Widget 的透明度)。
6.1 在普通场景使用
FadeScaleTransition( animation: animation, // 通常来自 AnimationController child: child, )FadeScaleTransition通过DualTransitionBuilder组合两段动画(fade_scale_transition.dart):前进方向使用Interval(0.0, 0.3)淡入 + 0.80→1.00 缩放(Easing.legacyDecelerate曲线),后退方向使用 1.0→0.0 的透明度渐变。
6.2 与 showModal 组合:弹窗/菜单
animations包还提供showModalAPI 与FadeScaleTransitionConfiguration,将 fade 模式应用于模态弹窗(示例取自 fade_scale_transition.dart):
showModal( context: context, configuration: FadeScaleTransitionConfiguration(), builder: (BuildContext context) { return Center( child: SizedBox( width: 250, height: 250, child: const Material( child: Center(child: FlutterLogo(size: 250)), ), ), ); }, );FadeScaleTransitionConfiguration继承自ModalConfiguration,可配置参数(见 fade_scale_transition.dart):
| 参数 | 默认值 | 说明 |
|---|---|---|
barrierColor | Colors.black54 | 遮罩(scrim)颜色 |
barrierDismissible | true | 点击遮罩是否关闭弹窗 |
transitionDuration | 150ms | 进入动画时长 |
reverseTransitionDuration | 75ms | 退出动画时长 |
barrierLabel | 'Dismiss' | 遮罩的无障碍语义标签 |
从 modal.dart 的实现可见,showModal内部通过Navigator.push推入一个PopupRoute(_ModalRoute),其useRootNavigator默认为true——即模态默认推入根 Navigator。因此,当应用存在多层Navigator时,关闭弹窗应使用Navigator.of(context, rootNavigator: true).pop(result)而非普通的Navigator.pop。showModal返回Future<T?>,关闭时传入Navigator.pop的值即为该 Future 的解析结果。
七、PageTransitionSwitcher:模式化的 AnimatedSwitcher
PageTransitionSwitcher是上述各过渡模式共享的基础设施(page_transition_switcher.dart)。它是AnimatedSwitcher的变体,区别在于:AnimatedSwitcher对进入/退出使用同一套动画,而PageTransitionSwitcher允许像PageRoute一样分别指定进入转场(primaryAnimation)与退出转场(secondaryAnimation)。
关键行为:
- 当
child更换时,transitionBuilder会被应用到新旧两个 child 上:新 child 的primaryAnimation向前播放(定义其"出现"),旧 child 的secondaryAnimation向前播放(定义其"消失")——相当于 push 一个新PageRoute; reverse: true时行为反转,相当于 pop 一个PageRoute;- 切换必须更换 child 的
Key:若新旧 child 类型与 key 相同,框架认为它们是同一个 Widget 而直接更新参数,不会触发过渡。通常使用ValueKey区分(如ValueKey<int>(_selectedIndex)); - 快速连续切换时,多个旧 child 可以同时在树上淡出,新 child 淡入;
layoutBuilder控制新旧 child 的布局,默认defaultLayoutBuilder将所有条目放入居中对齐的Stack,也可自定义(如左上角对齐)。
八、库结构速览
animations包的全部导出集中在 lib/animations.dart,六个实现文件一一对应四种模式及两个基础设施:
| 文件 | 对应模式 / 组件 |
|---|---|
| lib/src/open_container.dart | Container transform:OpenContainer、ContainerTransitionType |
| lib/src/shared_axis_transition.dart | Shared axis:SharedAxisTransition、SharedAxisPageTransitionsBuilder、SharedAxisTransitionType |
| lib/src/fade_through_transition.dart | Fade through:FadeThroughTransition、FadeThroughPageTransitionsBuilder |
| lib/src/fade_scale_transition.dart | Fade:FadeScaleTransition、FadeScaleTransitionConfiguration |
| lib/src/modal.dart | 模态弹窗基础设施:showModal、ModalConfiguration |
| lib/src/page_transition_switcher.dart | PageTransitionSwitcher(通用内容切换器) |
对应的单元测试覆盖了各模式的核心行为(test 目录):fade_scale_transition_test.dart、fade_through_transition_test.dart、shared_axis_transition_test.dart、open_container_test.dart、modal_test.dart、page_transition_switcher_test.dart以及dual_transition_builder_test.dart,可作为理解动画时序与中断处理行为的补充参考。
九、集成到自己的应用
在pubspec.yaml中声明依赖后即可使用:
dependencies: animations: ^3.0.0接入路径归纳如下:
- 页面级转场:在
ThemeData.pageTransitionsTheme中按平台配置SharedAxisPageTransitionsBuilder或FadeThroughPageTransitionsBuilder,一键替换所有MaterialPageRoute的转场; - 局部内容切换:用
PageTransitionSwitcher+SharedAxisTransition/FadeThroughTransition,为底部导航、Tab 内容区等注入专业转场; - 容器展开:用
OpenContainer实现卡片/列表项/FAB/搜索栏到详情页的放大过渡; - 弹窗与浮层:用
showModal+FadeScaleTransitionConfiguration获得符合 Material 规范的淡入弹窗。
结语
animations包把 Material motion 规范中"如何选择过渡模式"的语义判断,物化为四个可直接使用的组件和一套统一的切换基础设施。使用它时,核心决策在于根据元素间的关系强度选择模式:强关联用 Container transform,空间/导航关联用 Shared axis,弱关联用 Fade through,屏幕内进出用 Fade。在此基础上,通过各组件暴露的颜色、形状、海拔、时长与曲线参数,即可将默认动画调整为符合应用视觉语言的自定义转场。
【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考