Flutter 动画

每一个平台对于动画的实现大同小异,手段大部分都是在60帧(部分Android机型90FPS,部分iPad是120FPS)的刷新频率下实现UI的屡次变化,利用人眼视觉残留实现“流畅”的动做。html

  • 16 FPS: 比较流畅
  • 32 FPS: 很是的细腻平滑
  • 大于32 FPS: 人眼感觉无差异

在理想状态下,Flutter 可以实现 60 FPS。(这里的刷新频率是否跟随硬件,找到资料再更新)markdown

可是每种UI框架对动画的抽象方式都不同,而 Flutter 中实现一个动画须要涉及到 Animation、Curve、Controller、Tween这四个角色。框架

动画分解

Animation

Animation是一个抽象类,从下面的Animation的部分源码能够看出,class Animation 仅定义了动画当前的值和状态,以及监听方法等内容,与UI展现样式等相关定义和属性无关(color, width 等)。less

abstract class Animation<T> extends Listenable implements ValueListenable<T> {
  const Animation();
  @override
  void addListener(VoidCallback listener);
  @override
  void removeListener(VoidCallback listener);
  void addStatusListener(AnimationStatusListener listener);
  void removeStatusListener(AnimationStatusListener listener);
  AnimationStatus get status;
  @override
  T get value;
  bool get isDismissed => status == AnimationStatus.dismissed;
  bool get isCompleted => status == AnimationStatus.completed;
  // 代码省略
  // ...
}
复制代码

AnimationController

继承自 abstract class Animation 类,用于控制动画的 进行(forward)、中止(stop)、反向播放(reverse)。ide

默认状况下,AnimationController 对象会在动画的每一帧,按照动画曲线的规律生成 0 - 1 区间内的值。以下代码:函数

final AnimationController controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
复制代码

这段代码的意义是:在2000毫秒(秒)内,随着帧刷新的频率,线性的生成 0 - 1 区间内的值。动画

其中 vsync 参数须要传入 TickerProvider 对象,用于屏幕刷新时的回调。ui

传入 TickerProvider 对象除了提供屏幕刷新回调之外,还能够防止 屏外动画的问题出现。当动画UI不在当前屏幕时,或者手机锁屏时,动画刷新会中止,避免消耗没必要要的资源。this

固然,动画如此经常使用的操做,Flutter毫不会让你花费很大的代价去实现,一般咱们只要在 State 中 mixin SingleTickerProviderStateMixin 便可。 以下代码:spa

class _StaggerDemoState extends State<StaggerDemo> with TickerProviderStateMixin {
    //...
}
复制代码

动画的实现一般伴随着 AnimationControlelr 对象的 dispose,势必须要借助 StatefullWidget 的声明周期方法dispose。天然也会有对应的State对象,因此这里不须要去纠结 StatelessWidget 是否可使用 TickerProviderStateMixin 的疑问。

以上所讲的内容均是在 线性动画 下,若是想要执行非线性动画,则须要借助 Curve 的协助。

Curve

动画中使用 Curve 能够改变更画曲线,Curve 已经提供经常使用的动画曲线,下面列出部分枚举类:

Curves曲线 | 动画过程 - | - linear | 匀速的 decelerate | 匀减速 ease | 开始加速,后面减速 easeIn | 开始慢,后面快 easeOut | 开始快,后面慢 easeInOut | 开始慢,而后加速,最后再减速

除了已经提供好的 Curve 曲线,也能够自定义动画曲线,实现起来也很简单。下面是一个正弦曲线的实现:

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

从上面的代码能够看出,要实现一个自定义动画曲线,只须要重写 Curvetransform 方法便可。

若是要实现的动画效果不知足于0-1的区间的话,还能够借助 Tween 对象实现自定义动画区间。

Tween

提供开发自定义动画区间的能力。以下示例所示:

Tween<double>(begin: -200.0, end: 0.0);
Tween<Color>(begin: Colors.transparent, end: Colors.black54);
Tween<EdgeInsets>(begin: const EdgeInsets.only(left: .0), end: const EdgeInsets.only(left: 100.0)
复制代码

这些均不是0 - 1 的区间。Color的区间能够实现一个Color到另外一个Color的渐变过渡。EdgeInsets能够实现间距的渐变。

如此看起来,貌似Tween就已经能够实现0-1到任意区间的映射了,然而,事情毫不会如此顺利。

让咱们来看一下 Tween的定义:

class Tween<T extends dynamic> extends Animatable<T> {
  Tween({ this.begin, this.end });
  T begin;
  T end;

  /// Returns the value this variable has at the given animation clock value.
  ///
  /// The default implementation of this method uses the [+], [-], and [*]
  /// operators on `T`. The [begin] and [end] properties must therefore be
  /// non-null by the time this method is called.
  @protected
  T lerp(double t) {
    assert(begin != null);
    assert(end != null);
    return begin + (end - begin) * t;
  }
  @override
  T transform(double t) {
    if (t == 0.0)
      return begin;
    if (t == 1.0)
      return end;
    return lerp(t);
  }

  @override
  String toString() => '$runtimeType($begin \u2192 $end)';
}
复制代码

从上面的源码能够看出,Tween的定义十分的简单,毫不会支持全部类型的区间映射。lerp函数已经告诉咱们,类型 T 的对象,须要进行 + - * 三则运算,所以,可以运用 Tween 自动实现区间映射的对象,只能是实现了三则运算的对象,如上面例子中的 doubleEdgeInsets,而Color则不能直接使用Tween实现区间映射。这时候就须要开发者自行实现double -> Color类型的区间映射。

那咱们就本身实现一个 ColorTween

class ColorTween extends Tween<Color> {
  ColorTween({ Color begin, Color end }) : super(begin: begin, end: end);
  @override
  Color lerp(double t) => Color.lerp(begin, end, t);
}
复制代码

咱们利用 Color 对象的 lerp 方法很轻松的实现了 ColorTween,只是重写了 Tween 类的 lerp 方法,返回区间映射的计算方法而已。类比ColorTween的实现,其余类型的区间映射,也能够如此写。只要你喜欢,也能够实现正弦函数的区间映射关系。

Flutter 已经提供了一些线程的Tween子类给开发者使用:

  • ColorTween
  • IntTween
  • ReverseTween
  • SizeTween
  • RectTween
  • StepTween
  • ConstantTween

动画的当前值,动画的执行,动画曲线,动画区间 都已经实现了,那下一步就是结合这四个部分,实现动画效果。

完整的动画建立过程

先来看一个完整的动画定义:

// 1. 建立动画控制类 AnimationController,用于执行动画
final AnimationController _controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
// 2. 使用 CurvedAnimation 结合 动画控制类 和 动画曲线类,返回一个 「具备指定动画曲线的」「动画」 对象
fianl CurvedAnimation curvedAnimation = CurvedAnimation(
    parent: _controller,
    curve: Curves.ease,
);
// 3. 自定义区间 再次结合 CurvedAnimation,生成一个「具备指定区间值」和「指定动画曲线」的「动画」对象
final Animation<double> height = Tween<double>(begin: 0, end: 300.0).animate(curvedAnimation);
复制代码

上述就是一个完整的动画建立过程,其中使用了一个新的 CurvedAnimation 类,从名字就能够看出,CurvedAnimationCurveAnimation都有关系,关系就是结合这二者,生成一个具备指定动画曲线Animation对象。

CurvedAnimation 继承自 Animation<T>,一样,它能够标识动画的当前值,却不能控制动画的执行。

动画已经建立出来了,怎么根据建立好的动画构建出能够刷新的动画呢。

这里有主要的三种方式构建动画UI。有兴趣的能够看这篇文章。这里就直接上手最推荐使用AnimationedBuilder。示例代码以下:

@override
Widget build(BuildContext context) {
  return Center(
    child: AnimatedBuilder(
      animation: _controller,
      builder: (BuildContext context, Widget _) => Container(
        width: 200,
        height: height.value,
        color: Colors.red,
      ),
    ),
  );
}
复制代码

完整代码以下:

class BasicAnimation extends StatefulWidget {
  @override
  _BasicAnimationState createState() => _BasicAnimationState();
}

class _BasicAnimationState extends State<BasicAnimation> with SingleTickerProviderStateMixin {
  AnimationController _animationController;
  Animation<double> _height;

  @override
  void initState() {
    _animationController = AnimationController(vsync: this, duration: Duration(seconds: 1));
    _height = CurvedAnimation(parent: _animationController, curve: Curves.easeInOut);
    _height = Tween<double>(begin: 0, end: 200).animate(_height);

    super.initState();
    
    _animationController.forward();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
          child: Container(
            width: 300,
            height: 300,
            color: Colors.grey,
            alignment: Alignment.bottomCenter,
            child: _buildAnimatedWidget,
          ),
        ));
  }

  Widget get _buildAnimatedWidget => AnimatedBuilder(
        animation: _animationController,
        builder: (BuildContext context, Widget child) {
          print('build animation');
          return Container(
            width: 40,
            height: _height.value,
            color: Colors.red,
          );
        },
      );

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

交织动画(组合动画)

相似于组动画,或者动画组的概念。同时或者交叉的执行多个动画。

下面咱们实现一个:

  • 前半部分高度从0->300,同时颜色从绿色到红色
  • 后半部分往右平移
  • 动画执行结束以后,反向执行,直到结束

的一个组合动画。

咱们直接看代码(未完待续...)

其余动画

  • AnimatedCrossFade
  • 转场动画:Hero

参考内容

相关文章
相关标签/搜索