交错动画
交错动画是一个简单的概念:视觉变化作为一系列操作发生,而不是一次性全部发生。动画可能是纯粹的顺序,一个变化发生在另一个变化之后,也可能是部分或完全重叠。它也可能存在间隙,在这些间隙中没有发生任何变化。
本指南介绍如何在 Flutter 中构建交错动画。
以下视频演示了basic_staggered_animation执行的动画
在视频中,您会看到单个 Widget 的以下动画,该动画最初是一个带圆角的蓝色边框正方形。正方形按以下顺序进行更改
- 淡入
- 变宽
- 向上移动的同时变高
- 转换为带边框的圆形
- 颜色更改为橙色
向前运行后,动画反向运行。
交错动画的基本结构
#下图显示了basic_staggered_animation示例中使用的Interval
。您可能会注意到以下特征
- 不透明度在时间轴的前 10% 期间发生变化。
- 在不透明度变化和宽度变化之间存在一个很小的间隙。
- 在时间轴的最后 25% 期间没有任何动画。
- 增加填充使 Widget 似乎向上移动。
- 将边框半径增加到 0.5,将带圆角的正方形转换为圆形。
- 填充和高度更改在完全相同的区间内发生,但它们不必如此。
设置动画
- 创建一个
AnimationController
来管理所有Animation
。 - 为每个正在动画化的属性创建一个
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,
),
),
),
完整的交错动画
#与所有交互式 Widget 一样,完整的动画由一对 Widget 组成:一个无状态 Widget 和一个有状态 Widget。
无状态 Widget 指定Tween
,定义Animation
对象,并提供一个build()
函数,该函数负责构建 Widget 树的动画部分。
有状态 Widget 创建控制器,播放动画,并构建 Widget 树的非动画部分。当在屏幕上的任何位置检测到点击时,动画开始。
basic_staggered_animation 的 main.dart 的完整代码
无状态 Widget:StaggerAnimation
#在无状态 Widget StaggerAnimation
中,build()
函数实例化了一个AnimatedBuilder
——一个用于构建动画的通用 Widget。AnimatedBuilder
构建一个 Widget 并使用Tween
的当前值对其进行配置。该示例创建了一个名为_buildAnimation()
(执行实际 UI 更新)的函数,并将其分配给它的builder
属性。AnimatedBuilder 侦听来自动画控制器的通知,在值更改时将 Widget 树标记为脏。对于动画的每次刻度,值都会更新,从而导致调用_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,
);
}
}
有状态 Widget:StaggerDemo
#有状态 Widget StaggerDemo
创建AnimationController
(统治它们的那个),指定 2000 毫秒的持续时间。它播放动画,并构建 Widget 树的非动画部分。当在屏幕上检测到点击时,动画开始。动画向前运行,然后向后运行。
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),
),
),
),
);
}
}
除非另有说明,否则本网站上的文档反映了 Flutter 的最新稳定版本。页面上次更新于 2024-08-01。 查看源代码 或 报告问题。