一、动画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:
[ - 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
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
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:
[ - 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); //设为新样式时触发动画
- });