交织动画
交织动画是一个简单的概念:视觉变化是随着一系列的动作发生,而不是一次性的动作。动画可能是纯粹顺序的,一个改变随着一个改变发生,动画也可能是部分或者全部重叠的。动画也可能有间隙,没有变化发生。
本指南展示如何在Flutter中构建交织动画。
以下视频演示了 basic_staggered_animation 所执行的动画:
在这个视频中,你可以看到一个独立的 widget 的以下动画,以一个带边框的略微有圆角的蓝色矩形开始,这个矩形会按照以下顺序变化:
-
淡出
-
扩大
-
向上移动同时变得更高
-
变为一个有边框的圆圈
-
颜色变为橙色
向前运行之后,动画将反向运行。
一个交织动画的基础结构
#下图展示了在 basic_staggered_animation 使用间隔的例子。你会注意到有以下特点:
-
透明度在时间轴的前 10% 发生变化。
-
透明度的变化和宽度的变化之间有一个很小的间隔。
-
在时间轴的最后 25% 没有动画。
-
增加填充使 widget 看起来向上上升。
-
将圆角半径增加到 0.5,将圆角正方形变成一个圆。
-
填充和高度的变化发生在相同的时间间隔内,但它们不必这么做。
设置这个动画:
-
创建一个
AnimationController
管理所有的Animations
。 -
为每一个有动画的属性创建一个 Tween
-
Tween 定义一个值的范围。
-
Tween 的
animate
方法需要parent
控制器。同时生成一个动画为这个属性。
-
-
指定动画的 “curve” 属性的间隔
当控制动画的值发生变化时,新动画的值也随之变化值更改,触发 UI 更新。
下面的代码为 width
属性创建了一个 tween。它创建了一个 CurvedAnimation
, 指定一个 eased curve。其他更多的预定的动画曲线请看 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
的值不一定是 doubles。
下面的代码为 borderRadius
属性创建一个 tween(控制矩形的圆角半径),使用 BorderRadius.circular()
。
borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(4),
end: BorderRadius.circular(75),
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.375,
0.500,
curve: Curves.ease,
),
),
),
完整的交织动画
#像所有可交互的 widgets 一样,完整的动画包括一对 widget:一个无状态 widget 和一个有状态的 widget。
无状态 widget 指定 Tweens,定义动画对象,提供一个 build()
方法,负责构建 widget 树的动画部分。
有状态 widget 创建控制器,播放动画,同时构建 widget 树的非动画部分。当在屏幕上检测到一个点击时,动画开始。
Full code for basic_staggered_animation's main.dart
无状态的 widget: StaggerAnimation
#在无状态 widget 中,StaggerAnimation
,the build()
函数实例化了一个
AnimatedBuilder
—一个用于构建动画的通用 widget。
AnimatedBuilder
构建一个 widget 并使用 Tweens 的当前值配置它。这个例子创建一个名为 _buildAnimation()
(实际更新 UI)的方法,并将其分配给其 builder
属性。AnimatedBuilder
监听来自动画控制器的通知,当值发生更改时,将 widget 树标记为 dirty。对于动画的每一个标记,值都会更新,导致调用 _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-05。 查看文档源码 或者 为本页面内容提出建议。