Flutter学习5-Flutter中动画

Flutter动画分类

flutter动画 可以分为补间动画和基于物理动画

  • 补间动画 :在补间动画中,定义了开始点和结束点,时间线和定义转换的时间和速度曲线,然后由框架计算出如何从开始点到结束点
  • 基于物理动画: 在基于物理动画中,运动模拟为与真实世界的行为相似,例如:球体落地过程中其速度,重力都会影响其执行

Flutter中动画的基础类

  • Animation:是flutter动画库中的核心类,它生成知道动画的值
  • CurvedAnimation:Animation的子类,将动画抽象成为一个非线性的曲线
  • AnimationController:Animation的子类,用来管理Animation
  • Tween:在正在执行的动画对象所使用的数值范围之间生成值.

Animation

在Flutter中,animation对象本身和UI渲染没有任何关系,Animation是个抽象类,它拥有动画的当前值和状态(进行中,停止等),其中比较常用的Animation类是Animation< double>. Flutter中Animation对象是一个在一段时间内依次生成一个区间值的类,Animation对象输出可以是线性的,曲线的,一个步进函数或者在任何可以设计的映射.根据Animation对象的控制方式,动画可以反向进行,甚至可以在中间切换方向.

  • Animation还可以生成除了double以外的其他类型值,例如:Animation< Color>,Animation< Size>等
  • Animation对象有状态,可以通过访问其value属性,获取动画的当前值;
  • Animation对象本身和UI渲染没有任何关系;

CurvedAnimation

CurvedAnimation将动画过程定义为一个非线性曲线(跟Android动画中差值器似得)

 CurvedAnimation animation=CurvedAnimation(curve: Curves.linear, parent: controller);
复制代码

Curves是系统内置的一些差值器,也可以自定义

class SelfCurve extends Curve{
 @override
 double transform(double t) {
   return sin(t * pi * 2);
 }
}
复制代码

AnimationController

  • AnimationController 是一个特殊的Animation对象,在屏幕刷新的每一帧,就会生成一个新的值,默认情况下,AnimationController在给定的时间内会线性的生成从0.0~1.0的数字
  • AnimationController派生自Animation< double> ,因此可以在需要Animation对象的任何地方使用,
  • 当创建一个AnimationController对象时需要传一个vsync参数,该值可以在动画不需要显示时及时释放,例如退到后台,跳转到另一个页面等
//需要state 扩展 with SingleTickerProviderStateMixin
  AnimationController controller=AnimationController(duration: 
  Duration(milliseconds: 200),vsync: this);
   controller.forward();//启动动画
   controller.stop();//停止动画
   controller.reset();//重置动画
   controller.reverse();//倒放动画
复制代码

Tween

在默认情况下,AnimationController的值是从0.0~1.0,如果需要改变该值的范围则需要使用Tween

  • Tween是一个无状态(stateless)对象,需要begin和end值。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0,但这不是必须的
  • Tween继承自Animatable,而不是继承自Animation。Animatable与Animation相似,不是必须输出double值。例如,ColorTween指定两种颜色之间的过渡。
  • Tween对象不存储任何状态。相反,它提供了evaluate(Animation animation)方法将映射函数应用于动画当前值。 Animation对象的当前值可以通过value()方法取到。evaluate函数还执行一些其它处理,例如分别确保在动画值为0.0和1.0时返回开始和结束状态。 建了一个控制器、一条曲线和一个Tween:
final AnimationController controller = new AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
    new CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(curve);
复制代码

综上所述,使用Animation时,先定义AnimationController,如果需要的动画不是线性的,则需要为其设置CurvedAnimation作为差值器,如果值不是从0.0~1.0则需要设置Tween为其设置值的变化范围

为widget添加动画

使用AnimationController构建动画

class AnimationPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return AnimationState();
  }
}

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

  @override
  void initState() {
    super.initState();
    //创建动画
    controller = AnimationController(
        duration: Duration(milliseconds: 2000), vsync: this);
        //设置值的区间
    animation = Tween<double>(begin: 0, end: 300).animate(controller)
      ..addListener(() {
      //每次动画的值改变时回调该方法
        setState(() {
          animationValue = animation.value;
        });
      })
      ..addStatusListener((status) {
      //动画状态改变时回调该方法
        setState(() {
          animationState = status;
        });
      });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter动画'),
      ),
      body: Center(
          child: Column(
        children: [
          RaisedButton(
            onPressed: () {
              controller.reset();
              controller.forward();
            },
            child: Text('点击开始动画'),
          ),
          Text("$animationValue"),
          Text("$animationState"),
          Container(
            height: animation.value,
            width: animation.value,
            child: FlutterLogo(),
          )

        ],
      )),
    );
  }

  @override
  void dispose() {
  //回收动画
    controller.dispose();
    super.dispose();
  }
}
复制代码

使用AnimationWidget给Widget添加动画

AnimationWidget,实际上就是对需要执行动画变化的widget的一个封装,不用在手动调用setState()方法进行widget重绘

  • 首先将需要执行动画的widget封装到AnimationWidget中
class AnimationLogo extends AnimatedWidget {
  AnimationLogo({Key key, @required Animation<double> animation})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    Animation<double> animation = listenable;
    return Container(
      height: animation.value,
      width: animation.value,
      child: FlutterLogo(),
    );
  }
}
复制代码
  • 将原来设置动画的地方直接换成封装好的Widget即可,并且将动画传给封装好的widget
AnimationLogo(animation: animation)
复制代码

使用AnimationBuilder为widget构建动画

  • AnimatedBuilder继承AnimatedWidget
  • animation参数不为空,传入一个动画,然后传给AnimatedWidget的listenable,listenable在AnimatedWidget已介绍过,用于监听该动画,然后通知更新UI,就无需手动调用addListener监听动画然后调用setState更新UI
  • build参数不为空,传入一个build方法自定义动画起作用的控件
  • child是可选参数,有传的话,可以用于包裹在build方法返回的控件里面,作用可以详细看源码里child参数的说明,child是动画不起作用的子控件树,child由外部传入的话在每次动画更新重绘的时候就无需重绘该子控件树,可以提高效率
class AnimationPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return AnimationState();
  }
}

class AnimationState extends State<AnimationPage>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> _animation;
  // double animationValue;
  // AnimationStatus animationState;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
        duration: Duration(milliseconds: 2000), vsync: this);
    _animation = Tween<double>(begin: 0, end: 300).animate(controller);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter动画'),
      ),
      body: Center(
          child: Column(
        children: [
          RaisedButton(
            onPressed: () {
              controller.reset();
              controller.forward();
            },
            child: Text('点击开始动画'),
          ),
          AnimatedBuilder(animation: _animation,
              child: FlutterLogo(),
              builder: (context,child)=>Container(
                width: _animation.value,
                height: _animation.value,
                child: child,
          )),
        ],
      )),
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}
复制代码

AnimationBuilder其中的build 会在每次动画值更新时调用setState()进行重新绘制,如果给child参数赋值并将其赋值给build中,则每次重新绘制时则不再重新绘制child部分,以提高效率和节省资源

Hero动画

在 Flutter中可以用 Hero widget创建这个动画。当 hero 通过动画从源页面飞到目标页面时,目标页面逐渐淡入视野。通常, hero 是用户界面的一小部分,如图片,它通常在两个页面都有.跟Android中的共享元素差不多,就是让用户在交互上非常好的衔接效果.使用起来更加连贯

Hero 源码

 const Hero({
    Key key,
    @required this.tag,
    this.createRectTween,
    this.flightShuttleBuilder,
    this.placeholderBuilder,
    this.transitionOnUserGestures = false,
    @required this.child,
  }) : super(key: key);
复制代码
  • tag:必要参数,用于关联两个Hero动画的标识
  • child:必要参数,定义动画所呈现的widget
  • createRectTween:可选参数,定义目标Hero的边界,在从起始位置到目的位置的“飞行”过程中该如何变化;

Hero标准实现

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;

class HeroAnimation extends StatelessWidget {
  const HeroAnimation({Key key, this.photo, this.onTap, this.width})
      : super(key: key);
  final String photo;
  final VoidCallback onTap;
  final double width;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
      child: Hero(//定义hero
        tag: photo,//定义tag,为图片的url
        child: GestureDetector(
          onTap: onTap,
          child: Image.network(
            photo,
            fit: BoxFit.contain,
          ),
        ),
      ),
    );
  }
}

class HeroPage extends StatelessWidget{
  static const String url='https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/96119b38be4be1dafc95fcbff0d9ee30~300x300.image';

  @override
  Widget build(BuildContext context) {
    timeDilation = 5.0;
    return Scaffold(
      appBar: AppBar(title: Text('测试hero动画'),),
      body: Center(
      //使用Hero
        child: HeroAnimation(photo: url,width: 300,onTap: (){
          Navigator.of(context).push(MaterialPageRoute(builder: (context){
            return Scaffold(
              appBar: AppBar(title: Text('测试hero第二个页面'),),
              body: Container(
                padding: EdgeInsets.all(10),
                color: Colors.lightGreen,
                alignment: Alignment.topCenter,
                //第二个页面也使用该Hero的widget
                child: HeroAnimation(photo: url,width: 100,onTap: (){
                  Navigator.of(context).pop();
                },),
              ),
            );
          }));
        },),
      ),
    );
  }
}
复制代码

实现径向Hero动画(待续.........)

分类:
Android
标签: