• Flutter:动画摘要


    一、动画API说明:
    动画由Animation、Curve、AnimationController、Tween一起配合完成。

    1.Animation用于保存动画的过渡值和状态:

    1. addListener():监听每一帧的回调事件。
    2. addStatusListener():监听动画状态改变事件,AnimationStatus.forward为开始、completed为结束、reverse为反向、dismissed为终止。

    例:

    Animation anim = CurvedAnimation(parent: animController, curve: Curves.linear);

    2.Curve用于设置动画效果:

    效果常量:

    Curves.linear(匀速的)、decelerate(匀减速)、ease(开始加速,后面减速)、easeIn(开始慢,后面快)、easeOut(开始快,后面慢)、easeInOut(开始慢,然后加速,最后再减速)等

    自定义Curve:

    1. class MyCurve extends Curve {
    2.   @override
    3.   double transform(double 原值) {
    4.     return 新值; //自定义规则计算新值
    5.   }
    6. }

    3.AnimationController用于控制动画:

    1. AnimationController animController = AnimationController(
    2.   duration: const Duration(milliseconds: 动画时长),
    3.   lowerBound: 开始值,  //默认为0.0,默认范围[0.0,1.0]
    4.   upperBound: 结束值,  //默认为1.0,默认范围[0.0,1.0]
    5.   vsync: this,
    6. );
    7. ...
    8. animController.forward();   //开始执行动画
    9. animController.stop();      //停止动画
    10. animController.reverse();   //反向播放动画

    4.Tween用于生成不同范围或数据类型的动画值(默认范围[0.0,1.0]):

    1. Tween t = Tween<double>(begin: 开始值, end: 结束值);   //数值过渡
    2. Tween t = ColorTween(begin: 开始颜色, end: 结束颜色);  //颜色值过渡
    3. ...
    4. Animation<double> anim1 = t.animate(animController);   //Tween传入AnimationController,生成Animation
    5. Animation<double> anim2 = t.animate(anim);             //Tween包装原有Animation,生成新的Animation

    5.Ticker处理当前页在后台时停止动画,实现以下类之一即可:

    1. with SingleTickerProviderStateMixin    //适合1个AnimationController
    2. with TickerProviderStateMixin          //适合多个AnimationController

    6.线性插值lerp函数(图像是一条直线):

    1. //a 为起始颜色,b为终止颜色,t为当前动画的进度[0,1]
    2. Color.lerp(开始颜色, 结束颜色, 动画进度值);   //进度值为默认[0-1]
    3. Size.lerp(开始大小, 结束大小, 动画进度值);
    4. Rect.lerp(开始大小, 结束大小, 动画进度值);
    5. Offset.lerp(开始偏移值, 结束偏移值, 动画进度值);
    6. Decoration.lerp(开始装饰, 结束装饰, 动画进度值);
    7. ...
    8. Tween t = Tween<double>(begin: 开始值, end: 结束值);
    9. t.lerp(动画进度值);
    10. ...

    二、动画实现:

    1.Animation+AnimationController+Curve+Tween实现补间动画(效果类似Tween动画):

    (1)AnimatedBuilder方式(推荐,使用AnimatedBuilder包装要动画的Widget,省去..addListener):

    1. class _PageState extends State<AnimationPage> with SingleTickerProviderStateMixin { //with TickerProviderStateMixin适合多个AnimationController
    2.   late Animation<double> animation;  //用于保存动画的过渡值和状态
    3.   late AnimationController animController;   //动画控制类
    4.   @override
    5.   initState() {
    6.     super.initState();
    7.     animController = AnimationController(duration: const Duration(milliseconds: 5000), vsync: this); //创建动画控制类
    8.     animation = CurvedAnimation(parent: animController, curve: Curves.easeInOut);                    //(非必须,不添加时为匀速)添加另外动画效果
    9.     animation = Tween(begin: 0.0, end: 300.0).animate(animation);                                    //Tween将值从0-300,包装带动画效果的Animation,生成新的Animation
    10.     animation.addStatusListener((status) { //监听动画状态改变事件,AnimationStatus.forward为开始、completed为结束、reverse为反向、dismissed为终止
    11.       //...
    12.     });
    13.   }
    14.   @override
    15.   Widget build(BuildContext context) {
    16.     return Column(
    17.       children: [
    18.         ElevatedButton(child: Text("开始动画"),
    19.             onPressed: () {
    20.               animController.forward(); //开始执行动画
    21.             }),
    22.       AnimatedBuilder( //使用AnimatedBuilder包装要动画的Widget
    23.           animation: animController,
    24.           builder: (BuildContext ctx, child) {
    25.             return Container(width: animation.value, height: animation.value, color: Colors.blue);  //通过animation.value值的改变产生动画效果
    26.           })
    27.       ]);
    28.   }
    29.   @override
    30.   dispose() {
    31.     animController.dispose(); //释放动画资源
    32.     super.dispose();
    33.   }
    34. }

    (2)AnimatedWidget方式(自定义类继承AnimatedWidget,包装要执行动画的Widget,省去..addListener):

    1. class ... { //同方式1
    2.   ...  //同方式1
    3.   @override
    4.   initState() {
    5.     ...  //同方式1
    6.     animation = Tween(...).animate(animation);  //省略..addListener方法
    7.   }
    8.   @override
    9.   Widget build(BuildContext context) {
    10.     return Column(
    11.       children: [
    12.         ...,  //同方式1
    13.         CustomImage(animation: animation)  //使用自定义AnimatedWidge
    14.       ]);
    15.   }
    16.   ...  //同方式1
    17. }
    18. class CustomImage extends AnimatedWidget {
    19.   const CustomImage({Key? key, required Animation<double> animation}) : super(key: key, listenable: animation);
    20.   @override
    21.   Widget build(BuildContext context) {
    22.     final animation = listenable as Animation<double>;  //获取Animation
    23.     return Image.asset("images/header.png", width: animation.value, height: animation.value);  //通过animation.value值的改变产生动画效果
    24.   }
    25. }

    (3)方式3(最不推荐,手动添加..addListener执行setState):

    1. class ... { //同方式1
    2.   ...  //同方式1
    3.   @override
    4.   initState() {
    5.     ...  //同方式1
    6.     animation = Tween(...).animate(animation) //同方式1
    7.       ..addListener(() {    //需要添加..addListener方法监听每一帧的回调事件
    8.         setState(() => {}); //更新UI状态
    9.       });
    10.   }
    11.   @override
    12.   Widget build(BuildContext context) {
    13.     return Column(
    14.       children: [
    15.         ...,  //同方式1
    16.         Image.asset("images/header.png", width: animation.value, height: animation.value)  //通过animation.value值的改变产生动画效果
    17.       ]);
    18.   }
    19.   ...  //同方式1
    20. }

    (4)系统预置过渡类:

    1. FadeTransition(opacity: animation, child: Widget类());   //渐隐渐显过渡
    2. ScaleTransition(scale: animation, child: Widget类());    //放大缩小过渡
    3. SizeTransition(sizeFactor: animation, child: Widget类());//位移过渡

    2.实现页面跳转动画:

    系统自带页面跳转动画类:

    1. MaterialPageRoute:与系统页面保持一致的页面跳转动画
    2. CupertinoPageRoute:iOS风格的页面跳转动画

    (1)PageRouteBuilder实现页面跳转动画:

    1. Navigator.push(context,
    2.     PageRouteBuilder(  //PageRouteBuilder实现自定义页面跳转动画
    3.       transitionDuration: Duration(milliseconds: 跳转过渡时长),
    4.       pageBuilder: (BuildContext context, Animation<double> animation, Animation secondaryAnimation) {
    5.         return FadeTransition(opacity: animation, child: 页面2()); //此处根据不同过渡类实现不同页面跳转效果
    6.       }
    7.     ));

    (2)自定义PageRoute类实现页面跳转动画:

    1. class MyPageRoute extends PageRoute {//自定义PageRoute类,修改过渡动画
    2.   MyPageRoute({
    3.     required this.builder,
    4.     this.transitionDuration = const Duration(milliseconds: 1000),  //过渡时长
    5.     this.maintainState = true,
    6.     this.barrierLabel = "",
    7.     this.barrierColor = Colors.blue, //动画切换时页面周边的颜色
    8.   });
    9.   @override
    10.   Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => builder(context);
    11.   @override
    12.   Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
    13.     return ScaleTransition(scale: animation, child: builder(context));    //此处配置动画过渡类
    14.   }
    15.   final WidgetBuilder builder;
    16.   @override
    17.   final Duration transitionDuration;
    18.   @override
    19.   final bool maintainState;
    20.   @override
    21.   final String barrierLabel;
    22.   @override
    23.   final Color barrierColor;
    24. }
    25. ...
    26. Navigator.push(context, MyPageRoute(builder: (context) { //使用自定义PageRoute类实现页面跳转动画
    27.   return Page2();
    28. }));

    3.Hero实现飞行动画:
    实现视觉效果:第1页中指定Widget飞到另1个页面中指定位置

    (1)页面1实现(将待飞行Widget用Hero包装):

    1. class HeroPage1 extends StatelessWidget {//页面1
    2.   const HeroPage1({Key? key}) : super(key: key);
    3.   @override
    4.   Widget build(BuildContext context) {
    5.     return Scaffold(
    6.         appBar: AppBar(title: const Text("页面1标题")),
    7.         body: Row(
    8.           children: [
    9.             Hero( //将child的Widget执行飞行动画
    10.                 tag: "Hero-Tag", //Hero动画唯一标记,切换的两个页面Hero的tag要一致
    11.                 child: Image.asset( "images/header.png",)   //待飞行的Widget
    12.             ),
    13.             ElevatedButton(  //只是用来点击触发动画用
    14.                 child: Text("点击触发飞行动画"),
    15.                 onPressed: () {
    16.                   Navigator.push(context, PageRouteBuilder( //页面跳转
    17.                     pageBuilder: (BuildContext context, animation, secondaryAnimation) {
    18.                       return FadeTransition(opacity: animation, child: HeroPage2());  //FadeTransition为渐隐渐入过渡动效,HeroPage2为第2个页面
    19.                     },
    20.                   ));
    21.                 })
    22.           ],
    23.         ));
    24.   }
    25. }

    (2)页面2实现(将飞行终点Widget用Hero包装):

    1. class HeroPage2 extends StatelessWidget {//页面2
    2.   @override
    3.   Widget build(BuildContext context) {
    4.     return Scaffold(
    5.         appBar: AppBar(title: const Text("页面2标题")),
    6.         body: Hero(
    7.           tag: "Hero-Tag", //Hero动画唯一标记,切换的两个页面Hero的tag要一致
    8.           child: Image.asset("images/photo.jpg") //飞行停止后的最终Widget
    9.         ));
    10.   }
    11. }

    4.交织动画(动画组,多个动画同时进行):

    说明:
    需要多个Animation
    必须由一个AnimationController控制所有的Animation
    可以给每个Animation指定Interval

    1. Interval( //动画执行时长截取类
    2.   0.2, 0.8, //对动画原时长截取,例:动画时长为5秒时,只在1秒-4秒之间执行动画,0-1秒与4-5秒不执行
    3.   curve: Curves.easeInSine,
    4. )

    (1)多个Animation封装到Widget中,由外部传入AnimationController进行控制:

    1. class AnimGroupWidget extends StatelessWidget { //封装了多个动画的Widget
    2.   AnimGroupWidget({Key? key, required this.animController}) : super(key: key);
    3.   late final AnimationController animController;    //由外部传入1个AnimationController,控制所有动画
    4.   late final Animation<double> anim1 = Tween<double>(begin: 0, end: 100).animate(animController);  //动画1,此处为数值渐变动画
    5.   late final Animation anim2 = ColorTween(begin: Colors.green, end: Colors.red).animate(animController);  //动画2,此处为颜色渐变动画
    6.   late final Animation<double> anim3 = Tween<double>(begin: 0, end: 100).animate(CurvedAnimation( //动画3,此处使用了Interval
    7.     parent: animController,
    8.     curve: const Interval( //动画执行时长
    9.       0.2, 0.8, //对动画原时长截取,例:动画时长为5秒时,只在1秒-4秒之间执行动画,0-1秒与4-5秒不执行
    10.       curve: Curves.easeInSine,
    11.     ),
    12.   ));
    13.   @override
    14.   Widget build(BuildContext context) {
    15.     return AnimatedBuilder(animation: animController,
    16.       builder: (BuildContext ctx, child) {
    17.         return Container(width: anim1.value, height: anim1.value, color: anim2.value); //Container根据动画1+动画2,改变背景色与宽高
    18.       }
    19.     );
    20.   }
    21. }

    (2)使用动画组Widget,并控制开始动画:

    1. class _PageState extends State<Page1> with TickerProviderStateMixin {
    2.   late AnimationController animController; //控制多个动画
    3.   @override
    4.   void initState() {
    5.     super.initState();
    6.     animController = AnimationController(duration: const Duration(milliseconds: 5000), vsync: this);
    7.   }
    8.   @override
    9.   Widget build(BuildContext context) {
    10.     return Column(children: [
    11.         ElevatedButton(
    12.           onPressed: () {
    13.             animController.forward(); //开始动画(多个动画同时进行)
    14.           },
    15.           child: Text("点击开始多个动画"),
    16.         ),
    17.         AnimGroupWidget(animController: animController) //AnimGroupWidget封装了多个动画的Widget,传入统一的AnimationController
    18.       ],
    19.     );
    20.   }
    21. }

    5.AnimatedSwitcher实现Widget切换动画:

    1. late List layoutList;
    2. ...
    3. AnimatedSwitcher( //AnimatedSwitcher也是Widget
    4.   reverseDuration: Duration(milliseconds: 2000),  //旧child隐藏的动画时长
    5.   duration: Duration(milliseconds: 2000),         //新child显示的动画时长
    6.   switchOutCurve: Curves.easeInOut,               //旧child隐藏的动画效果
    7.   switchInCurve: Curves.easeInOut,                //新child显示的动画效果
    8.   transitionBuilder: (Widget child, Animation<double> animation) { //动画构造器
    9.     return FadeTransition(child: child, opacity: animation);  //过渡类
    10.   },
    11.   child: layoutList[position],  //需要动画的布局,同个布局名要设置不同的key才有动画效果
    12. )

    6.Widget属性改变时实现过渡动画:

    (1)AnimatedPadding,padding值变化时执行过渡动画:

    1. double padding = 0;  //旧padding值
    2. ...
    3. AnimatedPadding(
    4.     duration: Duration(milliseconds: 2000),
    5.     padding: EdgeInsets.all(padding),   //此值改变时执行过渡动画
    6.     child: Text("AnimatedPadding测试")
    7. )
    8. ...
    9. setState(() {
    10.   padding = 10;   //设为新padding值时执行过渡动画
    11. });

    (2)AnimatedPositioned(与Stack联合使用),位置或大小变化时执行过渡动画:

    1. double position = 0;  //旧位置
    2. ...
    3. Stack(
    4.   children: [
    5.     AnimatedPositioned(
    6.         duration: Duration(milliseconds: 2000),
    7.         left: position,  //此值改变时执行过渡动画
    8.         top: position,   //此值改变时执行过渡动画
    9.         child: Text("AnimatedPositioned测试")
    10.     )
    11.   ]
    12. )
    13. ...
    14. setState(() {
    15.   position = 40;  //设为新位置时执行过渡动画
    16. });

    (3)AnimatedAlign,alignment变化时执行过渡动画:

    1. Alignment align = Alignment.topLeft;  //旧对齐方式
    2. ...
    3. AnimatedAlign(
    4.   duration: Duration(milliseconds: 2000),
    5.   alignment: align,     //此值改变时执行过渡动画
    6.   child: Text("AnimatedAlign测试"),
    7. )
    8. ...
    9. setState(() {
    10.   align = Alignment.bottomRight; //设为新对齐方式时触发动画
    11. });

    (4)AnimatedOpacity,透明度opacity发生变化时执行过渡动画:

    1. double opacity = 0.5;  //旧透明度
    2. ...
    3. AnimatedOpacity(
    4.   duration: Duration(milliseconds: 2000),
    5.   opacity: scale,   //此值改变时执行过渡动画
    6.   child: Text("AnimatedOpacity测试")
    7. )
    8. ...
    9. setState(() {
    10.   opacity = 0.5; //设为新透明度时触发动画
    11. });

    (5)AnimatedContainer,属性值变化时执行过渡动画:

    1. double size = 50;  //旧宽高
    2. ...
    3. AnimatedContainer(
    4.   duration: Duration(milliseconds: 2000),
    5.   width: size,        //此值改变时执行过渡动画
    6.   height: size,       //此值改变时执行过渡动画
    7.   child: Text("AnimatedContainer测试")
    8. )
    9. ...
    10. setState(() {
    11.   size = 100; //设为新宽高时触发动画
    12. });

    (6)AnimatedDefaultTextStyle,字体样式发生变化时执行过渡动画:

    1. TextStyle style = const TextStyle(fontSize: 14, color: Colors.red);  //旧样式
    2. ...
    3. AnimatedDefaultTextStyle(
    4.     duration: Duration(milliseconds: 2000),
    5.     style: style,        //此值改变时执行过渡动画
    6.     child: Text("AnimatedDefaultTextStyle测试")),
    7. ...
    8. setState(() {
    9.   style = const TextStyle(fontSize: 16, color: Colors.yellow); //设为新样式时触发动画
    10. });


     

  • 相关阅读:
    Android Verity Boot(AVB)验证原理
    初识python
    java-php-python-ssm虚拟银行业务培训游戏计算机毕业设计
    SAP 通过Debug快速查找 EXPORT MEMORY ID 的 IMPORT MEMORY ID代码位置
    Spire.Office for .NET 7.12.0 2022年最后版本?
    聊聊springboot项目如何优雅的修改或者填充请求参数
    【面试】揭秘面试背后的那点真实
    el-table表格中加入输入框
    接口测试工具Postman的基本使用
    比赛调研资料
  • 原文地址:https://blog.csdn.net/a526001650a/article/details/127654284