前言
学习参考:老孟 flutter动画
基本上开发时使用的组件都有其动画,关于动画方面的知识,一般情况很少会用到。因此这里只学习关于动画的基本知识。
AnimationController
Flutter中的AnimationController
是一个用于控制动画的类。它可以控制动画的开始、停止、反转、重置等操作,并且可以设置动画的持续时间、曲线等属性。
AnimationController
通常在 initState
方法中初始化,在dispose
中释放动画
使用AnimationController
需要先创建一个实例,然后设置动画的持续时间、曲线等属性,最后通过调用forward()
方法来启动动画。在动画运行过程中,可以通过调用reverse()
方法来反转动画,通过调用stop()
方法来停止动画,通过调用reset()
方法来重置动画。
AnimationController
还可以添加监听器,用于监听动画的状态变化。例如,可以通过添加addListener()
方法来监听动画的值变化,从而更新UI界面。通过添加addStatusListener
来监听动画的状态
// 单个 AnimationController 的时候使用 SingleTickerProviderStateMixin,多个 AnimationController 使用 TickerProviderStateMixin。
class _YcHomeBodyState extends State<YcHomeBody>
with SingleTickerProviderStateMixin {
double size = 100;
// 定义动画控制器对象
late AnimationController _controller;
// AnimationController 通常在 initState 方法中初始化
void initState() {
// TODO: implement initState
super.initState();
// vsync 用于防止屏幕外动画消耗不必要的资源
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
// 监听动画帧的变化,在每一帧中调用setState来更新UI,AnimationController 的值默认是 0 到 1
_controller.addListener(() {
setState(() {
// 使size从100到200
size = 100 + 100 * _controller.value;
});
});
// 监听动画的状态,当动画正序完成后反向执行动画
_controller.addStatusListener((status) {
// 动画状态status的值有:dismissed(动画停止在开始处)、forward(正向运行)、reverse(反向运行)、completed(动画停止在结束处)
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
}
void dispose() {
super.dispose();
//释放动画
_controller.dispose();
}
Widget build(BuildContext context) {
return Center(
//创建一个手势识别器
child: GestureDetector(
onTap: () {
// 启动动画
_controller.forward();
},
child: Container(
width: size,
height: size,
color: Colors.blue,
alignment: Alignment.center, // 设置文字居中
child: const Text("点击变大"),
),
));
}
}
Tween
Flutter中的Tween
是用于在动画中定义起始值和结束值之间的插值计算的类。它可以将一个范围内的值映射到另一个范围内的值,从而实现动画效果
上面的案例可以修改为
class _YcHomeBodyState extends State<YcHomeBody>
with SingleTickerProviderStateMixin {
double size = 100;
// 定义动画控制器对象
late AnimationController _controller;
// 定义一个动画对象
late Animation _animation;
// AnimationController 通常在 initState 方法中初始化
void initState() {
// TODO: implement initState
super.initState();
// vsync 用于防止屏幕外动画消耗不必要的资源
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
final Tween tween = Tween(begin: 100.0, end: 200.0);
_animation = tween.animate(_controller);
_animation.addListener(() {
setState(() {
size = _animation.value;
});
});
}
void dispose() {
super.dispose();
//释放动画
_controller.dispose();
}
Widget build(BuildContext context) {
return Center(
//创建一个手势识别器
child: GestureDetector(
onTap: () {
// 启动动画
_controller.forward();
},
child: Container(
width: size,
height: size,
color: Colors.blue,
alignment: Alignment.center, // 设置文字居中
child: const Text("点击变大"),
),
));
}
}
同理也可以改变颜色
class _YcHomeBodyState extends State<YcHomeBody>
with SingleTickerProviderStateMixin {
Color _color = Colors.blue;
// 定义动画控制器对象
late AnimationController _controller;
// 定义一个动画对象
late Animation _animation;
// AnimationController 通常在 initState 方法中初始化
void initState() {
// TODO: implement initState
super.initState();
// vsync 用于防止屏幕外动画消耗不必要的资源
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
final Tween tween = ColorTween(begin: Colors.blue, end: Colors.red);
_animation = tween.animate(_controller);
_animation.addListener(() {
setState(() {
_color = _animation.value;
});
});
}
void dispose() {
super.dispose();
//释放动画
_controller.dispose();
}
Widget build(BuildContext context) {
return Center(
//创建一个手势识别器
child: GestureDetector(
onTap: () {
// 启动动画
_controller.forward();
},
child: Container(
width: 100,
height: 100,
color: _color,
alignment: Alignment.center, // 设置文字居中
child: const Text("点击改变颜色"),
),
));
}
}
Curve
动画中还有一个重要的概念就是 Curve,即动画执行曲线。使动画的效果能够以匀速、加速、减速、抛物线等各种速率变化。
// 使用了 chain 方法将 ColorTween 和 CurveTween 组合起来
final Animatable<Color?> tween =
ColorTween(begin: Colors.blue, end: Colors.red)
.chain(CurveTween(curve: Curves.easeInOut));
_animation = tween.animate(_controller);
_animation.addListener(() {
setState(() {
_color = _animation.value;
});
});
动画组件
Flutter 系统提供了20多个动画组件,这些组件都是基于动画的核心知识实现的。动画组件分为两大类:
- 隐式动画组件:只需提供给组件动画开始、结束值,组件创建 AnimationController、Curve、Tween,执行动画,释放AnimationController
- 显式动画组件:需要设置 AnimationController,控制动画的执行,使用显式动画可以完成任何隐式动画的效果,甚至功能更丰富一些,不过你需要管理该动画的 AnimationController 生命周期
- 显示动画组件和隐式动画组件中各有一个万能的组件,它们是 AnimatedBuilder 和 TweenAnimationBuilder,当系统中不存在我们想要的动画组件时,可以使用这两个组件
隐式动画组件的使用
class _YcHomeBodyState extends State<YcHomeBody>
with SingleTickerProviderStateMixin {
double _opacity = 1.0;
Widget build(BuildContext context) {
return Center(
//创建一个手势识别器
child: GestureDetector(
onTap: () {
setState(() {
_opacity = 0;
});
},
child: AnimatedOpacity(
opacity: _opacity,
duration: const Duration(seconds: 1),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
alignment: Alignment.center, // 设置文字居中
child: const Text("点击改变颜色"),
),
)));
}
}
**TweenAnimationBuilder **
TweenAnimationBuilder
是 Flutter 中的一个动画构建器,可以用于创建一个在两个值之间进行动画的动画组件。使用 TweenAnimationBuilder
需要指定两个值之间的插值器(Tween
),以及动画的持续时间和动画结束后的回调函数。
class _YcHomeBodyState extends State<YcHomeBody>
with SingleTickerProviderStateMixin {
// 一开始不能定义null,否则运行会报错
late ColorTween _tween = ColorTween(begin: Colors.blue, end: Colors.blue);
Widget build(BuildContext context) {
return Center(
//创建一个手势识别器
child: GestureDetector(
onTap: () {
setState(() {
_tween = ColorTween(begin: Colors.blue, end: Colors.red);
});
},
child: TweenAnimationBuilder(
duration: const Duration(seconds: 1),
tween: _tween,
builder: (BuildContext context, Color? value, Widget? child) {
return Container(
width: 100,
height: 100,
color: value,
alignment: Alignment.center, // 设置文字居中
child: const Text("点击改变颜色"),
);
},
)));
}
}
class _YcHomeBodyState extends State<YcHomeBody>
with SingleTickerProviderStateMixin {
// 动画控制器
late AnimationController _controller;
// 颜色动画
late Animation _colorAnimation;
// 大小动画
late Animation _sizeAnimation;
void initState() {
_controller =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
// 使用AnimationController的drive方法将一个Tween对象与AnimationController关联起来
_colorAnimation =
_controller.drive(ColorTween(begin: Colors.blue, end: Colors.red));
// 使用AnimationController的drive方法将一个Tween对象与AnimationController关联起来
_sizeAnimation = _controller
.drive(SizeTween(begin: const Size(100, 50), end: const Size(50, 100)));
super.initState();
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Center(
//创建一个手势识别器
child: GestureDetector(
onTap: () {
setState(() {
// 开始动画
_controller.forward();
});
},
child: AnimatedBuilder(
animation: _controller,
builder: (context, widget) {
return Container(
width: _sizeAnimation.value.width,
height: _sizeAnimation.value.height,
color: _colorAnimation.value,
alignment: Alignment.center, // 设置文字居中
child: const Text("点击改变颜色"),
);
})));
}
}
列表动画
AnimatedList
提供了一种简单的方式使列表数据发生变化时加入过渡动画
AnimatedList主要属性如下表。
属性 | 说明 |
---|---|
itemBuilder | 一个函数,列表的每一个索引会调用,这个函数有一个animation参数,可以设置成任何一个动画 |
initialItemCount | item的个数 |
scrollDirection | 滚动方向,默认垂直 |
controller | scroll控制器 |
列表数据的插入和删除有进出场动画需要调用AnimatedListState
指定的方法,只删除原数据并调用setState
方法是没有动画效果的
class _YcHomeBodyState extends State<YcHomeBody>
with SingleTickerProviderStateMixin {
// 定义一个全局的key来管理AnimatedListState对象,并将其传递给AnimatedList构造函数
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
// 定义列表
final List<String> _itemList = ["item 1", "item 2", "item 3"];
// 新增
void addItem() {
_itemList.add('item ${_itemList.length + 1}');
_listKey.currentState?.insertItem(_itemList.length - 1);
}
// 删除
void removeItem() {
// 删除操作要注意,要先删除列表中的数据在删除AnimatedListState的状态
// 并且关于index下标的操作,要放在删除操作之前,不然会导致删除时下标错误报错
int index = _itemList.length - 1;
String title = _itemList[index];
_itemList.removeAt(index);
_listKey.currentState?.removeItem(
index,
(context, animation) => SlideTransition(
position: animation.drive(CurveTween(curve: Curves.easeIn)).drive(
Tween<Offset>(begin: const Offset(1, 1), end: const Offset(0, 1))),
child: Card(
child: ListTile(
title: Text(title),
),
),
),
);
}
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(onPressed: addItem, child: const Text('增加')),
ElevatedButton(onPressed: removeItem, child: const Text('减少')),
],
),
// AnimatedList需要有高度
Expanded(
child: AnimatedList(
key: _listKey,
initialItemCount: _itemList.length, // item的个数
itemBuilder: (context, index, animation) {
// 为每一个item设置动画,将曲线动画和平移动画结合在一起
return SlideTransition(
// 动画对象,用于控制子组件的平移动画
position: animation
.drive(CurveTween(curve: Curves.easeIn))
.drive(Tween<Offset>(
begin: const Offset(1, 1),
end: const Offset(0, 1))),
child: Card(
child: ListTile(
title: Text(_itemList[index]),
),
),
);
}))
],
);
}
}
这个东西挺难搞的,出了不少问题。删除时一点要注意:
- 删除操作要注意,要先删除列表中的数据在删除AnimatedListState的状态
- 并且关于index下标的操作,要放在删除操作之前,不然会导致删除时下标错误报错
关于新增时简单,删除时复杂我查到的解释是:
在AnimatedList中,新增操作只需要添加数据到列表中并调用AnimatedListState的insertItem方法即可,AnimatedListState会自动处理动画效果。
但是删除操作需要手动处理动画效果,因为AnimatedListState无法自动处理删除动画。因此,需要手动调用AnimatedListState的removeItem方法,并在其中指定删除动画的实现方式。
Hero
Hero
用于在两个页面之间实现平滑的过渡效果。它可以将一个widget从一个页面转换到另一个页面,同时保持其外观和位置不
变。
Hero
动画通常用于在两个页面之间传递图像或其他媒体内容时,可以使用户感觉到这些内容在两个页面之间平滑地移动。
class _YcHomeBodyState extends State<YcHomeBody> {
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondPage()),
);
},
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://scpic3.chinaz.net/files/default/imgs/2023-06-07/f84b7dd1b1e82805_s_w285.jpg'),
),
),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://scpic3.chinaz.net/files/default/imgs/2023-06-07/f84b7dd1b1e82805_s_w285.jpg'),
),
),
);
}
}