Flutter动画快速入门

前言

动画是flutter中必学的知识点,动画的基础知识不多,学习flutter动画其实主要是学习下面这些类的使用,当然,如果要深度了解的话,还需要了解他们的源码。

  1. Animation
  2. AnimationController
  3. Tween
  4. AnimatedWidget
  5. AnimatedBuilder
  6. CurvedAnimation
  7. Hero

1. Animation

1. 基本概念

Animation是一个抽象类,CurvedAnimation(曲线动画)的父类,记录了当前动画的进度所代表的具体值和动画状态,内部有抽象方法addListener和addStatusListener,addListener可以监听当前动画的进度对应的具体值,addStatusListener可以监听当前动画的状态,有以下四种状态:

enum AnimationStatus {
  /// The animation is stopped at the beginning
  dismissed,

  /// The animation is running from beginning to end
  forward,

  /// The animation is running backwards, from end to beginning
  reverse,

  /// The animation is stopped at the end
  completed,
}
复制代码

每种状态的含义注释写的很明白了,就不再解释。

2. AnimationController

1. 基本概念

AnimationController 是一个动画控制器,它可以控制动画的开始和停止,控制动画正向还是反向播放,控制动画的执行时间,控制动画值的上下限。AnimationController的构造函数参数如下:

  1. duration:正方向动画的时间长度
  2. reverseDuration:反方向动画的时间长度
  3. lowerBound:动画值的下限,默认0
  4. upperBound:动画值的上限,默认1
  5. vsync:如果是在 State 中创建AnimationController,那么可以使用

SingleTickerProviderStateMixin,它的主要作用是获取每一帧刷新的通知,相当于给动画添加了一个动起来的引擎

AnimationController({
  double? value,
  this.duration,
  this.reverseDuration,
  this.debugLabel,
  this.lowerBound = 0.0,
  this.upperBound = 1.0,
  this.animationBehavior = AnimationBehavior.normal,
  required TickerProvider vsync,
}) 
复制代码

2. 使用

如下所示,在state类里面创建了一个动画控制器,正向播放,时间长度2秒,state类继承自
SingleTickerProviderStateMixin

AnimationController _animationController = AnimationController(duration: Duration(seconds: 2), vsync: this);
复制代码

3. Tween

1. 基本概念

Tween的代码如下:

class Tween<T extends dynamic> extends Animatable<T> {
 
  Tween({
    this.begin,
    this.end,
  });

  T? begin;

  T? end;

  @protected
  T lerp(double t) {
    assert(begin != null);
    assert(end != null);
    return begin + (end - begin) * t as T;
  }

  @override
  T transform(double t) {
    if (t == 0.0)
      return begin as T;
    if (t == 1.0)
      return end as T;
    return lerp(t);
  }
}
复制代码

Tween有一个dynamic的泛型限制,所以这个泛型可以是任意类型,Tween内部有 begin和end
两个成员属性,还有lerp和transform方法,这些代码很简单,其实就是用于将当前的动画进度转化成
begin和end两个成员变量的具体的中间值,也就是说,Tween的作用就是在给定的泛型限制下根据当前动画进度(0.0-1.0)获取动画进行的过程中的begin和end成员变量之间对应的具体值

2. 使用

1. 动画效果

如下图所示,一个黑色的方块由小变大

ca1bce8b79546dabde5f87ffc7f67339__mount_node_token=doccn7up292la7s7ufU9bSYLtor&mount_point=doc_image.gif

2. 代码

如下代码所示,先在21行创建一个AnimationController,正向执行动画,时间2s,再在22行创建一个Tween对象,起始值和结束值为0.0和200.0,然后在23行通过addListener方法设置监听,在监听的回调方法中通过_animation.value获取当前动画的进度的具体值,然后调用setState(() {})刷新页面。

mport 'dart:ffi';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';

class TweenStudy extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => TweenState();
}

class TweenState extends State with SingleTickerProviderStateMixin {
  Animation<double> _animation;
  AnimationController _animationController;
  double animationValue = 10;
  Color color = Colors.black;
  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(duration: Duration(seconds: 2), vsync: this);
    _animation = Tween<double>(begin: 0.0, end: 200.0).animate(_animationController)
      ..addListener(() {
        animationValue = _animation.value;
        setState(() {});
      })
      ..addStatusListener((status) {});
    _animationController.forward();
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        theme: ThemeData(primaryColor: Colors.white),
        home: Scaffold(
          appBar: AppBar(
              backgroundColor: Colors.white,
              toolbarHeight: 45,
              leading: GestureDetector(
                child: Icon(Icons.arrow_back),
                onTap: () => {Navigator.pop(context)},
              ),
              centerTitle: true,
              elevation: 0,
              title: Text(
                'TweenStudy',
                style: TextStyle(color: Colors.black87, fontSize: 18),
              )),
          body: Column(
            children: [
              GestureDetector(
                onTap:(){
                  _animationController.reset();
                  _animationController.forward();

                },
                child: Container(
                  color: color,
                  width: animationValue,
                  height: animationValue,
                  alignment: Alignment.center,
                ),
              )
            ],
          ),
        ));
  }

 @override
  void dispose() {
    //必须在super.dispose()调用,否则可能会有内存泄漏
    _animationController.stop();
    _animationController.dispose();
    super.dispose();
  }
}
复制代码

4. AnimatedWidget

1. 基本概念

AnimatedWidget 就是一个可以执行动画的Widget,继承自StatefulWidget,使用它可以简化动画的使用方式,在上面的例子中使用Tween的时候,我们给动画设置了addListener监听,然后在回调中手动调用setState(() {})刷新页面,如果使用了AnimatedWidget,那就不用再手动设置监听了

2. 使用

1. 动画效果

如下图所以,一个flutter logo从小放大

687b4730fe140203537b6fd2f6ed2dc5__mount_node_token=doccn7up292la7s7ufU9bSYLtor&mount_point=doc_image.gif

2. 代码

如下代码所示,先写一个类CustomAnimationWidget继承自 AnimatedWidget,然后重写build方法,返回一个承载动画的值,构造函数中接收一个Animation参数,然后重写build方法,返回一个承载动画的Container,Container的宽高使用Animation对象的value变量,value变量代表当前动画进度的具体值。然后在动画执行的过程中,Container的宽高就会发生变化,那么,使用CustomAnimationWidget呢?
首先还是需要在state中新建 AnimationController和Tween对象,将AnimationController和Tween对象绑定后启动动画,并且将Tween对象放入到
CustomAnimationWidget的构造函数中即可

/// 1. 先写一个类继承自 AnimatedWidget,然后重写build方法,返回一个承载动画的widget
class CustomAnimationWidget extends AnimatedWidget{

  /// 放大因子,用于放大动画值
  int factor = 1;

  CustomAnimationWidget({Key key, Animation<double> animation, this.factor = 1}): super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    Animation<double> animation = listenable;
    return Center(
       child: Container(
         width: animation.value * factor,
         height: animation.value * factor,
         child: FlutterLogo(),
       ),
    );
  }
}

class AnimationWidgetStudy extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => AnimationWidgetState();
}

class AnimationWidgetState extends State with SingleTickerProviderStateMixin {
  AnimationController animationController;
  Animation tween;

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
        duration: Duration(milliseconds: 2000), vsync: this);
    tween = Tween<double>(begin: 0.0, end: 200.0).animate(animationController);
    animationController.forward();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        theme: ThemeData(primaryColor: Colors.white),
        home: Scaffold(
          appBar: AppBar(
              backgroundColor: Colors.white,
              toolbarHeight: 45,
              leading: GestureDetector(
                child: Icon(Icons.arrow_back),
                onTap: () => {Navigator.pop(context)},
              ),
              centerTitle: true,
              elevation: 0,
              title: Text(
                'AnimationWidgetStudy',
                style: TextStyle(color: Colors.black87, fontSize: 18),
              )),
          body: CustomAnimationWidget(animation: tween),
        ));
  }

  @override
  void dispose() {
    //必须在super.dispose()调用,否则可能会有内存泄漏
    animationController.stop();
    animationController.dispose();
    super.dispose();
  }
}
复制代码

5. AnimatedBuilder

1. 基本概念

AnimatedBuilder是构建通用动画的一个组件,是拆分动画的一个工具类,可以将动画渲染和承载动画的widget相分离,在上面使用AnimationWidget构建动画的时候,其实有一个问题,就是每次刷新动画的时候都会重新构建显示图片logo的widget,而更好的做法是将动画渲染和widget相分离,而
AnimatedBuilder就可以实现这样的功能

2. 使用

1. 动画效果

687b4730fe140203537b6fd2f6ed2dc5__mount_node_token=doccn7up292la7s7ufU9bSYLtor&mount_point=doc_image.gif

2. 代码

如下代码所示,写一个AnimatedBuildWidget继承自StatelessWidget,然后在build中返回
AnimatedBuilder实例对象,AnimatedBuilder接收一个Animation类型的参数,这个参数就是要执行的动画,还接收一个child参数,这个就是承载动画的widget,还接收一个builder参数,返回一个widget,然后在State中新建AnimationController和Tween对象,这两个对象绑定后和承载动画的widget一起放入AnimatedBuildWidget的构造参数中即可

class AnimatedBuildWidget extends StatelessWidget {

  Widget child;
  Animation animation;

  AnimatedBuildWidget(this.child, this.animation);

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animation,
      builder: (context, child) {
        return Container(
          width: animation.value,
          height: animation.value,
          child: child,
        );
      },
      child: child,
    );
  }
}

class AnimatedBuildStudy extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => AnimatedBuildState();
}

class AnimatedBuildState extends State with SingleTickerProviderStateMixin {
  AnimationController animationController;
  Animation tween;

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
        duration: Duration(milliseconds: 2000), vsync: this);
    tween = Tween<double>(begin: 0.0, end: 200.0).animate(animationController);
    animationController.forward();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        theme: ThemeData(primaryColor: Colors.white),
        home: Scaffold(
          appBar: AppBar(
              backgroundColor: Colors.white,
              toolbarHeight: 45,
              leading: GestureDetector(
                child: Icon(Icons.arrow_back),
                onTap: () => {Navigator.pop(context)},
              ),
              centerTitle: true,
              elevation: 0,
              title: Text(
                'AnimatedBuildStudy',
                style: TextStyle(color: Colors.black87, fontSize: 18),
              )),
          body: Container(
            child: Center(
              child: AnimatedBuildWidget(FlutterLogo(), tween),
            ),
          ),
        ));
  }
}
复制代码

6. CurvedAnimation

1. 基本概念

使用Tween启动的动画是线性动画,但是动画有时候是一个曲线过程,例如可以先加速后减速,这时候的动画就是一个曲线过程的动画,使用CurvedAnimation就可以开启曲线动画,CurvedAnimation的构造函数如下所示:parent代表AnimationController,curve代表动画正向播放的曲线,reverseCurve代表动画反向播放的曲线

CurvedAnimation({
  required this.parent,
  required this.curve,
  this.reverseCurve,
}) : assert(parent != null),
     assert(curve != null) {
  _updateCurveDirection(parent.status);
  parent.addStatusListener(_updateCurveDirection);
}
复制代码

Curve类型的对象的有一些常量Curves可以直接使用,常用的有如下一些:

  • linear:匀速动画
  • decelerate:匀减速动画
  • ease:先加速后减速
  • easeIn:先快后慢动画
  • easeOut:先慢后快动画
  • easeInOut:先慢,然后加速,最后减速

当然,也可以自定义Curve,如下所示,写一个类继承自Curve,重写transformInter方法,完成内部逻辑即可

class _BounceInOutCurve extends Curve {
  const _BounceInOutCurve._();

  @override
  double transformInternal(double t) {
     ...
  }
}
复制代码

2.使用

1. 动画效果

如下图所示,一个图片放大的过程

56416e7cfdad46f29ddb154d04a3ca95__mount_node_token=doccn7up292la7s7ufU9bSYLtor&mount_point=doc_image.gif

2. 代码

如下代码所示,创建一个AnimationController和CurvedAnimation对象,将CurvedAnimation对象的curve设置为Curves.bounceInOut,即可出现上面放大过程中的反弹的效果

class CurvedAnimationStudy extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => CurvedAnimationState();
}

class CurvedAnimationState extends State with SingleTickerProviderStateMixin {
  AnimationController animationController;
  Animation tween;

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
        duration: Duration(milliseconds: 2000), vsync: this);
    tween =CurvedAnimation(parent: animationController, curve: Curves.bounceInOut);
    animationController.forward();
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        theme: ThemeData(primaryColor: Colors.white),
        home: Scaffold(
          appBar: AppBar(
              backgroundColor: Colors.white,
              toolbarHeight: 45,
              leading: GestureDetector(
                child: Icon(Icons.arrow_back),
                onTap: () => {Navigator.pop(context)},
              ),
              centerTitle: true,
              elevation: 0,
              title: Text(
                'CurvedAnimationStudy',
                style: TextStyle(color: Colors.black87, fontSize: 18),
              )),
          body: Container(
            child: CustomAnimationWidget(
              animation: tween,
              factor: 200,
            ),
          ),
        ));
  }
}
复制代码

7. Hero

1. 基本概念

不同页面的转场动画,在页面切换的时候出现的动画效果,例如在一个列表上面点击图片,跳到详情页,然后出现的图片飞行放大的效果,Hero的构造函数如下:

  • createRectTween:CreateRectTween对象,代表了widget从起始位置“飞到”目标位置的过程中如何变化
  • tag:用于关联发生hero动画的两个widget的标识
  • child:定义承载动画的widget
const Hero({
  Key key,
  @required this.tag,
  this.createRectTween,
  this.flightShuttleBuilder,
  this.placeholderBuilder,
  this.transitionOnUserGestures = false,
  @required this.child,
}) : assert(tag != null),
     assert(transitionOnUserGestures != null),
     assert(child != null),
     super(key: key);
复制代码

2. 使用

1. 动画效果

c7628cb2d50872fd0a73d5891d9bba33__mount_node_token=doccn7up292la7s7ufU9bSYLtor&mount_point=doc_image.gif

2. 代码

class HeroWidget extends StatelessWidget{

  String photo;
  VoidCallback voidCallback;
  double width;

  HeroWidget(this.photo, this.voidCallback, this.width);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
      height: width,
      child: Hero(
        tag: photo,
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            onTap: voidCallback,
            child: Image.network(photo, fit: BoxFit.contain,),
          ),
        ),
      ),
    );
  }
}

class HeroStudy extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    timeDilation = 2.6;
    return MaterialApp(
        theme: ThemeData(primaryColor: Colors.white),
        home: Scaffold(
          appBar: AppBar(
              backgroundColor: Colors.white,
              toolbarHeight: 45,
              leading: GestureDetector(
                child: Icon(Icons.arrow_back),
                onTap: () => {Navigator.pop(context)},
              ),
              centerTitle: true,
              elevation: 0,
              title: Text(
                'AnimationWidgetStudy',
                style: TextStyle(color: Colors.black87, fontSize: 18),
              )),
          body: Container(
            child: HeroWidget(
                "http://pic21.photophoto.cn/20111011/0006019003288114_b.jpg",
                () {
              Navigator.of(context).push(MaterialPageRoute(builder: (context) {
                return MaterialApp(
                    theme: ThemeData(primaryColor: Colors.white),
                    home: Scaffold(
                      appBar: AppBar(
                          backgroundColor: Colors.white,
                          toolbarHeight: 45,
                          leading: GestureDetector(
                            child: Icon(Icons.arrow_back),
                            onTap: () => {Navigator.pop(context)},
                          ),
                          centerTitle: true,
                          elevation: 0,
                          title: Text(
                            'AnimationWidgetStudy',
                            style:
                                TextStyle(color: Colors.black87, fontSize: 18),
                          )),
                      body: Center(
                        child: HeroWidget("http://pic21.photophoto.cn/20111011/0006019003288114_b.jpg",
                            () {
                          Navigator.of(context).pop();
                        }, 500),
                      ),
                    ));
              }));
            }, 100),
          ),
        ));
  }
}
复制代码

8. QQ交流群

770892444

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享