在 Flutter 中,动画分为两类,分别是补间动画、物理动画。
- 补间动画:补间动画是一种预先定义物体运动的起点和终点,物体的运动方式,运动时间,时间曲线,然后从起点过渡到终点的动画。
- 物理动画:基于物理的动画是一种模拟现实世界运动的动画,通过建立运动模型来实现。例如一个篮球 从高处落下,需要根据其下落高度,重力加速度,地面反弹力等影响因素来建立运动模型。
补间动画
需要注意,Flutter 中的补间动画和Android中的补间动画是不一样的。Flutter 中的补间动画会改变对象的属性,而不仅仅是视觉上UI的变化。
在 Flutter 中,补间动画分为以下几种类型:
- 隐式动画和显示动画
- Hero动画
- 交织动画
隐式动画和显示动画
在 Flutter 中,以 *Transition
命名,比如 FadeTransition 、SizeTransition 和 RotationTransition 等,需要我们自己定义和操作 AnimationController的动画叫做显示动画。
而以 Animated*
开头的 Widget,例如 AnimatedPositioned
、AnimatedContainer
、 AnimatedPadding
、AnimatedOpacity
等控件,它们最大的特点就是内部已经完全封装好逻辑,你只需要配置对应参数就可以触发动画。这种动画叫做隐式动画。
实际上,隐式动画 就是 显示动画 封装后的产物,因此隐式动画和显示动画他们的称呼不怎么重要。
- 隐式动画示例如下:
scala
class ImplicitAnimationWidget extends StatefulWidget {
const ImplicitAnimationWidget({super.key});
@override
_ImplicitAnimationWidgetState createState() => _ImplicitAnimationWidgetState();
}
class _ImplicitAnimationWidgetState extends State<ImplicitAnimationWidget> {
bool _isBig = false;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
width: _isBig ? 200 : 100,
height: _isBig ? 200 : 100,
color: _isBig ? Colors.blue : Colors.red,
duration: const Duration(seconds: 1),
curve: Curves.easeInOut,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
setState(() {
_isBig = !_isBig;
});
},
child: const Text('Toggle Size and Color'),
),
],
);
}
}
效果如下图所示:

- 显示动画示例如下:
less
class RotationAinmationPage extends StatefulWidget {
@override
_RotationAinmationPageState createState() => _RotationAinmationPageState();
}
class _RotationAinmationPageState extends State<RotationAinmationPage>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _turns;
bool _playing = false;
// 控制动画运行状态
void _toggle() {
if (_playing) {
_playing = false;
_controller.stop();
} else {
_controller.forward()..whenComplete(() => _controller.reverse());
_playing = true;
}
setState(() {});
}
@override
void initState() {
super.initState();
// 初始化动画控制器,设置动画时间
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 10),
);
// 设置动画取值范围和时间曲线
_turns = Tween(begin: 0.0, end: 3.1415926 * 2).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn),
);
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('显示动画')),
body: Center(
child: RotationTransition(
// 传入动画值
turns: _turns,
child: Container(
width: 200,
height: 200,
child: FlutterLogo(),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _toggle,
child: Icon(_playing ? Icons.pause : Icons.play_arrow),
),
);
}
}
效果如下图所示:

Hero 动画
Hero 指的是可以在路由(页面)之间"飞行"的 widget,简单来说 Hero 动画就是在路由切换时,有一个共享的widget 可以在新旧路由间切换。由于共享的 widget 在新旧路由页面上的位置、外观可能有所差异,所以在路由切换时会从旧路逐渐过渡到新路由中的指定位置,这样就会产生一个 Hero 动画。
代码示例如下:
less
class FirstPage extends StatelessWidget {
const FirstPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('First Page'),),
body: Center(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondPage(),
),
);
},
child: Hero(
tag: 'heroImage',
child: Image.asset(
'assets/images/test.png', // 请替换为你自己的图片路径
width: 200,
height: 200,
),
),
),
),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Page'),
),
body: Align(
alignment: Alignment.topCenter,
child: Hero(
tag: 'heroImage',
child: Image.asset(
'assets/images/test.png', // 请替换为你自己的图片路径
width: 400,
height: 400,
),
),
),
);
}
}
效果如下图所示:

交织动画
交织动画(Staggered Animation)是指多个动画按照一定的顺序和时间间隔依次或重叠播放。代码示例如下,代码来源9.5 交织动画 | 《Flutter实战·第二版》
less
class StaggerAnimation extends StatelessWidget {
StaggerAnimation({Key? key, required this.controller}) : super(key: key) {
//高度动画
height = Tween<double>(begin: .0, end: 300.0).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.0,
0.6, //间隔,前60%的动画时间
curve: Curves.ease,
),
),
);
color = ColorTween(begin: Colors.green, end: Colors.red).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.0,
0.6, //间隔,前60%的动画时间
curve: Curves.ease,
),
),
);
padding = Tween<EdgeInsets>(
begin: const EdgeInsets.only(left: .0),
end: const EdgeInsets.only(left: 100.0),
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.6,
1.0, //间隔,后40%的动画时间
curve: Curves.ease,
),
),
);
}
late final Animation<double> controller;
late final Animation<double> height;
late final Animation<EdgeInsets> padding;
late final Animation<Color?> color;
Widget _buildAnimation(BuildContext context, child) {
return Container(
alignment: Alignment.bottomCenter,
padding: padding.value,
child: Container(color: color.value, width: 50.0, height: height.value),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(builder: _buildAnimation, animation: controller);
}
}
class StaggerRoute extends StatefulWidget {
@override
_StaggerRouteState createState() => _StaggerRouteState();
}
class _StaggerRouteState extends State<StaggerRoute>
with TickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
);
}
_playAnimation() async {
try {
//先正向执行动画
await _controller.forward().orCancel;
//再反向执行动画
await _controller.reverse().orCancel;
} on TickerCanceled {
//捕获异常。可能发生在组件销毁时,计时器会被取消。
}
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
ElevatedButton(
onPressed: () => _playAnimation(),
child: Text("start animation"),
),
Container(
width: 300.0,
height: 300.0,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.1),
border: Border.all(color: Colors.black.withOpacity(0.5)),
),
//调用我们定义的交错动画Widget
child: StaggerAnimation(controller: _controller),
),
],
),
);
}
}
效果如下图所示:

物理动画
物理动画的实现可以看 Widget 的物理模拟动画效果 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter