Flutter 动画 Animation、AnimationController、AnimatedWidget、AnimatedBuilder示例

动画

Animation

Animation是一个抽象类,它由一个值(类型为T)和一个状态组成。 状态指示动画在概念上是从开始到结束或从结束到开始,尽管动画的实际值可能不会单调更改(例如,如果动画使用反弹的曲线bounces curve)。

查看源码,可以在里面看到一些方法,如相关的监听 Listener,获取动画值的方法value
在这里插入图片描述

Animation的使用需要配合AnimationController,所以我们先介绍完AnimationController再实现示例。

AnimationController

动画控制器,它包含动画的启动forward()、停止stop() 、反向播放 reverse()等方法。AnimationController需要一个TickerProvider,它使用构造函数上的vsync参数配置。TickerProvider接口是描述Ticker对象的工厂。Ticker是一个对象,它知道如何向SchedulerBinding注册,并在每一帧触发回调。 AnimationController类使用Ticker逐步控制动画,而 AnimationController中的必要参数TickerProvider vsync,可以使用TickerProvider 和 SingleTickerProviderStateMixin,但通常我们会将SingleTickerProviderStateMixin(效率稍高一些)添加到State的定义中,然后将State对象作为vsync的值,详情可查看下面的示例。

示例

实现居中图片重复执行的线性动画。

在这里插入图片描述

实现上面动图中的效果只需要以下代码即可完成:


class _AnimationTestPageState extends State<AnimationTestPage>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation animation;

  @override
  void initState() {
    super.initState();

    // 创建 AnimationController 对象
    controller = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 1000));

    // 创建线性变化的 Animation 对象
    animation = Tween(begin: 10.0, end: 100.0).animate(controller)
      ..addListener(() {
        setState(() {}); // 刷新界面,必须要有,否则动画动不了
      });

    //让动画重复执行
    controller.repeat(reverse: true);
  }

  @override
  void dispose() {
    controller.dispose(); // 释放资源
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("AnimationTest"),
        ),
        body: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              Center(
                  child: Container(
                    width: animation.value,// 图片宽高使用动画的值
                    height: animation.value,
                    child: Image.network(
                        'https://hbimg.huabanimg.com/fced2db29a9354db4747822b819b247d88adbb9be837-bB3TR0_fw658'),
                  )),
             )
            ],
          ),
        ));
  }
}
AnimatedWidget

AnimatedWidget 会将 Animation 的状态与其子 widget 的视觉样式绑定。省去了状态监听和 UI 刷新的工作。

也就是使用 AnimatedWidget 来实现动画,就可以不用写下面这段代码了。

  ..addListener(() {
        setState(() {}); // 刷新界面,必须要有,否则动画动不了
      });
示例

使用过程也比较简单,首先创建一个widget继承AnimatedWidget,在widget中传递动画的值及动画的在哪个child上显示

/// 使用 AnimatedWidget 创建动画
class AnimatedFlutterLogo extends AnimatedWidget {
  // AnimatedWidget 需要在初始化时传入animation对象,所以构造函数不能少
  AnimatedFlutterLogo({Key key, Animation<double> animation}) : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    // 取出动画对象
    final Animation<double> animation = listenable;
    return Center(
        child: Container(
      height: animation.value, //根据动画对象的当前状态更新宽高
      width: animation.value,
      child: FlutterLogo()));// FlutterLogo 是系统系统的 widget
  }
}

创建好widget之后,在创建animation的时候就不用监听动画和刷新动画了
代码如下:

class _AnimationTestPageState extends State<AnimationTestPage>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation animation;

  @override
  void initState() {
    super.initState();

    // 创建 AnimationController 对象
    controller = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 1000));

    // 创建线性变化的 Animation 对象
    animation = Tween(begin: 10.0, end: 100.0).animate(controller);

    //让动画重复执行
    controller.repeat(reverse: true);
  }

  @override
  void dispose() {
    controller.dispose(); // 释放资源
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("AnimationTest"),
        ),
        body: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              Center(
                  child: AnimatedFlutterLogo(
                      animation: animation) // 初始化 AnimatedWidget 时传入animation对象
              ),
            ],
          ),
        ));
  }
}

AnimatedWidget中可以看出widget动画的控制和widget本身是存在耦合的。
在这里插入图片描述

能不能把图中的耦合去除呢?
Flutter中提供了AnimatedBuilder来实现动画,使用AnimatedBuilder实现动画可以将widget和动画的控制解耦。

AnimatedBuilder

使用AnimatedBuilder实现动画可以将widget和动画的控制解耦。

示例

我们可以创建一个widget封装AnimatedBuilder,参数通过构造参数方法传入。

class AnimatedFlutterLogoBuilder extends StatelessWidget{
  final Widget child;
  final AnimationController controller;
  final Animation<double> animation;

  AnimatedFlutterLogoBuilder({this.child,this.animation});

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: animation, // 传入动画对象
        child: child,
        // 动画构建回调
        builder: (context, child) =>  Container(
            width: animation.value, //使用动画的当前状态更新UI
            height: animation.value,
            child: child, // 即 AnimatedBuilder 中的 child
          ),);
  }
}

有了widget之后,直接调用并传入相关参数即可:

Center(
    child: AnimatedFlutterLogoBuilder(
      child: Image.network(
          'https://hbimg.huabanimg.com/fced2db29a9354db4747822b819b247d88adbb9be837-bB3TR0_fw658'),
      animation: animation,
))

AnimationWidget 和 AnimationBuilder 的区别

可能从上面的示例无法看出二者的区别,那么我们通过实现以下的效果来区分二者的差别

效果图:第二张图片增加了透明度动画而第一张图片没有加。
在这里插入图片描述

AnimationWidget 方式实现

上面的第二个效果图,如果用AnimationWidget来实现需要定义两个AnimationWidget,分别是:


/// 使用 AnimatedWidget 创建动画,传入 Animation
class AnimatedFlutterLogo extends AnimatedWidget {
  // AnimatedWidget 需要在初始化时传入animation对象,所以构造函数不能少
  AnimatedFlutterLogo({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    // 取出动画对象
    final Animation<double> animation = listenable;
    return Center(
      child: Container(
        height: animation.value, //根据动画对象的当前状态更新宽高
        width: animation.value,
        child: Image.network(
            'https://hbimg.huabanimg.com/fced2db29a9354db4747822b819b247d88adbb9be837-bB3TR0_fw658'),
      ),
    );
  }
}

/// 使用 AnimatedWidget 创建动画,传入 AnimationController 
class AnimatedFadeFlutterLogo extends AnimatedWidget {
  // AnimatedWidget 需要在初始化时传入animation对象,所以构造函数不能少
  AnimatedFadeFlutterLogo({Key key, AnimationController controller})
      : super(key: key, listenable: controller);

  Widget build(BuildContext context) {
    // 取出动画对象
    final AnimationController controller = listenable;
    // 创建线性变化的 Animation 对象
    var animation = Tween(begin: 10.0, end: 100.0).animate(controller);// 通过 controller 生成 animation
    // 透明度动画
    return Center(
      child: FadeTransition(
        opacity: controller,
        child: AnimatedFlutterLogo(animation: animation,),// 使用第一个 AnimatedWidget,并传入 animation
      ),
    );
  }
}

定义好两个widget之后,就可以直接使用AnimatedFadeFlutterLogo这个类做child了,使用如下:

Center(
 child: AnimatedFadeFlutterLogo(
     controller: controller) // 初始化 AnimatedWidget 时传入animation对象
 ),
AnimationBuilder 方式实现

还是定义widget,并在构造方法传入需要的参数:

class AnimatedFlutterLogoBuilder extends StatelessWidget{
  final Widget child;
  final AnimationController controller;
  final Animation<double> animation;

  AnimatedFlutterLogoBuilder({this.child,this.controller,this.animation});

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: animation, // 传入动画对象
        child: child,
        // 动画构建回调
        builder: (context, child) => FadeTransition(// 透明度动画
          opacity: controller,
          child: Container(
            width: animation.value, //使用动画的当前状态更新UI
            height: animation.value,
            child: child, // 即 AnimatedBuilder 中的 child
          ),
        ));
  }
}

使用定义好的widget

Center(
  child: AnimatedFlutterLogoBuilder(
    child: Image.network(
        'https://hbimg.huabanimg.com/fced2db29a9354db4747822b819b247d88adbb9be837-bB3TR0_fw658'),
    controller: controller,
    animation: animation,
  ))
总结

由于继承自AnimationWidget的类构造方法每次只能传递两个参数,这就导致当需要传递多个参数时比较麻烦。而使用AnimatedBuilder则不会受参数个数的限制。而且使用AnimatedBuilder相当于是将动画的实现封装出去,而使用的时候只管传递child widget和相关参数即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_龙衣

赏杯快乐水喝喝

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值