一、动画API说明:
动画由Animation、Curve、AnimationController、Tween一起配合完成。
1.Animation用于保存动画的过渡值和状态:
addListener():监听每一帧的回调事件。
addStatusListener():监听动画状态改变事件,AnimationStatus.forward为开始、completed为结束、reverse为反向、dismissed为终止。
例:
Animation anim = CurvedAnimation(parent: animController, curve: Curves.linear);
2.Curve用于设置动画效果:
效果常量:
Curves.linear(匀速的)、decelerate(匀减速)、ease(开始加速,后面减速)、easeIn(开始慢,后面快)、easeOut(开始快,后面慢)、easeInOut(开始慢,然后加速,最后再减速)等
自定义Curve:
class MyCurve extends Curve {
@override
double transform(double 原值) {
return 新值; //自定义规则计算新值
}
}
3.AnimationController用于控制动画:
AnimationController animController = AnimationController(
duration: const Duration(milliseconds: 动画时长),
lowerBound: 开始值, //默认为0.0,默认范围[0.0,1.0]
upperBound: 结束值, //默认为1.0,默认范围[0.0,1.0]
vsync: this,
);
...
animController.forward(); //开始执行动画
animController.stop(); //停止动画
animController.reverse(); //反向播放动画
4.Tween用于生成不同范围或数据类型的动画值(默认范围[0.0,1.0]):
Tween t = Tween<double>(begin: 开始值, end: 结束值); //数值过渡
Tween t = ColorTween(begin: 开始颜色, end: 结束颜色); //颜色值过渡
...
Animation<double> anim1 = t.animate(animController); //Tween传入AnimationController,生成Animation
Animation<double> anim2 = t.animate(anim); //Tween包装原有Animation,生成新的Animation
5.Ticker处理当前页在后台时停止动画,实现以下类之一即可:
with SingleTickerProviderStateMixin //适合1个AnimationController
with TickerProviderStateMixin //适合多个AnimationController
6.线性插值lerp函数(图像是一条直线):
//a 为起始颜色,b为终止颜色,t为当前动画的进度[0,1]
Color.lerp(开始颜色, 结束颜色, 动画进度值); //进度值为默认[0-1]
Size.lerp(开始大小, 结束大小, 动画进度值);
Rect.lerp(开始大小, 结束大小, 动画进度值);
Offset.lerp(开始偏移值, 结束偏移值, 动画进度值);
Decoration.lerp(开始装饰, 结束装饰, 动画进度值);
...
Tween t = Tween<double>(begin: 开始值, end: 结束值);
t.lerp(动画进度值);
...
二、动画实现:
1.Animation+AnimationController+Curve+Tween实现补间动画(效果类似Tween动画):
(1)AnimatedBuilder方式(推荐,使用AnimatedBuilder包装要动画的Widget,省去..addListener):
class _PageState extends State<AnimationPage> with SingleTickerProviderStateMixin { //with TickerProviderStateMixin适合多个AnimationController
late Animation<double> animation; //用于保存动画的过渡值和状态
late AnimationController animController; //动画控制类
@override
initState() {
super.initState();
animController = AnimationController(duration: const Duration(milliseconds: 5000), vsync: this); //创建动画控制类
animation = CurvedAnimation(parent: animController, curve: Curves.easeInOut); //(非必须,不添加时为匀速)添加另外动画效果
animation = Tween(begin: 0.0, end: 300.0).animate(animation); //Tween将值从0-300,包装带动画效果的Animation,生成新的Animation
animation.addStatusListener((status) { //监听动画状态改变事件,AnimationStatus.forward为开始、completed为结束、reverse为反向、dismissed为终止
//...
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(child: Text("开始动画"),
onPressed: () {
animController.forward(); //开始执行动画
}),
AnimatedBuilder( //使用AnimatedBuilder包装要动画的Widget
animation: animController,
builder: (BuildContext ctx, child) {
return Container(width: animation.value, height: animation.value, color: Colors.blue); //通过animation.value值的改变产生动画效果
})
]);
}
@override
dispose() {
animController.dispose(); //释放动画资源
super.dispose();
}
}
(2)AnimatedWidget方式(自定义类继承AnimatedWidget,包装要执行动画的Widget,省去..addListener):
class ... { //同方式1
... //同方式1
@override
initState() {
... //同方式1
animation = Tween(...).animate(animation); //省略..addListener方法
}
@override
Widget build(BuildContext context) {
return Column(
children: [
..., //同方式1
CustomImage(animation: animation) //使用自定义AnimatedWidge
]);
}
... //同方式1
}
class CustomImage extends AnimatedWidget {
const CustomImage({Key? key, required Animation<double> animation}) : super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>; //获取Animation
return Image.asset("images/header.png", width: animation.value, height: animation.value); //通过animation.value值的改变产生动画效果
}
}
(3)方式3(最不推荐,手动添加..addListener执行setState):
class ... { //同方式1
... //同方式1
@override
initState() {
... //同方式1
animation = Tween(...).animate(animation) //同方式1
..addListener(() { //需要添加..addListener方法监听每一帧的回调事件
setState(() => {}); //更新UI状态
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
..., //同方式1
Image.asset("images/header.png", width: animation.value, height: animation.value) //通过animation.value值的改变产生动画效果
]);
}
... //同方式1
}
(4)系统预置过渡类:
FadeTransition(opacity: animation, child: Widget类()); //渐隐渐显过渡
ScaleTransition(scale: animation, child: Widget类()); //放大缩小过渡
SizeTransition(sizeFactor: animation, child: Widget类());//位移过渡
2.实现页面跳转动画:
系统自带页面跳转动画类:
MaterialPageRoute:与系统页面保持一致的页面跳转动画
CupertinoPageRoute:iOS风格的页面跳转动画
(1)PageRouteBuilder实现页面跳转动画:
Navigator.push(context,
PageRouteBuilder( //PageRouteBuilder实现自定义页面跳转动画
transitionDuration: Duration(milliseconds: 跳转过渡时长),
pageBuilder: (BuildContext context, Animation<double> animation, Animation secondaryAnimation) {
return FadeTransition(opacity: animation, child: 页面2()); //此处根据不同过渡类实现不同页面跳转效果
}
));
(2)自定义PageRoute类实现页面跳转动画:
class MyPageRoute extends PageRoute {//自定义PageRoute类,修改过渡动画
MyPageRoute({
required this.builder,
this.transitionDuration = const Duration(milliseconds: 1000), //过渡时长
this.maintainState = true,
this.barrierLabel = "",
this.barrierColor = Colors.blue, //动画切换时页面周边的颜色
});
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => builder(context);
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return ScaleTransition(scale: animation, child: builder(context)); //此处配置动画过渡类
}
final WidgetBuilder builder;
@override
final Duration transitionDuration;
@override
final bool maintainState;
@override
final String barrierLabel;
@override
final Color barrierColor;
}
...
Navigator.push(context, MyPageRoute(builder: (context) { //使用自定义PageRoute类实现页面跳转动画
return Page2();
}));
3.Hero实现飞行动画:
实现视觉效果:第1页中指定Widget飞到另1个页面中指定位置
(1)页面1实现(将待飞行Widget用Hero包装):
class HeroPage1 extends StatelessWidget {//页面1
const HeroPage1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("页面1标题")),
body: Row(
children: <Widget>[
Hero( //将child的Widget执行飞行动画
tag: "Hero-Tag", //Hero动画唯一标记,切换的两个页面Hero的tag要一致
child: Image.asset( "images/header.png",) //待飞行的Widget
),
ElevatedButton( //只是用来点击触发动画用
child: Text("点击触发飞行动画"),
onPressed: () {
Navigator.push(context, PageRouteBuilder( //页面跳转
pageBuilder: (BuildContext context, animation, secondaryAnimation) {
return FadeTransition(opacity: animation, child: HeroPage2()); //FadeTransition为渐隐渐入过渡动效,HeroPage2为第2个页面
},
));
})
],
));
}
}
(2)页面2实现(将飞行终点Widget用Hero包装):
class HeroPage2 extends StatelessWidget {//页面2
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("页面2标题")),
body: Hero(
tag: "Hero-Tag", //Hero动画唯一标记,切换的两个页面Hero的tag要一致
child: Image.asset("images/photo.jpg") //飞行停止后的最终Widget
));
}
}
4.交织动画(动画组,多个动画同时进行):
说明:
需要多个Animation
必须由一个AnimationController控制所有的Animation
可以给每个Animation指定Interval
Interval( //动画执行时长截取类
0.2, 0.8, //对动画原时长截取,例:动画时长为5秒时,只在1秒-4秒之间执行动画,0-1秒与4-5秒不执行
curve: Curves.easeInSine,
)
(1)多个Animation封装到Widget中,由外部传入AnimationController进行控制:
class AnimGroupWidget extends StatelessWidget { //封装了多个动画的Widget
AnimGroupWidget({Key? key, required this.animController}) : super(key: key);
late final AnimationController animController; //由外部传入1个AnimationController,控制所有动画
late final Animation<double> anim1 = Tween<double>(begin: 0, end: 100).animate(animController); //动画1,此处为数值渐变动画
late final Animation<Color?> anim2 = ColorTween(begin: Colors.green, end: Colors.red).animate(animController); //动画2,此处为颜色渐变动画
late final Animation<double> anim3 = Tween<double>(begin: 0, end: 100).animate(CurvedAnimation( //动画3,此处使用了Interval
parent: animController,
curve: const Interval( //动画执行时长
0.2, 0.8, //对动画原时长截取,例:动画时长为5秒时,只在1秒-4秒之间执行动画,0-1秒与4-5秒不执行
curve: Curves.easeInSine,
),
));
@override
Widget build(BuildContext context) {
return AnimatedBuilder(animation: animController,
builder: (BuildContext ctx, child) {
return Container(width: anim1.value, height: anim1.value, color: anim2.value); //Container根据动画1+动画2,改变背景色与宽高
}
);
}
}
(2)使用动画组Widget,并控制开始动画:
class _PageState extends State<Page1> with TickerProviderStateMixin {
late AnimationController animController; //控制多个动画
@override
void initState() {
super.initState();
animController = AnimationController(duration: const Duration(milliseconds: 5000), vsync: this);
}
@override
Widget build(BuildContext context) {
return Column(children: [
ElevatedButton(
onPressed: () {
animController.forward(); //开始动画(多个动画同时进行)
},
child: Text("点击开始多个动画"),
),
AnimGroupWidget(animController: animController) //AnimGroupWidget封装了多个动画的Widget,传入统一的AnimationController
],
);
}
}
5.AnimatedSwitcher实现Widget切换动画:
late List<T> layoutList;
...
AnimatedSwitcher( //AnimatedSwitcher也是Widget
reverseDuration: Duration(milliseconds: 2000), //旧child隐藏的动画时长
duration: Duration(milliseconds: 2000), //新child显示的动画时长
switchOutCurve: Curves.easeInOut, //旧child隐藏的动画效果
switchInCurve: Curves.easeInOut, //新child显示的动画效果
transitionBuilder: (Widget child, Animation<double> animation) { //动画构造器
return FadeTransition(child: child, opacity: animation); //过渡类
},
child: layoutList[position], //需要动画的布局,同个布局名要设置不同的key才有动画效果
)
6.Widget属性改变时实现过渡动画:
(1)AnimatedPadding,padding值变化时执行过渡动画:
double padding = 0; //旧padding值
...
AnimatedPadding(
duration: Duration(milliseconds: 2000),
padding: EdgeInsets.all(padding), //此值改变时执行过渡动画
child: Text("AnimatedPadding测试")
)
...
setState(() {
padding = 10; //设为新padding值时执行过渡动画
});
(2)AnimatedPositioned(与Stack联合使用),位置或大小变化时执行过渡动画:
double position = 0; //旧位置
...
Stack(
children: <Widget>[
AnimatedPositioned(
duration: Duration(milliseconds: 2000),
left: position, //此值改变时执行过渡动画
top: position, //此值改变时执行过渡动画
child: Text("AnimatedPositioned测试")
)
]
)
...
setState(() {
position = 40; //设为新位置时执行过渡动画
});
(3)AnimatedAlign,alignment变化时执行过渡动画:
Alignment align = Alignment.topLeft; //旧对齐方式
...
AnimatedAlign(
duration: Duration(milliseconds: 2000),
alignment: align, //此值改变时执行过渡动画
child: Text("AnimatedAlign测试"),
)
...
setState(() {
align = Alignment.bottomRight; //设为新对齐方式时触发动画
});
(4)AnimatedOpacity,透明度opacity发生变化时执行过渡动画:
double opacity = 0.5; //旧透明度
...
AnimatedOpacity(
duration: Duration(milliseconds: 2000),
opacity: scale, //此值改变时执行过渡动画
child: Text("AnimatedOpacity测试")
)
...
setState(() {
opacity = 0.5; //设为新透明度时触发动画
});
(5)AnimatedContainer,属性值变化时执行过渡动画:
double size = 50; //旧宽高
...
AnimatedContainer(
duration: Duration(milliseconds: 2000),
width: size, //此值改变时执行过渡动画
height: size, //此值改变时执行过渡动画
child: Text("AnimatedContainer测试")
)
...
setState(() {
size = 100; //设为新宽高时触发动画
});
(6)AnimatedDefaultTextStyle,字体样式发生变化时执行过渡动画:
TextStyle style = const TextStyle(fontSize: 14, color: Colors.red); //旧样式
...
AnimatedDefaultTextStyle(
duration: Duration(milliseconds: 2000),
style: style, //此值改变时执行过渡动画
child: Text("AnimatedDefaultTextStyle测试")),
...
setState(() {
style = const TextStyle(fontSize: 16, color: Colors.yellow); //设为新样式时触发动画
});