Flutter笔记 - 基本动画

595 阅读5分钟

Flutter中的动画类型

 补间动画(tween):在补间动画中,定义了开始点和结束点、时间线以及定义转换时间和速度的曲线,然后由框架计算如何从开始点过渡到结束点。
 基于物理的动画:在基于物理的动画中,运动被模拟为与真实世界的行为相似。例如 当你掷球时,他在何处落地,取决于抛球的速度,球的重量。距离地面的距离,

动画相关的类

 Animation:是flutter动画库中的一个核心类,它生成指导动画的值
 CurvedAnimation:Animation的一个子类,将过程抽象为一个非线性曲线
 AnimationController:Animation的子类,用来管理Animation
 Tween:在正在执行动画的对象所使用的数据范围之间生成值,例如从颜色值 0到255

Animation

 flutter中的animation对象**是一个在一段时间内依次生成一个区间之间的值的类** ,animation对象的输出可以是线性、曲线、一个步进函数或者任何其他可以设计的映射。根据animation对象的控制方式,动画可以方向运行,甚至可以在中间切换方法
 -Animation还可以生成出了double之外的其他类型值,如 Animation<Color> ,Animation(Size)
 -Animation对象是有状态的,可以通过访问value属性获取动画的当前值
 -Animation对象本身和UI渲染无关

#CurvedAnimation

 CurvedAnimation将动画过程定义为一个非线性曲线
 final CurvedAnimation curve = CurvedAnimation(parent:controller,curve:Curves.easeIn);
 
 自定义曲线 ,继承 Curve 重写 transform 方法
 ```dart
 class ShakeCure extends Curve{
     @override
     double transform(double t){
         return math.sin(t * math.PI *2 );
     }
     
 }

AnimationController

 AnimationController是一个特殊的Animation对象,在屏幕刷新的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内会线性的生成从0.0到1.0的数字,例如:
 final AnimationController controller = AnimationController(duration:Duration(milliseconds:2000),vsync:this)
 此时动画就会在2000毫秒内,从0.0的值过渡到1.0
 
 AnimationController继承自Animation<double>,因此可以在需要Animation对象的任何地方使用,但是AnimationController具有控制动画的其他方式
 - forward() : 启动动画
 - reverse({double from}):倒放动画
 - reset() : 重置动画,
 - stop({bool canceled = true}) : 定制动画
 
 vsync参数是为了防止屏幕外动画消耗不必要的资源,可以将stateful 对象作为vsync的值
 
 *在某些情况下,值(position,值动画的当前值)可能会超出AnimationController的0.0-1.0 范围,例如 fling()函数允许提供速度(velocity)、力量(force)、position(通过force对象)。位置(position)可以是任何东西,因此可以在范围之外,CuredAnimation生成的值也可以超过这个范围。根据选择的曲线,CuredAnimation的输出可以具有比喻输入更大的范围。例如 Curves.elasticln等弹性曲线会生成大于或小于默认范围的值*

Tween

 默认情况下AnimationController对象额范围从0.0-1.0,如果需要不同的范围或者不同的数据类型,就可以使用Tween来配置动画
 final Tween doubleTween = Tween<double>(begin:-100.0,end:0.0);
 
 Tween是一个无状态的对象,需要begin和end值。Tween的唯一职责就是定义从输入返回到输出返回的映射。输入返回通常为0.0到1.0,但是不是必须的
 Tween继承自Animatable<T> ,而不是继承自Animation<T>。Animatable与Animation相似,不是必须有输出的double值,例如 ColorTween知道两种颜色之间过渡
 Tween colorTween = Tween(begin:Colors.transparent,end:Colors.black54)
 
 以下构建一个控制器,一条曲线和一个tween:
 
  AnimationController controller = AnimationController(duration:Duration(milliseconds:500),vsync:this)
 Animation curve = CurvedAnimation(parent:controller,curve:Curves.easeOut);
 Animation<int> alpha = IntTween(begin:0,end:255).animate(curve)

动画的监听

 addListener() : 动画的值发生变化时会被调用
 addStatusListener(AnimationStatus status):动画状态发生变化时被调用
class AnimationWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return AnimationState();
  }
}

class AnimationState extends State<AnimationWidget>
    with SingleTickerProviderStateMixin {
  Animation<double> animation;
  AnimationController controller;
  AnimationStatus animationStatus;
  double animationValue;

  @override
  void initState() {
    super.initState();
    //初始化动画
    controller =
        AnimationController(duration: Duration(seconds: 2), vsync: this);
    animation = Tween<double>(begin: 0, end: 300).animate(controller)
      //监听动画的执行过程
      ..addListener(() => setState(() => animationValue = animation.value))
      //监听动画的执行状态
      ..addStatusListener((status) => setState(() => animationStatus = status));
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(top: 150),
      child: Column(
        children: <Widget>[
          //点击时播放动画
          GestureDetector(
            onTap: () {
              //重置动画
              controller.reset();
              //再次播放动画
              controller.forward();
            },
            child: FlatButton(
              child: Text("Start Animation"),
            ),
          ),
          //显示动画执行状态
          Text("animationState = $animationStatus"),
          //显示动画执行中数值的变化
          Text("animationValue = $animationValue"),
          Container(
            height: animationValue,
            width: animationValue,
            child: FlutterLogo(),
          )
        ],
      ),
    );
  }
}

以上代码执行一个简单的动画,通过动画改变logo的宽高
9xhmw-acjtk.gif
![](https://user-gold-cdn.xitu.io/2019/12/10/16eefea4ca6832e6?w=384&h=846&f=png&s=44293)

AnimationWidget

 AnimatedWidget 可以理解为Animation的助手,可以简化对动画的使用
//创建一个继承自AnimatedWidget的子类, 
class LogoAnimationWidget extends AnimatedWidget {
  //重写构造函数,从外部创建一个动画进来
  LogoAnimationWidget({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);
  
  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = this.listenable;
    return Container(
      width: animation.value,
      height: animation.value,
      child: FlutterLogo(),
    );
  }
  
   @override
   void _handleChange() {
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }
}

//使用时就像不同widget那样,
@override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(top: 150),
      child: Column(
        children: <Widget>[
          //点击时播放动画
          FlatButton(
            child: Text("Start Animation"),
            onPressed: () {
              //重置动画
              controller.reset();
              //再次播放动画
              controller.forward();
            },
          ),
          LogoAnimationWidget(
            animation: animation,
          )
        ],
      ),
    );
  }

AnimatedBuilder

 是用于构建动画的通用widget,将动画和widget进行分离。
 在上面的实例中存在的一个问题:更改动画需要更改显示logo的widget。解决方案就是将职责分离
 - 显示logo
 - 定义 animation对象
 - 渲染过渡效果
class LogoWidget extends StatelessWidget {

  const LogoWidget();

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 10),
      child: FlutterLogo(),
    );
  }

}

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

  GrowTransition({this.animation, this.child = const LogoWidget()});

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

Hero动画

 两个页面之间的过渡动画,个人感觉就是元素共享动画
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;

class HeroWidget extends StatelessWidget {
  final String photo;
  final VoidCallback onTap;
  final double width;
  final double height;

  HeroWidget({this.photo, this.onTap, this.width, this.height});

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

class HeroAnimation extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //设置动画速度
    timeDilation = 3.0;
    return Scaffold(
      appBar: AppBar(
        title: Text("HeroAnimation"),
      ),
      body: Container(
        alignment: Alignment.center,
        child: HeroWidget(
          photo:
              "https://c-ssl.duitang.com/uploads/item/201501/18/20150118135140_84XVt.thumb.700_0.jpeg",
          width: 300,
          height: 300,
          onTap: () {
            Navigator.of(context).push(MaterialPageRoute(builder: (context) {
              return Scaffold(
                appBar: AppBar(
                  title: Text("过渡page"),
                ),
                body: Container(
                  color: Colors.lightBlueAccent,
                  padding: EdgeInsets.all(20),
                  alignment: Alignment.center,
                  child: HeroWidget(
                    photo:
                        "https://c-ssl.duitang.com/uploads/item/201501/18/20150118135140_84XVt.thumb.700_0.jpeg",
                    width: 100,
                    height: 100,
                    onTap: () => Navigator.pop(context),
                  ),
                ),
              );
            }));
          },
        ),
      ),
    );
  }
}