【Flutter -- 进阶】动画

在这里插入图片描述
Flutter 动画库的核心类是 Animation 对象,它生成指导动画的值,Animation 对象指导动画的当前状态(例如,是开始、停止还是向前或者向后移动),但它不知道屏幕上显示的内容。动画类型分为两类:

  • 补简动画(Tween),定义了开始点和结束点、时间线以及定义转换时间和速度的曲线。然后由框架计算如何从开始点过渡到结束点。Tween是一个无状态(stateless)对象,需要begin和end值。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0,但这不是必须的。
  • 基于物理动画,运动被模拟与真实世界行为相似,例如,当你掷球时,它何处落地,取决于抛球速度有多快、球有多重、距离地面有多远。类似地,将连接在弹簧上的球落下(并弹起)与连接到绳子的球放下的方式也是不同。

在Flutter中的动画系统基于Animation对象的。widget可以在build函数中读取Animation对象的当前值,并且可以监听动画的状态改变。

1. 动画示例

1. 代码

import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';


void main() {
  //运行程序
  runApp(LogoApp());
}

class LogoApp extends StatefulWidget{
  @override
  State<StatefulWidget> createState(){
    return new _LogoAppState();
  }

}

//logo
Widget ImageLogo = new Image(
    image: new AssetImage('images/logo.jpg'),
);

//with 是dart的关键字,混入的意思,将一个或者多个类的功能天骄到自己的类无需继承这些类
//避免多重继承问题
//SingleTickerProviderStateMixin 初始化 animation 和 Controller的时候需要一个TickerProvider类型的参数Vsync
//所依混入TickerProvider的子类
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{
  //动画的状态,如动画开启,停止,前进,后退等
  Animation<double> animation;
  //管理者animation对象
  AnimationController controller;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //创建AnimationController
    //需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
    //译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
    controller = new AnimationController(
        //时间是3000毫秒
        duration: const Duration(
            milliseconds: 3000
        ),
        //vsync 在此处忽略不必要的情况
        vsync: this,
    );
    //补间动画
    animation = new Tween(
      //开始的值是0
      begin: 0.0,
      //结束的值是200
      end : 200.0,
    ).animate(controller)//添加监听器
      ..addListener((){
        //动画值在发生变化时就会调用
        setState(() {

        });
      });
    //只显示动画一次
    controller.forward();
  }
  @override
  Widget build(BuildContext context){
    return new MaterialApp(
      theme: ThemeData(
          primarySwatch: Colors.red

      ),
      home: new Scaffold(
        appBar: new AppBar(
          title: Text("动画demo"),
        ),
        body:new Center(
          child: new Container(
            //宽和高都是根据animation的值来变化
            height: animation.value,
            width: animation.value,
            child: ImageLogo,
          ),
        ),
      ),
    );
  }


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

上面实现了图像在3000毫秒间从宽高是0变化到宽高是200,主要分为六部

  • 混入SingleTickerProviderStateMixin,为了传入vsync对象
  • 初始化AnimationController对象
  • 初始化Animation对象,并关联AnimationController对象
  • 调用AnimationControllerforward开启动画
  • widget根据Animationvalue值来设置宽高
  • widgetdispose()方法中调用释放资源

2. 效果图

在这里插入图片描述

3. AnimatedWidget 简化

使用AnimatedWidget对动画进行简化,使用AnimatedWidget创建一个可重用动画的widget,而不是用addListener()和setState()来给widget添加动画。AnimatedWidget类允许从setState()调用中的动画代码中分离出widget代码。AnimatedWidget不需要维护一个State对象了来保存动画。

import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';


void main() {
  //运行程序
  runApp(LogoApp());
}

class LogoApp extends StatefulWidget{
  @override
  State<StatefulWidget> createState(){
    return new _LogoAppState();
  }

}

//logo
Widget ImageLogo = new Image(
    image: new AssetImage('images/logo.jpg'),
);


//抽象出来
class AnimatedLogo extends AnimatedWidget{
  AnimatedLogo({Key key,Animation<double> animation})
     :super(key:key,listenable:animation);


  @override
  Widget build(BuildContext context){
    final Animation<double> animation = listenable;
    return new MaterialApp(
      theme: ThemeData(
          primarySwatch: Colors.red

      ),
      home: new Scaffold(
        appBar: new AppBar(
          title: Text("动画demo"),
        ),
        body:new Center(
          child: new Container(
            //宽和高都是根据animation的值来变化
            height: animation.value,
            width: animation.value,
            child: ImageLogo,
          ),
        ),
      ),
    );

  }
}

//with 是dart的关键字,混入的意思,将一个或者多个类的功能添加到自己的类无需继承这些类
//避免多重继承问题
//SingleTickerProviderStateMixin 初始化 animation 和 Controller的时候需要一个TickerProvider类型的参数Vsync
//所依混入TickerProvider的子类
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{
  //动画的状态,如动画开启,停止,前进,后退等
  Animation<double> animation;
  //管理者animation对象
  AnimationController controller;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //创建AnimationController
    //需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
    //译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
    controller = new AnimationController(
        //时间是3000毫秒
        duration: const Duration(
            milliseconds: 3000
        ),
        //vsync 在此处忽略不必要的情况
        vsync: this,
    );
    //补间动画
    animation = new Tween(
      //开始的值是0
      begin: 0.0,
      //结束的值是200
      end : 200.0,
    ).animate(controller);//添加监听器
    //只显示动画一次
    controller.forward();
  }
  
  @override
  Widget build(BuildContext context){
      return AnimatedLogo(animation: animation);
  }


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

}

可以发现 AnimatedWidget 中会自动调用addListenersetState()_LogoAppStateAnimation对象传递给基类并用animation.value设置Image宽高。

4. 监视动画

在平时开发,我们知道,很多时候都需要监听动画的状态,好像完成、前进、倒退等。在Flutter中可以通过addStatusListener()来得到这个通知,以下代码添加了动画状态

    //补间动画
    animation = new Tween(
      //开始的值是0
      begin: 0.0,
      //结束的值是200
      end : 200.0,
    ).animate(controller)
    //添加动画状态
    ..addStatusListener((state){
      return print('$state');
    });//添加监听器

输出下面结果:

I/flutter (16745): AnimationStatus.forward //动画开始
Syncing files to device KNT AL10...
I/zygote64(16745): Do partial code cache collection, code=30KB, data=25KB
I/zygote64(16745): After code cache collection, code=30KB, data=25KB
I/zygote64(16745): Increasing code cache capacity to 128KB
I/flutter (16745): AnimationStatus.completed//动画完成

下面那就运用addStatusListener()在开始或结束反转动画。那就产生循环效果:

    //补间动画
    animation = new Tween(
      //开始的值是0
      begin: 0.0,
      //结束的值是200
      end : 200.0,
    ).animate(controller)
    //添加动画状态
    ..addStatusListener((state){
      //如果动画完成了
      if(state == AnimationStatus.completed){
        //开始反向这动画
        controller.reverse();
      } else if(state == AnimationStatus.dismissed){
        //开始向前运行着动画
        controller.forward();
      }

    });//添加监听器

效果如下:
在这里插入图片描述

5. 用 AnimatedBuilder 重构

上面的代码存在一个问题:更改动画需要更改显示 Image 的 widget ,更好的解决方案是将职责分离:

  • 显示图像
  • 定义Animation对象
  • 渲染过渡效果 这时候可以借助AnimatedBuilder类完成此分离。AnimatedBuilder是渲染树中的一个独立的类,与AnimatedWidget类似,AnimatedBuilder自动监听来自Animation对象的通知,并根据需要将该控件树标记为脏(dirty),因此不需要手动调用addListener()
//AnimatedBuilder
class GrowTransition extends StatelessWidget{
  final Widget child;
  final Animation<double> animation;
  GrowTransition({this.child,this.animation});

  @override
  Widget build(BuildContext context){
    return new MaterialApp(
      theme: ThemeData(
          primarySwatch: Colors.red

      ),
      home: new Scaffold(
        appBar: new AppBar(
          title: Text("动画demo"),
        ),
        body:new Center(
            child: new AnimatedBuilder(
                animation: animation,
                builder: (BuildContext context,Widget child){
                  return new Container(
                    //宽和高都是根据animation的值来变化
                    height: animation.value,
                    width: animation.value,
                    child: child,
                  );
                },
              child: child,
            ),

        ),
      ),
    );

  }
  class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{
  //动画的状态,如动画开启,停止,前进,后退等
  Animation animation;
  //管理者animation对象
  AnimationController controller;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //创建AnimationController
    //需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
    //译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
    controller = new AnimationController(
        //时间是3000毫秒
        duration: const Duration(
            milliseconds: 3000
        ),
        //vsync 在此处忽略不必要的情况
        vsync: this,
    );
    final CurvedAnimation curve  = new CurvedAnimation(parent: controller, curve: Curves.easeIn);
    //补间动画
    animation = new Tween(
      //开始的值是0
      begin: 0.0,
      //结束的值是200
      end : 200.0,
    ).animate(curve)
//    //添加动画状态
    ..addStatusListener((state){
      //如果动画完成了
      if(state == AnimationStatus.completed){
        //开始反向这动画
        controller.reverse();
      } else if(state == AnimationStatus.dismissed){
        //开始向前运行着动画
        controller.forward();
      }

    });//添加监听器
    //只显示动画一次
    controller.forward();
  }

  @override
  Widget build(BuildContext context){
      //return AnimatedLogo(animation: animation);
        return new GrowTransition(child:ImageLogo,animation: animation);
  }


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

}

上面代码有一个迷惑的问题是,child看起来好像是指定了两次,但实际发生的事情是,将外部引用的child传递给AnimatedBuilderAnimatedBuilder将其传递给匿名构造器,然后将该对象用作其子对象。最终的结果是AnimatedBuilder插入到渲染树中的两个Widget之间。最后,在initState()方法创建一个AnimationController和一个Tween,然后通过animate()绑定,在build方法中,返回带有一个Image为子对象的GrowTransition对象和一个用于驱动过渡的动画对象。如果只是想把可复用的动画定义成一个widget,那就用AnimatedWidget

6. 并行动画

很多时候,一个动画需要两种或者两种以上的动画,在Flutter也是可以实现的,每一个Tween管理动画的一种效果,如:

    final AnimationController controller =
    new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
    final Animation<double> sizeAnimation =
    new Tween(begin: 0.0, end: 300.0).animate(controller);
    final Animation<double> opacityAnimation =
    new Tween(begin: 0.1, end: 1.0).animate(controller);

可以通过sizeAnimation.Value来获取大小,通过opacityAnimation.value来获取不透明度,但AnimatedWidget的构造函数只能接受一个动画对象,解决这个问题,需要动画的widget创建了自己的Tween对象,上代码:

//AnimatedBuilder
class GrowTransition extends StatelessWidget {
  final Widget child;
  final Animation<double> animation;

  GrowTransition({this.child, this.animation});
  static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
  static final _sizeTween = new Tween<double>(begin: 0.0, end: 200.0);

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: ThemeData(primarySwatch: Colors.red),
      home: new Scaffold(
        appBar: new AppBar(
          title: Text("动画demo"),
        ),
        body: new Center(
          child: new AnimatedBuilder(
            animation: animation,
            builder: (BuildContext context, Widget child) {
              return new Opacity(
                  opacity: _opacityTween.evaluate(animation),
                child: new Container(
                //宽和高都是根据animation的值来变化
                height: _sizeTween.evaluate(animation),
                width: _sizeTween.evaluate(animation),
                child: child,
              ),
              );

            },
            child: child,
          ),
        ),
      ),
    );
  }
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  //动画的状态,如动画开启,停止,前进,后退等
  Animation<double> animation;

  //管理者animation对象
  AnimationController controller;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //创建AnimationController
    //需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
    //译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
    controller = new AnimationController(
      //时间是3000毫秒
      duration: const Duration(milliseconds: 3000),
      //vsync 在此处忽略不必要的情况
      vsync: this,
    );
    //新增
    animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn)
      ..addStatusListener((state) {
        //如果动画完成了
        if (state == AnimationStatus.completed) {
          //开始反向这动画
          controller.reverse();
        } else if (state == AnimationStatus.dismissed) {
          //开始向前运行着动画
          controller.forward();
        }
      }); //添加监听器
    //只显示动画一次
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
     return new GrowTransition(child:ImageLogo,animation: animation);
  }

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

可以看到在GrowTransition定义两个Tween动画,并且加了不透明Opacitywidget,最后在initState方法中修改增加一句animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn),最后的动画效果:

在这里插入图片描述

2. 自定义动画

1. 效果图

在这里插入图片描述

2. 自定义小球

class _bollView extends CustomPainter{
  //颜色
  Color color;
  //数量
  int count;
  //集合放动画
  List<Animation<double>> ListAnimators;
  _bollView({this.color,this.count,this.ListAnimators});
  @override
  void paint(Canvas canvas,Size size){
     //绘制流程
     double boll_radius = (size.width - 15) / 8;
     Paint paint = new Paint();
     paint.color = color;
     paint.style = PaintingStyle.fill;
     //因为这个wiaget是80 球和球之间相隔5
     for(int i = 0; i < count;i++){
       double value = ListAnimators[i].value;
       //确定圆心 半径 画笔
       //第一个球 r
       //第二个球 5 + 3r
       //第三个球 15 + 5r
       //第四个球 30 + 7r
       //半径也是随着动画值改变
       canvas.drawCircle(new Offset((i+1) * boll_radius + i * boll_radius  + i * 5,size.height / 2), boll_radius * (value > 1 ? (2 - value) : value), paint);
     }
  }

  //刷新是否重绘
  @override
  bool shouldRepaint(CustomPainter oldDelegate){
    return oldDelegate != this;

  }
}

3. 配置小球属性

class MyBalls extends StatefulWidget{
  Size size;
  Color color;
  int count;
  int seconds;

  //默认四个小球 红色
  MyBalls({this.size,this.seconds : 400,this.color :Colors.redAccent,this.count : 4});

  @override
  State<StatefulWidget> createState(){
    return MyBallsState();
  }

}

4. 创建动画

//继承TickerProviderStateMixin,提供Ticker对象
class MyBallsState extends State<MyBalls> with TickerProviderStateMixin {
  //动画集合
  List<Animation<double>>animatios = [];
  //控制器集合
  List<AnimationController> animationControllers = [];
  //颜色
  Animation<Color> colors;

  @override
  void initState(){
    super.initState();
    for(int i = 0;i < widget.count;i++){
         //创建动画控制器
         AnimationController animationController = new AnimationController(
             vsync: this,
             duration: Duration(
               milliseconds: widget.count * widget.seconds
             ));
         //添加到控制器集合
         animationControllers.add(animationController);
         //颜色随机
         colors = ColorTween(begin: Colors.red,end:Colors.green).animate(animationController);
         //创建动画 每个动画都要绑定控制器
         Animation<double> animation = new Tween(begin: 0.1,end:1.9).animate(animationController);
         animatios.add(animation);
    }
    animatios[0].addListener((){
      //刷新
      setState(() {

      });
    });

    //延迟执行
    var delay = (widget.seconds ~/ (2 * animatios.length - 2));
    for(int i = 0;i < animatios.length;i++){
     Future.delayed(Duration(milliseconds: delay * i),(){
        animationControllers[i]
            ..repeat().orCancel;
      });
    }
  }
  @override
  Widget build(BuildContext context){
    return new CustomPaint(
      //自定义画笔
      painter: _bollView(color: colors.value,count: widget.count,ListAnimators : animatios),
      size: widget.size,
    );
  }
  //释放资源
  @override
  void dispose(){
    super.dispose();
    animatios[0].removeListener((){
      setState(() {

      });
    });
    animationControllers[0].dispose();
  }
}

5. 调用

class Ball extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Animation demo'),
        ),
        body: Center(
            child: MyBalls(size: new Size(80.0,20.0)),
        ),
      ),
    );
  }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Kevin-Dev

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值