交错动画
交错动画是一个简单的概念:视觉变化发生为一系列操作,而不是一次全部发生。动画可能是纯粹的顺序,一个变化发生在另一个变化之后,或者它可能部分或完全重叠。它也可能存在间隙,其中不会发生变化。
本指南展示如何在 Flutter 中构建交错动画。
以下视频演示了 basic_staggered_animation 执行的动画
在视频中,您会看到单个小部件的以下动画,它最初是一个带边框的蓝色正方形,带有略微圆润的角。正方形按以下顺序进行变化
- 淡入
- 变宽
- 在向上移动的同时变高
- 转换为带边框的圆形
- 变为橙色
在向前运行后,动画反向运行。
交错动画的基本结构
下图显示了 basic_staggered_animation 示例中使用的 Interval
。您可能会注意到以下特征
- 不透明度在时间轴的前 10% 内发生变化。
- 不透明度变化和宽度变化之间出现了一个很小的间隙。
- 在时间轴的最后 25% 内没有任何动画。
- 增加填充会使小部件看起来向上升起。
- 将边框半径增加到 0.5,将圆角正方形转换为圆形。
- 填充和高度变化发生在完全相同的间隔内,但它们不必如此。
设置动画
- 创建一个
AnimationController
,以管理所有Animations
。 - 为每个正在进行动画的属性创建一个
Tween
。Tween
定义一个值范围。Tween
的animate
方法需要parent
控制器,并为该属性生成一个Animation
。
- 在
Animation
的curve
属性上指定间隔。
当控制动画的值发生变化时,新动画的值也会发生变化,从而触发 UI 更新。
以下代码为 width
属性创建一个补间动画。它构建一个 CurvedAnimation
,指定一个缓动曲线。有关其他可用的预定义动画曲线,请参阅 Curves
。
width = Tween<double>(
begin: 50.0,
end: 150.0,
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.125,
0.250,
curve: Curves.ease,
),
),
),
begin
和 end
值不必是双精度浮点数。以下代码使用 BorderRadius.circular()
为 borderRadius
属性(控制正方形角的圆度)构建补间动画。
borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(4),
end: BorderRadius.circular(75),
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.375,
0.500,
curve: Curves.ease,
),
),
),
完成交错动画
与所有交互式小部件一样,完整动画由一对小部件组成:无状态小部件和有状态小部件。
无状态小部件指定 Tween
,定义 Animation
对象,并提供一个 build()
函数,负责构建小部件树的动画部分。
有状态小部件创建控制器,播放动画,并构建小部件树的非动画部分。当在屏幕中的任何位置检测到点击时,动画开始。
basic_staggered_animation 的 main.dart 的完整代码
无状态小部件:StaggerAnimation
在无状态小部件 StaggerAnimation
中,build()
函数实例化一个 AnimatedBuilder
,这是一个用于构建动画的通用小部件。AnimatedBuilder
构建一个小部件,并使用 Tweens
的当前值对其进行配置。该示例创建一个名为 _buildAnimation()
的函数(执行实际的 UI 更新),并将其分配给其 builder
属性。AnimatedBuilder 侦听来自动画控制器的通知,并在值发生变化时标记小部件树为脏。对于动画的每次滴答声,值都会更新,从而调用 _buildAnimation()
。
class StaggerAnimation extends StatelessWidget { StaggerAnimation({super.key, required this.controller}) : // Each animation defined here transforms its value during the subset // of the controller's duration defined by the animation's interval. // For example the opacity animation transforms its value during // the first 10% of the controller's duration. opacity = Tween<double>( begin: 0.0, end: 1.0, ).animate( CurvedAnimation( parent: controller, curve: const Interval( 0.0, 0.100, curve: Curves.ease, ), ), ), // ... Other tween definitions ... ); final AnimationController controller; final Animation<double> opacity; final Animation<double> width; final Animation<double> height; final Animation<EdgeInsets> padding; final Animation<BorderRadius?> borderRadius; final Animation<Color?> color; // This function is called each time the controller "ticks" a new frame. // When it runs, all of the animation's values will have been // updated to reflect the controller's current value. Widget _buildAnimation(BuildContext context, Widget? child) { return Container( padding: padding.value, alignment: Alignment.bottomCenter, child: Opacity( opacity: opacity.value, child: Container( width: width.value, height: height.value, decoration: BoxDecoration( color: color.value, border: Border.all( color: Colors.indigo[300]!, width: 3, ), borderRadius: borderRadius.value, ), ), ), ); } @override Widget build(BuildContext context) { return AnimatedBuilder( builder: _buildAnimation, animation: controller, ); } }
有状态小组件:StaggerDemo
有状态小组件 StaggerDemo
创建 AnimationController
(控制所有动画),指定 2000 毫秒持续时间。它播放动画,并构建小组件树中不播放动画的部分。当在屏幕中检测到点击时,动画开始。动画向前运行,然后向后运行。
class StaggerDemo extends StatefulWidget { @override State<StaggerDemo> createState() => _StaggerDemoState(); } class _StaggerDemoState extends State<StaggerDemo> with TickerProviderStateMixin { late AnimationController_controller; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 2000), vsync: this, ); } // ...Boilerplate... Future<void> _playAnimation() async { try { await _controller.forward().orCancel; await _controller.reverse().orCancel; } on TickerCanceled { // The animation got canceled, probably because it was disposed of. } } @override Widget build(BuildContext context) { timeDilation = 10.0; // 1.0 is normal animation speed. return Scaffold( appBar: AppBar( title: const Text('Staggered Animation'), ), body: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { _playAnimation(); }, child: Center( child: Container( width: 300, height: 300, decoration: BoxDecoration( color: Colors.black.withOpacity(0.1), border: Border.all( color: Colors.black.withOpacity(0.5), ), ), child: StaggerAnimation(controller:_controller.view), ), ), ), ); } }