Flutter动画-一篇就够了

4,503 阅读8分钟

高效的动画系统作为Flutter的特性,不得不学习,接下来说下动画的分类,由浅入深,建议阅读两次,后面的知识会巩固前面的学习。 关注公众号:Flutter入门

动画的分类,可以分为隐式动画和显示动画,主要的区别是是否开发者自己控制AnimationController。

隐式动画(Implicit Animations):

AnimatedFoo

TweenAnimatedBuilder

显示动画(Explicit Animations):

FooTransaction

AnimatedWidget

AnimatedBuilder

隐式动画

AnimatedFoo

AnimatedFoo是Flutter framework提供的一系列的动画组件,动画改变的属性由名字就可以看出。继承自ImplicitlyAnimatedWidget统一管理AnimationController,不用开发者自己dispose()framework提供了:

AnimatedAlign
AnimatedContainer
AnimatedDefaultTextStyle
AnimatedOpacity
AnimatedPadding
AnimatedPhysicalMode
AnimatedPositioned
AnimatedPositionedDirectional
AnimatedTheme
AnimatedCrossFade
AnimatedSize
AnimatedSwitcher

动画效果可以看下心里有个印象。

AnimatedFoo

使用方法:

AnimatedPadding(
  duration: Duration(seconds: 1),
  padding: EdgeInsets.all(_containerSelected ? 20 : 1),
  curve: Curves.fastOutSlowIn,
  child: Stack(
    children: <Widget>[
      FlutterLogo(size: 75),
      Text("AnimatedPadding"),
    ],
  ),
)

详细代码:github.com/damengzai/f…

可以看出只用传递动画时长和对应的动画变化值,就可以给child添加动效了。

需要注意的是有几个动画组件需要父组件必须是Stack;

AnimatedCrossFade更是需要父组件的宽高和AnimatedCrossFade一样,否则切换动画会有跳动,不过自己重写layoutBuilder这个方法就可以,可以详细看AnimatedImplicitWidget.dart中AnimatedCrossFade的具体使用。

AnimatedFoo继承自ImplicitlyAnimatedWidget,在改变属性的时候添加动画,首次添加到widget树的时候动画并不会执行;在重建时,如果属性值变了,会有动效的执行变化。

在动画执行过程中,每一帧都要执行build方法,在build方法中要构建每一帧的动画状态数据(即动画要改变的属性),通过evaluate(animation)获取每一帧对应的动画数据。

而开发者如果想自己实现ImplicitlyAnimatedWidget,只需要重写buildforEachTween分别获取每一帧的状态值和更新Tween的初始值。

拉官方的AnimatedPadding源码来看下,会有动效的改变padding值,State为_AnimatedPaddingState

class _AnimatedPaddingState extends AnimatedWidgetBaseState<AnimatedPadding> {
  EdgeInsetsGeometryTween _padding;

  //forEachTween初始化Tween值,其EdgeInsetsGeometryTween是自定义的Tween,生成两个EdgeInsetsGeometry之间的插值。
  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
    _padding = visitor(_padding, widget.padding, (dynamic value) => EdgeInsetsGeometryTween(begin: value));
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: _padding.evaluate(animation),//通过动画生成对应的padding
      child: widget.child,
    );
  }
}


//自定义Tween
class EdgeInsetsGeometryTween extends Tween<EdgeInsetsGeometry> {
  EdgeInsetsGeometryTween({ EdgeInsetsGeometry begin, EdgeInsetsGeometry end }) : super(begin: begin, end: end);
  @override
  EdgeInsetsGeometry lerp(double t) => EdgeInsetsGeometry.lerp(begin, end, t);//此方法会根据时间变化,生成参数插值
}

TweenAnimatedBuilder

当Flutter framework没有实现对应的组件的动效的时候,可以考虑使用TweenAnimatedBuilder去控制任何组件的动效。

TweenAnimationBuilder(
  tween: Tween<double>(begin: 0, end: _targetValue),
  duration: Duration(seconds: 1),
  builder: (BuildContext context, double value, Widget child) {
    return Container(
      width: value,
      height: value,
      child: child,
    );
  },
  child: FlutterLogo(
    size: 20,
  ),
)

builder对应的函数中,value是动画的Animation的值,即每一帧的动画值,可以更灵活的控制哪些组件的具体参数跟随者动画变化,对于framework没有实现的动画组件也可以实现。

详细代码:github.com/damengzai/f…

显式动画

如果你想更多的控制动画的运行,比方说动画的开始、结束、重复等,以及动画中间的状态,就需要使用显示动画了;考虑使用AnimatedWidget或其子类,这样的话自己管理AnimationController类。

显式动画和隐式动画最主要的不同点是是否由开发者管理AnimationController,先说一下几个动画相关的概念:

Animation:一个抽象类,保存动画执行过程中的插值和状态,常用的子类有Animation,还有Animation、Animation根据自己需要改变的属性使用不同的泛型。

AnimationController:动画控制器,初始化动画的时长、返回值区间、启动、重复动画、监听状态等相关功能。初始化动画的时候需要一个vsync参数,是一个Ticker类型的子类,绑定Ticker的目的是当动画不在屏幕中的时不再执行动画,节省性能。因为应用启动的时候会绑定一个SchedulerBinding,通过SchedulerBinder在每次屏幕刷新的时候添加回调,Ticker是通过SchedulerBinder添加回调的,当锁屏时不再刷新屏幕,Ticker不会再触发,动画也就不再执行。

Curve:动画的曲线,动画执行过程中如果中间的值的变化非线性,可以使用Curve包装AnimationController将生成曲线动画,Curve和AnimationController都是Animation类型,正是通过此种方式将动画和动画曲线关联起来,因为这样可以直接使用Curve返回值作为动画运行的参数,实现非线性动画。常用CurvedAnimation添加动画曲线。

Tween:因为默认AnimationController返回值是0-1,当返回取值范围或类型不同时,使用Tween修饰动画。值得注意的是Tween继承自Animatable。Tween提供了animation()方法,传入Animation,返回Animation,来使用返回的值控制组件的显示。

使用动画的步骤可以大体分为(并非所有步骤必须):

初始化AnimationController->[将AnimationController传入Curve,添加动画曲线]->[将AnimaitionController或Curve传入Tween生成需要的插值]->AnimationController或Curve或Tween生成的插值给到组件的参数,实现动画

Animation定义

如图中显式动画的组件,Curve、Tween都是根据需要使用。

FooTransaction

FooTransition指的是一系列组件,这些组件继承自AnimatedWidget,会根据给定的Listenable(AnimationController)变化的时候,重新构建。framework提供了有

AlignTransition
DecoratedBoxTransition
DefaultTextStyleTransition
PositionedTransition
RelativePositionedTransition
RotationTransition
ScaleTransition
SizeTransition
SlideTransition
FadeTransition
AnimatedModalBarrier

FooTransaction

下面看一个例子:

@override
void initState() {
  super.initState();
  _controller = AnimationController(
    vsync: this,
    duration: Duration(seconds: 2),
  )..repeat(reverse: true);

  _rotationDoubleAnimation = Tween<double>(
      begin: 0.0,
      end: 0.05,
    ).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.easeInOutSine,
      ),
  );
}

//使用animation,省略了部分代码...
RotationTransition(
  turns: _rotationDoubleAnimation,
  child: _ViewWidget(
    showText: 'RotationTransition',
  ),
)

可以看到开发者自己初始化AnimationController,可以通过AnimationController控制、监听动画的状态。

详细代码:github.com/damengzai/f…

需要提醒的是,并不是所有的动效参数通过Tween传递给对应的FooTransition就可以,例如PositionedTransition的rect参数需要Tween参数,这时候不能直接传递Tween过去,而是要传递封装的RelativeRectTween,RelativeRectTween继承自Tween有复写lerp方法,这个方法是子类自定义动画行为的一个方法。对于父类Tween lerp方法只有begin + (end - begin) * t,对于复杂的参数(例如:RelativeRect),参数没有重写运算符(+、-),要自己复写对应参数的运算符方法,或者继承Tween重写lerp,所以使用官方对应的FooTween更为便捷(如果有实现的话)。

AnimatedWidget

AnimatedWidget是一个抽象类,需要自己继承这个类,传递一个显示的Listenable(实现类包括Animation,ChangeNotifier,ValueNotifier等)作为其参数,通常是AnimationController驱动的Animation,AnimationController由开发者管理,对于FooTransition没有实现的动画效果,可以自己使用AnimatedWidget封装。


class _ExplicitAnimationState extends State<_ExplicitAnimationPage> with SingleTickerProviderStateMixin {
  Animation<double> animation;
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    //初始化动画控制器
    controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
    //因为动画控制器默认返回0-1之间的值,所以需要将动画的值转成需要的区间或类型
    animation = Tween<double>(begin: 10, end: 100).animate(controller);
    //添加动画控制器动画相关的属性
    controller.repeat(reverse: false);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        //使用自定义的AnimatedWidget子类
        child: _AnimatedFlutterLogo(animation: animation),
      ),
    );
  }
}

//实现AnimatedWidget子类
class _AnimatedFlutterLogo extends AnimatedWidget {
  final Animation<double> animation;

  _AnimatedFlutterLogo({Key key, @required this.animation}) : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    return Container(
      //接收外界传递过来的animation,使用动画的值控制组件宽高
      width: animation.value,
      height: animation.value,
      child: FlutterLogo(),
    );
  }
}

AnimatedWidget

详细代码:github.com/damengzai/f…

动画需要变化更复杂一点

class _CurveAnimatedFlutterLogo extends AnimatedWidget {
  final Animation<double> controller;

  _CurveAnimatedFlutterLogo({Key key, @required this.controller}) : super(key: key, listenable: controller);

  //控制组件的透明度
  final _opacityTween = Tween<double>(begin: 0.1, end: 1);
  //控制组件的大小
  final _sizeTween = Tween<double>(begin: 10, end: 200);

  @override
  Widget build(BuildContext context) {
    return Opacity(
      //直接evaluate(controller)即可获得对应的动画值
      opacity: _opacityTween.evaluate(controller),
      child: Container(
        //也可通过Tween.animate获得Animation<T>,再取对应的值
        width: _sizeTween.animate(controller).value,
        height: _sizeTween.evaluate(controller),
        child: FlutterLogo(),
      ),
    );
  }
}

这个传AnimationController进来,处理不同属性的变化。

CurvedAnimatedWidget

详细代码:github.com/damengzai/f…

AnimatedBuilder

class _AnimatedBuilderWidget extends StatelessWidget {
  _AnimatedBuilderWidget({Key key, this.child, this.animation}) : super(key: key);
  final Widget child;
  final Animation<double> animation;

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

动画效果就不在额外的贴出来了。AnimatedBuilder作为自定义性更好的组件,优势是性能更好,对于只想把动画作为一部分的复杂的组件,AnimationBuilder是一个不错的选择,如果builder包括不依赖动画的子组件,这个子组件不会每次都重建,优化了性能。

怎么选择:

对于需要额外的state的复杂组件的情况,考虑使用AnimatedBuilder,AnimatedBuilder性能更好。

AnimatedWidget和其子类,传递一个显示的Listenable(实现类包括Animation,ChangeNotifier,ValueNotifier等)作为其参数,通常是AnimationController驱动的Animation,AnimationController由开发者管理。

而ImplicitlyAnimatedWidget自动管理内部的AnimationController,更方便开发,如果只是设置一个目标值给动画,配置时间和曲线,考虑使用ImplicitlyAnimatedWidget或其子类,Flutter framework提供了一系列动画组件,FooTransition,这些动画和ImplicitlyAnimatedWidget子类不同,其子类通常是AnimatedFoo。

原则是能使用Flutter提供的就使用Flutter提供的,能使用Flutter管理AnimationController的就是有Flutter管理的隐式动画。实在比较复杂的时候再使用AnimatedWidget或AnimatedBuilder。

代码:github.com/damengzai/f…