Flutter绘制-12-动画专项-基础使用篇

查看目录–>

动画的本质

动画,即,动起来的画。

Flutter,达到60FPS,即16ms一帧,每一帧即一个画面。

如一个Image,初始时width=300,height=300

第1帧:width=300,height=300

第2帧:width=295,height=295

第3帧:width=290,height=290

。。。。。。

。。。。。。

第58帧:width=10,height=10

第59帧:width=5,height=5

第60帧:width=0,height=0

在一秒内,从第1帧刷到60帧,你看到的就是Image的缩小效果。这就是动画。

Image的宽高属性在1秒内连续发生了变化,是缩放;

Image的位置属性,在1秒内连续发生变化,是移动效果;

Text的字体大小属性,在一段时间内发生连续变化,是字体大小的缩放效果;

。。。。。。

也就是说,widget的某个或某几个属性,在一段时间内发生连续变化,就是该widget对应属性的动画效果。

关键点:

  • 作用在widget的属性上
  • 一段时间
  • 连续变化的属性值

上面的都是思路,那flutter中具体如何实现的呢?

flutter动画的几个类:

  • AnimationController,设定在一个时间段内,会持续的产生0-1之间的值,可以控制动画的执行,如正向执行、反向执行、重复等,也可以添加状态监听,如每一帧的回调、整个动画状态的回调等。
  • Tween,专门设定取值范围,AnimationController默认是0-1,通过Tween可以将这个取值范围改变,如0-100,红色-绿色等
  • Curve,AnimationController会持续的产生一个区间的值,Curve控制的是在这个区间内,值变化的规律,比如上面一开始的例子,是线性变化,每一次固定减小5;我们也可以改变这个算法,如依次减少60、55、50.。。。,这是一个变化放缓的效果。Flutter内置了一些效果,后面我们一一看。

你是否注意到,上面的内容,说的都是在一段时间段内如何天花乱坠的产生天花乱坠的值,跟widget一点关系都没有。

怎么建立关联?

在build中:

build(){
    return Widget( xxx属性:_AnimationController.value);
}

复制代码

每次AnimationController产生的值发生变化后,都会最终调用到build方法,让界面发生变化,最终形成连续的效果,也就是动画。

QQ20210429-090335-HD.gif

上面的示例代码:

import 'package:flutter/material.dart';

class Test01AnimationController extends StatefulWidget {
  @override
  _Test01AnimationControllerState createState() =>
      _Test01AnimationControllerState();
}

class _Test01AnimationControllerState extends State<Test01AnimationController>
    with SingleTickerProviderStateMixin {
  int _count = 0;
  double _width = 0; // 图片宽
  double _height = 0; // 图片高
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _count = 0; // addListener回调的次数
    // 创建一个动画,设定持续时间duration为2000ms
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 2000));
    // 每次值发生变化就回调该方法
    _controller.addListener(() {
      // 每次值发生变化后,就让图片宽高也发生变化
      setState(() {
        _count++;
        _width = 300 * _controller.value;
        _height = 300 * _controller.value;
      });
    });
    // 动画开始执行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Text("${_count}  ${_controller.value}")
          ],
        ),
      ),
    );
  }

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

简短总结:

  • 动画AnimationnController,持续产生一系列的值
  • 每产生一次值,就setState一次,让该值作用到widget的属性上
  • 每次setState后,build方法重新执行
  • 最终看到一个变化的widget,即动画效果

重要的类

AnimationController

构造方法

AnimationController
AnimationController({
    double? value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    required TickerProvider vsync,
  }) : assert(lowerBound != null),
       assert(upperBound != null),
       assert(upperBound >= lowerBound),
       assert(vsync != null),
       _direction = _AnimationDirection.forward {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value ?? lowerBound);
  }
复制代码

此构造方法特点:

  • 有上下值边界
  • 在动画执行过程中持续产生上下边界范围内的值,通过controller.value考验获取到该值。
AnimationController.unbounded
AnimationController.unbounded({
    double value = 0.0,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    required TickerProvider vsync,
    this.animationBehavior = AnimationBehavior.preserve,
  }) : assert(value != null),
       assert(vsync != null),
       lowerBound = double.negativeInfinity,
       upperBound = double.infinity,
       _direction = _AnimationDirection.forward {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value);
  }
复制代码

此构造方法特点:

  • lowerBound与upperBound是无限的
  • 通过controller.value 获取到的值是Infinity.
  • 因此该构造方法的意义在于定义了一个持续的时间段,但是该时间段内的动画需要自己定义。通常用于物理模拟。

属性

value

有两个作用:

  • 在动画执行过程中,通过controller.value获取当前时间点动画产生的值。
  • 在创建AnimationController时设置初始值,比如value=0.5,AnimationController默认取值范围是0-1,那么在动画执行时,是从0.5开始取值,直到1。如果你设定了duration为2秒,那么实际执行时间约为1秒。如果你设定value=1,那么动画不执行。也就是说,AnimationController会按照duration去产生持续变化的值,设置value后,会取value-1的这一段。
```
_controller = new AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 2000),
    value: 0.9
);
```
复制代码

image.png

还有一个关键点,value取值在lowerBound与upperBound之间时才有效果,如果value小于lowerBound,取值从lowerBound开始;如果value大于upperBound,那么动画相当于直接结束,不再执行。value默认值是lowerBound。

duration

动画持续时间
#####reverseDuration
当动画执行revrse时的持续时间,如果不设置,那么时间按duration计算。

lowerBound

默认0.0,设置时,lowerBound要小upperBounnd,否则报错

upperBound

默认1.0

设定了lowerBound与upperBound的例子:

import 'package:flutter/material.dart';

class Test02AnimationController extends StatefulWidget {
  @override
  _Test02AnimationControllerState createState() =>
      _Test02AnimationControllerState();
}

class _Test02AnimationControllerState extends State<Test02AnimationController>
    with SingleTickerProviderStateMixin {
  int _count = 0;
  double _width = 0; // 图片宽
  double _height = 0; // 图片高
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _count = 0; // addListener回调的次数
    // 创建一个动画,设定持续时间duration为2000ms
    _controller = new AnimationController(
        vsync: this,
        duration: const Duration(milliseconds: 2000),
        value: 0,
        lowerBound: 0,
        upperBound: 20,
        animationBehavior: AnimationBehavior.normal
    );
    // 每次值发生变化就回调该方法
    _controller.addListener(() {
      // 每次值发生变化后,就让图片宽高也发生变化
      setState(() {
        _count++;
        _width = 30 * _controller.value;
        _height = 30 * _controller.value;
      });
    });
    // 动画开始执行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Text("${_count}  ${_controller.value}")
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

复制代码
animationBehavior

注意:此处限个人理解,做个记录,后续研究具体情形后再来更正。目前可略过此属性不看

flutter支持很多平台,但是有些动画在某些平台就是不支持或要求简化,而AccessibilityFeatures.disableAnimations字段就是返回的平台支持情况,当为true时,设备会要求flutter尽快减少或禁用动画。有两种方式控制:

  • AnimationBehavior.normal,当使用new AnimationController()时默认此值,通过减少动画持续时间来让简化动画,让其尽早结束。
  • AnimationBehavior.preserve,当使用new AnimationController.unbounded()时默认此值,AnimationController会保留其行为,我理解的是惯性。对于重复性动画,当不考虑AccessibilityFeatures.disableAnimations时,默认就是这种行为。
vsync

注册屏幕的每一帧回调监听,防止屏幕外动画,如锁屏时不回调,避免消耗不必要的资源。创建AnimationController时比须传入此值,而AnimationController通常是在State中创建,让State with 一个SingleTickerProviderStateMixin,传vsync时,传入this。

Curve

先看个效果:

3.gif

Curve描述动画过程中速度变化的过程,AnimationController默认效果是匀速的,Curve可以改变这种效果,如实现加速、减速、反弹等。实际控制的是AnimationController在duration内产生值的规则。

如何使用:

  AnimationController _controller;
  CurvedAnimation _curvedAnimation;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 3000));
    _curvedAnimation =
        new CurvedAnimation(parent: _controller, curve: Curves.ease);
    // 动画开始执行
    _controller.repeat();
  }
  
复制代码
  • 依然创建AnimationController
  • 创建CurvedAnimation时,将AnimationController 和 curve传入,通过Curses.xxx,可以获取flutter内置的效果,当前时间点有41种。
  • 依然是使用AnimationController进行动画的控制,如repeat、forward、reverse等。
  • 以后取值改为从CurvedAnimation获取,如_curvedAnimation.value。

上面效果的代码:

import 'package:flutter/material.dart';

class Test05CurveImage extends StatefulWidget {
  @override
  _Test05CurveImageState createState() =>
      _Test05CurveImageState();
}

class _Test05CurveImageState extends State<Test05CurveImage>
    with SingleTickerProviderStateMixin {
  double _width = 0; // 图片宽
  double _height = 0; // 图片高
  AnimationController _controller;
  CurvedAnimation _curvedAnimation;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 5000));
    _curvedAnimation = new CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
    _controller.addListener(() {
      setState(() {
        _width = 300 * _curvedAnimation.value;
        _height = 300 * _curvedAnimation.value;
      });
    });
    // 动画开始执行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Text("_curvedAnimation.value=${_curvedAnimation.value}")
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

复制代码

Curve来源:

  • flutter内置提供
  • 自定义

Flutter提供的Curve效果

当前是41种,见效果:

QQ20210507-094246-HD.gif

代码如下:

import 'package:flutter/material.dart';
import 'dart:math';
class Test04Curve extends StatefulWidget {
  @override
  _Test04CurveState createState() => _Test04CurveState();
}

class _Test04CurveState extends State<Test04Curve>
    with SingleTickerProviderStateMixin {
  List<ItemModel> curveList;
  double size = 120;

  @override
  void initState() {
    super.initState();
    curveList = [
      ItemModel('linear', Curves.linear),
      ItemModel('bounceIn', Curves.bounceIn),
      ItemModel('bounceOut', Curves.bounceOut),
      ItemModel('bounceInOut', Curves.bounceInOut),
      ItemModel('ease', Curves.ease),
      ItemModel('easeIn', Curves.easeIn),
      ItemModel('easeInBack', Curves.easeInBack),
      ItemModel('easeInCirc', Curves.easeInCirc),
      ItemModel('easeInCubic', Curves.easeInCubic),
      ItemModel('easeInExpo', Curves.easeInExpo),
      ItemModel('easeInQuad', Curves.easeInQuad),
      ItemModel('easeInQuart', Curves.easeInQuart),
      ItemModel('easeInQuint', Curves.easeInQuint),
      ItemModel('easeInSine', Curves.easeInSine),
      ItemModel('easeInToLinear', Curves.easeInToLinear),
      ItemModel('easeOut', Curves.easeOut),
      ItemModel('easeOutBack', Curves.easeOutBack),
      ItemModel('easeOutCirc', Curves.easeOutCirc),
      ItemModel('easeOutCubic', Curves.easeOutCubic),
      ItemModel('easeOutExpo', Curves.easeOutExpo),
      ItemModel('easeOutQuad', Curves.easeOutQuad),
      ItemModel('easeOutQuart', Curves.easeOutQuart),
      ItemModel('easeOutQuint', Curves.easeOutQuint),
      ItemModel('easeOutSine', Curves.easeOutSine),
      ItemModel('easeInOut', Curves.easeInOut),
      ItemModel('easeInOutBack', Curves.easeInOutBack),
      ItemModel('easeInOutCirc', Curves.easeInOutCirc),
      ItemModel('easeInOutCubic', Curves.easeInOutCubic),
      ItemModel('easeInOutExpo', Curves.easeInOutExpo),
      ItemModel('easeInOutQuad', Curves.easeInOutQuad),
      ItemModel('easeInOutQuart', Curves.easeInOutQuart),
      ItemModel('easeInOutQuint', Curves.easeInOutQuint),
      ItemModel('easeInOutSine', Curves.easeInOutSine),
      ItemModel('decelerate', Curves.decelerate),
      ItemModel('elasticIn', Curves.elasticIn),
      ItemModel('elasticOut', Curves.elasticOut),
      ItemModel('elasticInOut', Curves.elasticInOut),
      ItemModel('slowMiddle', Curves.slowMiddle),
      ItemModel('fastLinearToSlowEaseIn', Curves.fastLinearToSlowEaseIn),
      ItemModel('fastOutSlowIn', Curves.fastOutSlowIn),
      ItemModel('linearToEaseOut', Curves.linearToEaseOut),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Curve"),
        centerTitle: true,
      ),
      body: Container(
        padding: EdgeInsets.all(10),
        child: GridView.builder(
          gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
              mainAxisExtent: size + 20,
              mainAxisSpacing: 10,
              crossAxisSpacing: 10,
              maxCrossAxisExtent: size + 10),
          itemBuilder: (context, index) {
            return Container(
              padding: EdgeInsets.symmetric(horizontal: 5),
              child: Column(
                children: [
                  MyCurveWidget(curveList[index].curve, size),
                  Text(curveList[index].name, style: TextStyle(fontSize: 10)),
                ],
              ),
            );
          },
          itemCount: curveList.length,
        ),
      ),
    );
  }
}

class MyCurveWidget extends StatefulWidget {
  Curve _curve;
  double _size;

  MyCurveWidget(this._curve, this._size);

  @override
  _MyCurveWidgetState createState() => _MyCurveWidgetState();
}

class _MyCurveWidgetState extends State<MyCurveWidget>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  CurvedAnimation _curvedAnimation;

  @override
  void initState() {
    super.initState();
    // 创建一个动画,设定持续时间duration为2000ms
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 6000));
    _curvedAnimation =
        new CurvedAnimation(parent: _controller, curve: widget._curve);
    // 动画开始执行
    _controller.repeat();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: widget._size,
      width: widget._size,
      child: Center(
        child: CustomPaint(
          size: Size(widget._size - 10, widget._size - 10),
          painter: MyCurvePainter(_curvedAnimation),
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

class MyCurvePainter extends CustomPainter {
  Animation<double> _animation;

  MyCurvePainter(this._animation) : super(repaint: _animation);

  @override
  void paint(Canvas canvas, Size size) {
    translateToCenter(canvas, size);
    drawBg(canvas, size);
    drawBoll(canvas, size);
  }

  void drawBg(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = Colors.black12
      ..strokeWidth = 1
      ..style = PaintingStyle.stroke;
    Offset p1 = Offset(-size.width / 2, 0);
    Offset p2 = Offset(size.width / 2, 0);
    canvas.drawLine(p1, p2, paint);
    canvas.drawCircle(Offset.zero, size.width / 2, paint);
  }

  void drawBoll(Canvas canvas, Size size) {
    double radius = size.width / 2;
    Paint paint = Paint()
      ..color = Colors.orangeAccent
      ..style = PaintingStyle.fill;
    canvas.drawCircle(
        Offset(-radius + size.width * _animation.value, 0), 5, paint);

    double sweepAngle = pi + pi * 2 * _animation.value;
    canvas.drawCircle(
        Offset(radius * cos(sweepAngle), radius * sin(sweepAngle)), 5, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }

  void translateToCenter(Canvas canvas, Size size) {
    canvas.translate(size.width / 2, size.height / 2);
  }
}

class ItemModel {
  String name;
  Curve curve;

  ItemModel(this.name, this.curve);
}

复制代码

自定义Curve

继承Curve,然后重写transformInternal方法,自己定义变化的规律。
如:

class MyCurve extends Curve {
  @override
  double transformInternal(double t) {
    return sin(pi / 2 * t);
  }
}
复制代码
  • t的范围在0-1之间
  • sin(pi / 2 * t) 应用t做了一个正弦函数曲线

效果如下;

4.gif

代码如下:

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

class Test06CustomCurve extends StatefulWidget {
  @override
  _Test06CustomCurveState createState() => _Test06CustomCurveState();
}

class _Test06CustomCurveState extends State<Test06CustomCurve>
    with SingleTickerProviderStateMixin {
  double _width = 0; // 图片宽
  double _height = 0; // 图片高
  AnimationController _controller;
  CurvedAnimation _curvedAnimation;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 5000));
    _curvedAnimation =
        new CurvedAnimation(parent: _controller, curve: MyCurve());
    _controller.addListener(() {
      setState(() {
        _width = 300 * _curvedAnimation.value;
        _height = 300 * _curvedAnimation.value;
      });
    });
    // 动画开始执行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Text("_curvedAnimation.value=${_curvedAnimation.value}")
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

class MyCurve extends Curve {
  @override
  double transformInternal(double t) {
    return sin(pi / 2 * t);
  }
}

复制代码

Tween

AnimationController可以设定取值范围lowerBound-upperBound,类型是double的。Tween扩展了取值范围和类型,可以是颜色、int、double等。

有两个重要的方法:

  • evaluate
  • animate

使用evaluate

1.gif

代码如下;

import 'package:flutter/material.dart';

class Test07Tween extends StatefulWidget {
  @override
  _Test07TweenState createState() => _Test07TweenState();
}

class _Test07TweenState extends State<Test07Tween>
    with SingleTickerProviderStateMixin {
  double _width = 0; // 图片宽
  double _height = 0; // 图片高
  AnimationController _controller;
  Tween sizeTween = new Tween(begin: 0.0, end: 300.0);

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 5000),upperBound: 1);
    _controller.addListener(() {
      setState(() {
        _width = sizeTween.evaluate(_controller);
        _height = sizeTween.evaluate(_controller);
      });
    });
    // 动画开始执行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Row(
              children: [
                Text("sizeTween.evaluate(_controller)="),
                Text("${sizeTween.evaluate(_controller)}")
              ],
            )
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

复制代码

关键点:

  • new Tween(begin: 0.0, end: 300.0),创建Tween对象,设定取值范围
  • 正常创建controller
  • sizeTween.evaluate(_controller),evaluate方法获取当前时间点的插值

其实到这里有个疑问,lowerBound、upperBound与Tween同时存在会怎么样?上面的例子upperBound=1,现在我们改成2看下效果:

22.gif

相比upperBound=1,图片放大了一倍。因此推断是相乘的关系。

使用animate

使用Tween提供的animate方法,可以获取一个新的Animation ,通过此也可以获取value值,如下代码:

import 'package:flutter/material.dart';

class Test08TweenAnimate extends StatefulWidget {
  @override
  _Test08TweenAnimateState createState() => _Test08TweenAnimateState();
}

class _Test08TweenAnimateState extends State<Test08TweenAnimate>
    with SingleTickerProviderStateMixin {
  double _width = 0; // 图片宽
  double _height = 0; // 图片高
  AnimationController _controller;
  Animation<dynamic> _animation;
  Tween sizeTween = new Tween(begin: 0.0, end: 300.0);

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 5000));
    _animation = sizeTween.animate(_controller);
    _controller.addListener(() {

      setState(() {
        _width = _animation.value;
        _height = _animation.value;
      });
    });
    // 动画开始执行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Row(
              children: [
                Text("_animation.value="),
                Text("${_animation.value}")
              ],
            )
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

复制代码

关键点:

  • 创建Tween:Tween sizeTween = new Tween(begin: 0.0, end: 300.0)
  • 与controller关联: _animation = sizeTween.animate(_controller);
  • 使用:_animation.value获取插值
  • 注意依然是使用_controller来控制动画的开始、重复等

Interval 组合动画

Interval是Curve的子类,定义了一个区间范围[0,1]内取值,可以是[0.1,0.3],也可以是[0.4,0.8]。我们都知道,AnimationController整体描述的是一个动画过程,看做是[0,1],Interval则在[0,1]内选择了一块区域,如[0.3,0.5],当Tween在animate时指定该Interval后,表示该Tween在0.3时开始,0.5时结束。

举个例子,一个正方形,在动画的0-0.6完成大小变化,0.3-1.0完成颜色变化。
效果:

4.gif

代码:

import 'package:flutter/material.dart';

class Test29Interval extends StatefulWidget {
  @override
  _State createState() => _State();
}

class _State extends State<Test29Interval>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  Tween _sizeTween = new Tween<double>(begin: 0.0, end: 200.0);
  Tween _colorTween = new ColorTween(begin: Colors.orangeAccent, end: Colors.green);
  Animation<double> _animationSize;
  Animation<Color> _animationColor;
  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 6000),
    );
    _animationSize = _sizeTween.animate(new CurvedAnimation(
        parent: _controller, curve: new Interval(0.0, 0.6)));
    _animationColor = _colorTween.animate(new CurvedAnimation(
        parent: _controller, curve: new Interval(0.3, 1.0)));
    _controller.addListener(() {
      setState(() {});
    });
    // 动画开始执行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Test29Interval"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              width: _animationSize.value,
              height: _animationSize.value,
              margin: EdgeInsets.all(10),
              color: _animationColor.value,
            ),
            Container(
              width: 400,
              child: Text("_animationSize.value = ${_animationSize.value}",style: TextStyle(fontSize: 14),),
            )
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

复制代码

AnimatedWidget

大家应该注意到了,上面所有的动画变化都是通过setState来维护的,对于一个通用功能,如果每次都使用setState,显然比较繁琐。因此,Flutter提供了AnimatedWidget,在使用时就不用显示的调用setState了。

看个例子,效果:

5.gif

代码:

import 'package:flutter/material.dart';

class Test30AnimatiedWidget extends StatefulWidget {
  @override
  _State createState() => _State();
}

class _State extends State<Test30AnimatiedWidget>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 6000), vsync: this);
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
    controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return ImageAnimatiedWidget(
      animation: animation,
    );
  }

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

class ImageAnimatiedWidget extends AnimatedWidget {
  @override
  Widget build(BuildContext context) {
    Animation animation = listenable;
    return Center(
      child: Container(
        child: Image.asset(
          "images/a2.png",
          width: animation.value,
          height: animation.value,
        ),
        // width: animation.value,
        // height: animation.value,
      ),
    );
  }

  ImageAnimatiedWidget({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);
}

复制代码

AnimatedBuilder

同AnimatedWidget类似,隐了setState,提供了另一种widget组织方式。

同上面的例子,效果:

5.gif

代码:

import 'package:flutter/material.dart';

class Test31AnimateBuilder extends StatefulWidget {
  @override
  _State createState() => _State();
}

class _State extends State<Test31AnimateBuilder>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 6000), vsync: this);
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
    controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return ImageAnimateBuilder(animation, MyImage());
  }

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

class ImageAnimateBuilder extends StatelessWidget {
  final Animation animation;
  final Widget child;

  ImageAnimateBuilder(this.animation, this.child);

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

class MyImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: Image.asset(
          "images/a2.png",
        ),
        // width: animation.value,
        // height: animation.value,
      ),
    );
  }
}

复制代码

AnimatedSwitcher

这是flutter提供的一个特殊的widget,效果是对于AnimatedSwitcher的child,当发生变化时,旧值执行去的动画,新值执行来的动画。去和来,指的是同一个动画的forward与reverse。

看一个例子,数字发生变化,当前数字逐渐缩小隐藏,新的数字是放大的过程。

效果:

6.gif

代码:

import 'package:flutter/material.dart';

class Test32AnimatedSwitcher extends StatefulWidget {
  const Test32AnimatedSwitcher({Key key}) : super(key: key);

  @override
  _State createState() => _State();
}

class _State extends State<Test32AnimatedSwitcher> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          AnimatedSwitcher(
            duration: const Duration(milliseconds: 1000),
            transitionBuilder: (Widget child, Animation<double> animation) {
              //执行缩放动画
              return ScaleTransition(child: child, scale: animation);
            },
            child: Text(
              '$_count',
              //显示指定key,不同的key会被认为是不同的Text,这样才能执行动画
              key: ValueKey<int>(_count),
              style: TextStyle(fontSize: 30),
            ),
          ),
          RaisedButton(
            child: const Text('+1',),
            onPressed: () {
              setState(() {
                _count += 1;
              });
            },
          ),
        ],
      ),
    );
  }
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享