Flutter 掌握动画开发

主本文主要说明动画的基本原理和简单的动画的实例,若有不当之处敬请指正。git

阅读本文大约须要 6 分钟github

背景

给UI界面设计合理的动画,可让用户以为更加流畅、直观,提升用户的交互使用感觉,改善用户体验。markdown

在 Flutter 中动画分为两类:基于补间 (Tween) 的和基于物理 (Physics) 的;less

补间动画是介于二者之间的简称,在补间动画中定义起点和终点、时间点以及定义时间变化和速度的曲线,而后由系统计算如何从开始点到结束点。ide

物理动画是运动被模拟为与真实世界的行为类似,好比抛一件物体,它落在什么地方取决于这个物体的重量,抛出去的速度以及这个物体与地面的高度,相似数学中的抛物线运动轨迹。函数

介绍

在 Flutter 中想要实现动画效果离不开几个核心的角色:Animation(动画对象),AnimationController(动画控制器),Tweens(插值器),Curves(动画曲线);学习

一、Animation

在 Flutter 中动画自己和UI渲染没有任何关系,Animation是一个抽象类,它拥有其当前值和状态(完成或中止),Flutter 中的动画系统就是基于 Animation 对象的。其中比较经常使用的就是Animation类是Animation。它能够经过其 value 属性来获取当前动画的值。动画

Animation 除了能够生成 double 的值以外还能够生成如:颜色--Animation<Color> 或者大小--Animation<Size>ui

Animation 对象能够拥有 Listeners 和 StatusListeners 监听器,能够用 addListener()addStatusListener() 来添加。只要动画的的值发生变化,就会调用监听器。正常咱们在 Listeners 中调用setState() 来触发UI重建;动画开始、结束、向前移动或向后移动时会调用StatusListener。this

二、AnimationController

AnimationController 是一个特殊的 Animation 对象,在屏幕刷新的每一帧,就会生成一个新的值。默认状况下,AnimationController 会在特定的时间内线性的生成0.0到1.0的数字。AnimationController派生于 Animation<double>,所以能够在须要Animation对象的任何地方使用。不但如此,AnimationController还具备控制动画的其余方法,好比 forward()方法能够启动动画。

AnimationController({
    double value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    @required TickerProvider vsync,
  })
复制代码

建立 AnimationController 必须需传入 vsync,传入 vsunc 是为了防止动画的UI不在当前屏幕时,不须要绘制,从而防止消耗没必要要的资源。经过将 SingleTickerProviderStateMixin 混入到类定义中,就能够将 statefu l对象做为 vsync 的值。

除了 vsync 还能够传入正向动画执行的时间 duration 以及反向动画执行时间 reverseDuration 等。

经常使用函数:

序号 方法 介绍
1 forward() 开始播放动画
2 stop() 中止动画
3 reset() 重制动画
4 reverse() 反向播放动画,必须处于正向动画播放完成的状态以后才有用
5 dispose() 释放动画占用资源
6 repeat() 循环播放动画

注意:动画完成时释放控制器(调用 dispose() 方法)以防止内存泄漏

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

三、Tween

默认状况下,AnimationController对象的范围从0.0到1.0。若是您须要不一样的范围或不一样的数据类型,则可使用Tween来配置动画以生成不一样的范围或数据类型的值。好比,能够生产从0-100的数字:

final Tween doubleTween = new Tween<double>(begin: 0.0, end: 100.0);
复制代码

Tween是一个无状态(stateless)对象,继承自Animatable<T>,而不是继承自 Animation<T>。Tween 须要两个值,分别是:begin 和 end。Tween的惟一职责就是定义从输入范围到输出范围的映射。

Animatable与Animation类似,不是必须输出double值,也能够是颜色,好比,从白色到黑色:

final Tween colorTween = new ColorTween(begin: Colors.withe, end: Colors.black);
复制代码

Tween 能够经过 animate() 方法传入 controller 对象建立 Animation 对象。以下

AnimationController _animationController = AnimationController(animationBehavior:AnimationBehavior.normal,vsync: this);
Tween<double> _tween = Tween<double>(begin: 0.0, end: 100.0)..animate(_animationController);
复制代码

四、CurvedAnimation

Curves 用来调整动画过程当中随时间的变化率,默认状况下,动画以均匀的线性模型变化。Flutter 内部也提供了一系列实现相应变化率的 Curves 对象:linea ------ 线性,decelerate ------ 减速等。

固然,也能够自定义继承 Curves 的类来定义动画的变化率,如:

class ShakeCurve extends Curve {
  @override
  double transform(double t) {
    return math.sin(t * math.PI * 2);
  }
}
复制代码

五、添加监听

目前为止动画只是实现了自身数值的变化,并无让 Widget 动起来,这里咱们须要对动画数值进行监听,而后使用 setstatus 来更新 Widget 的属性,从而使 Widget 动起来。

添加数值监听:

Animation animation = CurvedAnimation(parent: _animationController, curve: Curves.linear);
    animation.addListener((){
      setState(() {
        
      });
    });
复制代码

除此以外咱们还能够监听动画的状态变动,当动画结束时咱们反转动画,当动画的反转也结束后咱们重新开始动画,这样动画就会一直这样循环下去。

状态变动监听:

animation.addStatusListener((status){
      print(status);
    });
复制代码

六、AnimatedWidget

AnimatedWidget 类容许您从 setState() 调用中的动画代码中分离出 widget 代码。AnimatedWidget 不须要维护一个 State 对象来保存动画。

如下代码为官方文档自定义 AnimatedLogo

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

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

  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return new Center(
      child: new Container(
        margin: new EdgeInsets.symmetric(vertical: 10.0),
        height: animation.value,
        width: animation.value,
        child: new FlutterLogo(),
      ),
    );
  }
}

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => new _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
    controller.forward();
  }

  Widget build(BuildContext context) {
    return new AnimatedLogo(animation: animation);
  }

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

AnimatedWidget 为何不须要维护一个 State 对象来保存动画呢?

AnimatedWidget 源码中看一看出 AnimatedWidget 是继承自 StatefulWidget 类,在 AnimatedWidget 中,建立 state 是建立了 _AnimatedState,接着看 _AnimatedState 类部分源码:

abstract class AnimatedWidget extends StatefulWidget{
  
   @override
  _AnimatedState createState() => _AnimatedState();
  
}
复制代码

_AnimatedState 类的 initState 方法添加了监听 _handleChange,并在 didUpdateWidgetdispose 方法中移除了,_handleChange 里面只有一行代码就是 setState 方法:

_AnimatedState 源码

class _AnimatedState extends State<AnimatedWidget> {
  @override
  void initState() {
    super.initState();
    // 添加监听
    widget.listenable.addListener(_handleChange);
  }

  @override
  void didUpdateWidget(AnimatedWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 移除
    if (widget.listenable != oldWidget.listenable) {
      oldWidget.listenable.removeListener(_handleChange);
      widget.listenable.addListener(_handleChange);
    }
  }

  @override
  void dispose() {
    // 移除
    widget.listenable.removeListener(_handleChange);
    super.dispose();
  }

  void _handleChange() {
    // 更新状态
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }

  @override
  Widget build(BuildContext context) => widget.build(context);
}
复制代码

七、并行动画

所谓的并行动画就是一块儿执行多个动画,在 Flutter 中能够在同一个动画控制器上使用多个Tween,而后每一个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的构造函数只接受一个动画对象。 为了解决这个问题,能够建立了本身的Tween对象并显式计算了这些值。

build方法.evaluate()在父级的动画对象上调用Tween函数以计算所需的sizeopacity值。

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

class AnimatedLogo extends AnimatedWidget {
  // The Tweens are static because they don't change.
  static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
  static final _sizeTween = new Tween<double>(begin: 0.0, end: 300.0);

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

  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return new Center(
      child: new Opacity(
        opacity: _opacityTween.evaluate(animation),
        child: new Container(
          margin: new EdgeInsets.symmetric(vertical: 10.0),
          height: _sizeTween.evaluate(animation),
          width: _sizeTween.evaluate(animation),
          child: new FlutterLogo(),
        ),
      ),
    );
  }
}
复制代码

实例

效果图

一、缩放动画

直接贴代码

///放大缩小动画
  Widget scale() {
    return Column(
      children: <Widget>[
        Container(
          height: 170,
          child: Center(
            child: Container(
              width: _scaleAnimation.value,
              height: _scaleAnimation.value,
              child: new FlutterLogo(),
            ),
          ),
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              color: Colors.blue,
              child: Text(
                "放大",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _scaleController.forward();
              },
            ),
            RaisedButton(
              color: Colors.red,
              child: Text(
                "缩小",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _scaleController.reverse();
              },
            )
          ],
        ),
      ],
    );
  }
复制代码

二、淡入淡出动画

代码:

/// 淡入淡出
  Widget alpha() {
    return Column(
      children: <Widget>[
        Container(
          height: 170,
          child: Center(
            child: Container(
              height: 100,
              width: 100,
              child: Opacity(
                opacity: _alphaAnimation.value,
                child: FlutterLogo(),
              ),
            ),
          ),
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              color: Colors.blue,
              child: Text(
                "淡入",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _alphaController.forward();
              },
            ),
            RaisedButton(
              color: Colors.red,
              child: Text(
                "淡出",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _alphaController.reverse();
              },
            )
          ],
        ),
      ],
    );
  }
复制代码

注意,一个 Widget 使用多个animationController 须要修改混入SingleTickerProviderStateMixin 为 TickerProviderStateMixin。

结尾

完整代码奉上GitHub地址:fluter_demo ,欢迎star和fork。

到此,本文就结束了,若有不当之处敬请指正,一块儿学习探讨,谢谢🙏。

相关文章
相关标签/搜索