美文网首页
Flutter入门10--动画

Flutter入门10--动画

作者: zyc_在路上 | 来源:发表于2022-01-26 10:25 被阅读0次

    Animation

    • 在Flutter中,实现动画的核心类是Animation,Widget可以直接将这些动画合并到自己的build方法中来读取它们当前值或监听它们的状态变化;
    • Animation是一个抽象类,常见的方法如下:
    • addListener方法
      • 每当动画的状态值发生变化时,动画都会通知所有通过 addListener 添加的监听器。
      • 通常,一个正在监听动画的state对象会调用自身的setState方法,将自身传入这些监听器的回调函数来通知 widget 系统需要根据新状态值进行重新构建。
    • addStatusListener方法
      • 当动画的状态发生变化时,会通知所有通过 addStatusListener 添加的监听器。
      • 通常情况下,动画会从 dismissed 状态开始,表示它处于变化区间的开始点。
      • 举例来说,从 0.0 到 1.0 的动画在 dismissed 状态时的值应该是 0.0。
      • 动画进行的下一状态可能是 forward(比如从 0.0 到 1.0)或者 reverse(比如从 1.0 到 0.0)。
      • 最终,如果动画到达其区间的结束点(比如 1.0),则动画会变成 completed状态。

    AnimationController

    • AnimationController是Animation的一个子类,通常我们会创建一个 AnimationController实例对象来实现动画效果;
    • AnimationController会生成一系列的值,默认情况下值是0.0到1.0区间的值;
    • AnimationController提供了对动画的控制
      • forward:向前执行动画
      • reverse:方向播放动画
      • stop:停止动画
    • 其构造函数如下:
      AnimationController({
        double value,
        this.duration,
        this.reverseDuration,
        this.debugLabel,
        this.lowerBound = 0.0,
        this.upperBound = 1.0,
        this.animationBehavior = AnimationBehavior.normal,
        @required TickerProvider vsync,
      })
    
    • value:初始化值;
    • duration:动画执行的时间;
    • reverseDuration:反向动画执行的时间;
    • lowerBound:最小值;
    • upperBound:最大值;
    • vsync:垂直信号,必传参数;

    CurvedAnimation

    • CurvedAnimation是Animation的一个子类,它的作用是给Animation增加执行曲线,执行速率;
    • CurvedAnimation可以将AnimationController和Curve结合起来,生成一个新的Animation对象;
    • Curve类型的对象的有一些常量Curves(和Color类型有一些Colors是一样的),可以供我们直接使用,在官网https://api.flutter.dev/flutter/animation/Curves-class.html 中提供了动画效果;

    Tween

    • 默认情况下,AnimationController动画生成的值所在区间是0.0到1.0,如果需要使用这个以外的值,或者其他的数据类型,就需要使用Tween;
    • Tween构造函数如下:
    Tween({ this.begin, this.end })
    
    • 传参味初始值与结束值,可确定一段区间;
    • Tween也有一些子类,比如ColorTween、BorderTween,可以针对动画或者边框来设置动画的值;

    案例代码:

    • 需求:目标组件心形图标,由小到大,由大到小 无限循环 ,点击可暂停动画,再次点击在原来的状态基础上继续执行动画;
    import 'package:flutter/material.dart';
    
    void main() => runApp(SFMyApp());
    
    class SFMyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: SFHomePage());
      }
    }
    
    class SFHomePage extends StatefulWidget {
      @override
      _SFHomePageState createState() => _SFHomePageState();
    }
    
    class _SFHomePageState extends State<SFHomePage> with SingleTickerProviderStateMixin{
      AnimationController _controller;
      Animation _animation;
      Animation _animationSize;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //1.创建Animation Controller
        _controller = AnimationController(
          vsync: this,
          duration: Duration(seconds: 2),
          lowerBound: 0.0,
          upperBound: 1.0
        );
    
        //2.设置Curve值 动画的执行速率
        _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
    
        //3.Tween
        _animationSize = Tween(begin: 50.0,end: 150.0).animate(_animation);
    
        //3.监听动画值的改变 刷新界面
        _controller.addListener(() {
          setState(() {
    
          });
        });
    
        //4.监听动画状态的改变
        _controller.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            _controller.reverse();
          }else if (status == AnimationStatus.dismissed) {
            _controller.forward();
          }
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: Text("基础widget")),
            body: Center(
              child: Icon(Icons.favorite,color: Colors.red,size: _animationSize.value),
            ),
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.play_arrow),
            onPressed: () {
              if (_controller.isAnimating) {
                _controller.stop();
              } else if (_controller.status == AnimationStatus.forward) {
                _controller.forward();
              } else if (_controller.status == AnimationStatus.reverse) {
                _controller.reverse();
              } else {
                _controller.forward();
              }
            },
          ),
        );
      }
    
      @override
      void dispose() {
        //销毁 回收内存
        _controller.dispose();
        super.dispose();
      }
    }
    

    AnimatedWidget

    • 上述实现的动画效果,存在一个弊端:
    • 监听动画值的改变,然后调用setState方法,就会导致State类build的方法重新执行,里面包含的所有组件都会重新构建,这样效率十分低下;
    • 可通过AnimatedWidget来解决这个弊端,执行动画时,仅仅让心形组件重建即可,自定义组件SFAnimationIcon继承自AnimatedWidget, 实现如下:
    import 'package:flutter/material.dart';
    
    void main() => runApp(SFMyApp());
    
    class SFMyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: SFHomePage());
      }
    }
    
    class SFHomePage extends StatefulWidget {
      @override
      _SFHomePageState createState() => _SFHomePageState();
    }
    
    class _SFHomePageState extends State<SFHomePage> with SingleTickerProviderStateMixin{
      AnimationController _controller;
      Animation _animation;
      Animation _animationSize;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //1.创建Animation Controller
        _controller = AnimationController(
          vsync: this,
          duration: Duration(seconds: 2),
          lowerBound: 0.0,
          upperBound: 1.0
        );
    
        //2.设置Curve值 动画的执行速率
        _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
    
        //3.Tween
        _animationSize = Tween(begin: 50.0,end: 150.0).animate(_animation);
    
        //3.监听动画值的改变 刷新界面
        // _controller.addListener(() {
        //   setState(() {
        //
        //   });
        // });
    
        //4.监听动画状态的改变
        _controller.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            _controller.reverse();
          }else if (status == AnimationStatus.dismissed) {
            _controller.forward();
          }
        });
      }
    
      @override
      Widget build(BuildContext context) {
        print("_SFHomePageState build");
        return Scaffold(
            appBar: AppBar(title: Text("基础widget")),
            body: Center(
              child: SFAnimationIcon(_animationSize),
            ),
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.play_arrow),
            onPressed: () {
              if (_controller.isAnimating) {
                _controller.stop();
              } else if (_controller.status == AnimationStatus.forward) {
                _controller.forward();
              } else if (_controller.status == AnimationStatus.reverse) {
                _controller.reverse();
              } else {
                _controller.forward();
              }
            },
          ),
        );
      }
    
      @override
      void dispose() {
        //销毁 回收内存
        _controller.dispose();
        super.dispose();
      }
    }
    
    class SFAnimationIcon extends AnimatedWidget {
      final Animation _animationSize;
    
      SFAnimationIcon(this._animationSize): super(listenable: _animationSize);
    
      @override
      Widget build(BuildContext context) {
        return Icon(Icons.favorite,color: Colors.red,size: _animationSize.value);
      }
    }
    

    AnimatedBuilder

    • AnimatedWidget解决了上述动画的执行效率问题,但其也存在弊端:
      • 我们每次都要新建一个类来继承自AnimatedWidget;
      • 如果我们的动画Widget有子Widget,那么意味着它的子Widget也会重新build;
    • 可通过AnimatedBuilder来解决上面的两个问题,代码如下:
    import 'package:flutter/material.dart';
    
    void main() => runApp(SFMyApp());
    
    class SFMyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: SFHomePage());
      }
    }
    
    class SFHomePage extends StatefulWidget {
      @override
      _SFHomePageState createState() => _SFHomePageState();
    }
    
    class _SFHomePageState extends State<SFHomePage> with SingleTickerProviderStateMixin{
      AnimationController _controller;
      Animation _animation;
      Animation _animationSize;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //1.创建Animation Controller
        _controller = AnimationController(
            vsync: this,
            duration: Duration(seconds: 2),
            lowerBound: 0.0,
            upperBound: 1.0
        );
    
        //2.设置Curve值 动画的执行速率
        _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
    
        //3.Tween
        _animationSize = Tween(begin: 50.0,end: 150.0).animate(_animation);
    
        //3.监听动画值的改变 刷新界面
        // _controller.addListener(() {
        //   setState(() {
        //
        //   });
        // });
    
        //4.监听动画状态的改变
        _controller.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            _controller.reverse();
          }else if (status == AnimationStatus.dismissed) {
            _controller.forward();
          }
        });
      }
    
      @override
      Widget build(BuildContext context) {
        print("_SFHomePageState build");
        return Scaffold(
          appBar: AppBar(title: Text("基础widget")),
          body: Center(
            child: AnimatedBuilder(
              animation: _controller,
              builder: (ctx,child) {
                return Icon(Icons.favorite,color: Colors.red,size: _animationSize.value);
              },
            ),
          ),
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.play_arrow),
            onPressed: () {
              if (_controller.isAnimating) {
                _controller.stop();
              } else if (_controller.status == AnimationStatus.forward) {
                _controller.forward();
              } else if (_controller.status == AnimationStatus.reverse) {
                _controller.reverse();
              } else {
                _controller.forward();
              }
            },
          ),
        );
      }
    
      @override
      void dispose() {
        //销毁 回收内存
        _controller.dispose();
        super.dispose();
      }
    }
    

    交织动画

    • 动画集合了透明度变化、大小变化、颜色变化、旋转动画等;
    • 通过多个Tween生成了多个Animation对象;
    • 代码如下所示:
    import 'dart:math';
    
    import 'package:flutter/material.dart';
    
    void main() => runApp(SFMyApp());
    
    class SFMyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: SFHomePage());
      }
    }
    
    class SFHomePage extends StatefulWidget {
      @override
      _SFHomePageState createState() => _SFHomePageState();
    }
    
    class _SFHomePageState extends State<SFHomePage> with SingleTickerProviderStateMixin{
      AnimationController _controller;
      Animation _animation;
      Animation _animationSize;
      Animation _animationColor;
      Animation _animationOpacity;
      Animation _animationRotation;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //1.创建Animation Controller
        _controller = AnimationController(
            vsync: this,
            duration: Duration(seconds: 2),
            lowerBound: 0.0,
            upperBound: 1.0
        );
    
        //2.设置Curve值 动画的执行速率
        _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
    
        //3.Tween
        _animationSize = Tween(begin: 10.0,end: 200.0).animate(_controller);
        _animationColor = ColorTween(begin: Colors.orange,end: Colors.pink).animate(_controller);
        _animationOpacity = Tween(begin: 0.0,end: 1.0).animate(_controller);
        _animationRotation = Tween(begin: 0.0,end: 2 * pi).animate(_controller);
    
        //4.监听动画状态的改变
        _controller.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            _controller.reverse();
          }else if (status == AnimationStatus.dismissed) {
            _controller.forward();
          }
        });
      }
    
      @override
      Widget build(BuildContext context) {
        print("_SFHomePageState build");
        return Scaffold(
          appBar: AppBar(title: Text("基础widget")),
          body: Center(
            child: AnimatedBuilder(
              animation: _controller,
              builder: (ctx,child) {
                return Opacity(
                  opacity: _animationOpacity.value,
                  child: Transform(
                    transform: Matrix4.rotationZ(_animationRotation.value),
                    alignment: Alignment.center,
                    child: Container(
                      width: _animationSize.value,
                      height: _animationSize.value,
                      color: _animationColor.value,
                    ),
                  ),
                );
              },
            )
          ),
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.play_arrow),
            onPressed: () {
              if (_controller.isAnimating) {
                _controller.stop();
              } else if (_controller.status == AnimationStatus.forward) {
                _controller.forward();
              } else if (_controller.status == AnimationStatus.reverse) {
                _controller.reverse();
              } else {
                _controller.forward();
              }
            },
          ),
        );
      }
    
      @override
      void dispose() {
        //销毁 回收内存
        _controller.dispose();
        super.dispose();
      }
    }
    

    转场动画

    • 界面切换时的动画效果;
    • 新建一个界面ModalPage,代码如下:
    
    import 'package:flutter/material.dart';
    
    class ModalPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.red,
          appBar: AppBar(
            title: Text("Modal Page"),
          ),
          body: Center(
            child: Text("Modal Page"),
          ),
        );
      }
    }
    
    • 首页代码:
    import 'package:Fluter01/day01/pages/ModalPage.dart';
    import 'package:flutter/material.dart';
    
    void main() => runApp(SFMyApp());
    
    class SFMyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: SFHomePage());
      }
    }
    
    class SFHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: Text("基础widget")),
            body: Center(
              child: Text("Hello world!!"),
            ),
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.pool),
            onPressed: () {
              //iOS Modal界面切换
              // Navigator.of(context).push(MaterialPageRoute(
              //   builder: (ctx) {
              //     return ModalPage();
              //   },
              //   fullscreenDialog: true
              // ));
    
              Navigator.of(context).push(PageRouteBuilder(
                transitionDuration: Duration(seconds: 3),
                pageBuilder: (ctx,animation1,animation2) {
                  return FadeTransition(
                    opacity: animation1,
                    child: ModalPage()
                  );
                }
              ));
    
            },
          ),
        );
      }
    }
    
    • 通过PageRouteBuilder实现转场动画效果;

    Hero动画

    • 需求:图片列表展示图片,当点击一张图片时,切换到图片详情页面,显示点击的图片,切换有动画效果;
    • 图片详情页面,代码如下:
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    
    class ImageDetailPage extends StatelessWidget {
    
      final String imageUrl;
    
      ImageDetailPage(this.imageUrl);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.black,
          body: Center(
            child: GestureDetector(
              onTap: () {
                Navigator.of(context).pop();
              },
              child: Hero(
                tag: imageUrl,
                child: Image.network(imageUrl)
              )
            ),
          ),
        );
      }
    }
    
    • 首页代码如下:
    import 'package:Fluter01/day01/pages/ImageDetailPage.dart';
    import 'package:Fluter01/day01/pages/ModalPage.dart';
    import 'package:flutter/material.dart';
    
    void main() => runApp(SFMyApp());
    
    class SFMyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: SFHomePage());
      }
    }
    
    class SFHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("基础widget")),
          body: GridView(
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              crossAxisSpacing: 8,
              mainAxisSpacing: 8,
              childAspectRatio: 16/9
            ),
            children: List.generate(20, (index) {
              final String imageUrl = "https://picsum/photos/500/500?random=$index";
              return GestureDetector(
                onTap: () {
                  Navigator.of(context).push(PageRouteBuilder(
                    pageBuilder: (ctx,animation1,animation2) {
                      return FadeTransition(opacity: animation1, child: ImageDetailPage(imageUrl));
                    }
                  ));
                },
                child: Hero(
                  tag: imageUrl,
                  child: Image.network(
                    "https://picsum/photos/500/500?random=$index",
                    fit: BoxFit.cover,
                  ),
                ),
              );
            }),
          ),
        );
      }
    }
    

    相关文章

      网友评论

          本文标题:Flutter入门10--动画

          本文链接:https://www.haomeiwen.com/subject/bjuwhrtx.html