Flutter动画分析之AnimatedWidget & ImplicitlyAnimatedWidget
本文讨论的 Flutter 动画主要限定在: 随着每一帧的变化,修改 Flutter Widget 的大小、颜色、位置等属性,使之看起来从一种状态渐变为另外一种状态 这一范围。
在之前的文章中,我们将 Flutter 中动画的实现分为 底层实现 和 封装好的 Widget 两大部分,目前已经分析了底层实现的部分:
而 Flutter 封装好的动画 Widget 主要分为两大类:
ImplicitlyAnimatedWidget 隐式动画,关于动画的开始、停止等都封装在 Widget 内部,只要 Widget 前后传入的值不同 便可以自动从 old 渐变到 new,内置的这些类主要以 AnimatedFoo 命名。
AnimatedWidget,显式动画,需要使用者自己创建 Animation(一般是 AnimationController)并通过其 _主动管理动画_,此类 Widget 主要是监听 AnimationController 的值并刷新 Widget 的内容。
此类 Widget 主要有三种使用方式:
- 继承 AnimatedWidget
- 使用 AnimatedBuilder
- 使用各种内置的 AnimatedWidget 子类,一般以 FooTransition 命名。
对于 Flutter 中这些与动画有关的类如何选择,Flutter 官方给了一张图:
简单来说,Flutter 有一些内置的动画,在要写动画的时候,可以依次考虑(实现程度由易到难):
- AnimatedFoo 参考文章,设置新的状态,这些控件会自动从之前的状态切换到新状态
- TweenAnimationBuilder 参考文章,将任意属性在 Tween 指定的范围变化,和上面的 AnimatedFoo 都是属于Implicitly animated widgets(隐式动画,由系统控件控制动画)。
- FooTranslation
- AnimatedBuilder / AnimatedWidget
- CustomPainter
本文将对 Flutter 内置封装好的动画相关的 Widget 的实现和用法进行简单分析。
源码分析
按照上述分析,Flutter 中的动画 Widget 可以大体分为 隐式动画 和 显式动画 两种。
ImplicitlyAnimatedWidget
ImplicitlyAnimatedWidgets (and their subclasses) automatically animate changes in their properties whenever they change.
隐式动画内部持有 AnimationController 以管理动画,默认没有动画,当使用不同的值重新构建 Widget 的时候,会执行动画,使用者只能设置 Duration 和 Curve,如果想要更深入的控制动画(比如暂停动画)则应该使用 AnimatedWidget。
ImplicitlyAnimatedWidget 主要分为 2 大类:
- TweenAnimationBuilder,which animates any property expressed by a Tween to a specified target value.
- AnimatedFoo
- AnimatedAlign, which is an implicitly animated version of Align.
- AnimatedContainer, which is an implicitly animated version of Container.
- AnimatedDefaultTextStyle, which is an implicitly animated version of DefaultTextStyle.
- AnimatedScale, which is an implicitly animated version of Transform.scale.
- AnimatedRotation, which is an implicitly animated version of Transform.rotate.
- AnimatedSlide, which implicitly animates the position of a widget relative to its normal position.
- AnimatedOpacity, which is an implicitly animated version of Opacity.
- AnimatedPadding, which is an implicitly animated version of Padding.
- AnimatedPhysicalModel, which is an implicitly animated version of PhysicalModel.
- AnimatedPositioned, which is an implicitly animated version of Positioned.
- AnimatedPositionedDirectional, which is an implicitly animated version of PositionedDirectional.
- AnimatedTheme, which is an implicitly animated version of Theme.
- AnimatedCrossFade, which cross-fades between two given children and animates itself between their sizes.
- AnimatedSize, which automatically transitions its size over a given duration.
- AnimatedSwitcher, which fades from one widget to another.
我们简单分析一下 ImplicitlyAnimatedWidget 和 TweenAnimationBuilder:
1 | abstract class ImplicitlyAnimatedWidget extends StatefulWidget { |
ImplicitlyAnimatedWidget.createState()
必须返回 ImplicitlyAnimatedWidgetState 或者 AnimatedWidgetBaseState 及其子类。
ImplicitlyAnimatedWidget 作为 StatefulWidget,它的主要逻辑在 ImplicitlyAnimatedWidgetState 中:
1 | abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> { |
ImplicitlyAnimatedWidgetState 中,根据传入的 Duration 和 Curve ,创建并持有了 AnimationController 和 Animation<double> 用于驱动隐式动画。
initState
在ImplicitlyAnimatedWidgetState.initState
方法中:
- 监听
_controller
的状态,当AnimationStatus.completed
时回调ImplicitlyAnimatedWidget.onEnd
方法; - 此外还调用了
_constructTweens()
遍历 Tween,并调用由子类实现的forEachTween()
方法(子类在此方法内部,获取到对应的 Tween,比如 Padding,从而在监听到_controller
变化并触发 rebuilt 时使用Animatable.evaluate()
方法获取并显示最新的属性,实现动画效果); - 最后还调用了
didUpdateTweens()
方法通知子类 Tweens 发生变化了。
_constructTweens
_constructTweens()
方法会创建一个 TweenVisitor<dynamic>并传给子类 forEachTween()
方法,子类可以使用其获取对应的 Tween 对象。
_constructTweens()
则在此过程中,使用_shouldAnimateTween()
得知了子类中是否有 Tween 可以开始动画——shouldStartAnimation
。
1 | bool _constructTweens() { |
didUpdateWidget
当 ImplicitlyAnimatedWidget 被重新创建时,会调用 ImplicitlyAnimatedWidgetState.didUpdateWidget 方法。
在此方法中,除了检查并更新 Curve、Duration、Tween 之外,最重要的是使用 AnimationController.forward()开启了动画。也就是说——“ImplicitlyAnimatedWidget 第一次插入 Widget Tree 时没有动画,当再次被更新时会触发动画”。
1 | void didUpdateWidget(T oldWidget) { |
forEachTween
子类必须实现此方法,使用传入的 TweenVisitor 创建自己对应的 Tween。
1 | void forEachTween(TweenVisitor<dynamic> visitor); |
didUpdateTweens
当 Tween 变化时会调用此方法通知子类,子类(可选)可以实现此方法。
到目前为止,我们的 AnimationController 已经控制动画开始执行,但是因为没有监听 AnimationController.value 的变化,所以还不能自动触发 ImplicitlyAnimatedWidgetState.build()方法。
为了实现动画效果,子类可以选择自己主动监听 AnimationController;或者,继承 AnimatedWidgetBaseState:
1 | abstract class AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> extends ImplicitlyAnimatedWidgetState<T> { |
ImplicitlyAnimatedWidget 的子类主要实现 AnimatedWidgetBaseState/ImplicitlyAnimatedWidgetState 的forEachTween()
和build()
方法即可。前者用于生成 Widget 所需的 Tween;后者则使用生成的 Tween<T>的evaluate(animation)
方法计算对应的属性并展示。
以 AnimatedPadding 为例,它继承自 ImplicitlyAnimatedWidget,创建的_AnimatedPaddingState 继承自 AnimatedWidgetBaseState<AnimatedPadding>:
1 | class _AnimatedPaddingState extends AnimatedWidgetBaseState<AnimatedPadding> { |
上述分析是继承 ImplicitlyAnimatedWidget 实现隐式动画的常用流程,Flutter 中内置的 AnimatedFoo 动画都是类似实现。
TweenAnimationBuilder
ImplicitlyAnimatedWidget 的子类(以 AnimatedFoo 命名的一众子类)提供了常见的动画效果,但是如果有特殊的动画效果需要实现,除了直接继承 ImplicitlyAnimatedWidget 之外,还可以使用 TweenAnimationBuilder 并传入 Tween 来实现:
1 | return TweenAnimationBuilder<double>( |
当 Widget 首次 build 的时候就会触发动画从 Tween.begin 过渡到 Tween.end;当再次提供一个有新 end 的 Tween 也可以随时触发新动画(新动画从动画当前值开始)。
需要注意:
- 传入到 TweenAnimationBuilder 中的 Tween 被其持有(可能修改),所以不应当再操作它;
- 当动画执行完毕会调用 TweenAnimationBuilder.onEnd 方法;
- 为了性能,应当将不需要每次更新的 subtree 传入到 TweenAnimationBuilder.child 中避免重绘。
和其他 ImplicitlyAnimatedWidget 的子类一样,TweenAnimationBuilder 的主要逻辑也在继承自 AnimatedWidgetBaseState 的_TweenAnimationBuilderState 中:
1 | class _TweenAnimationBuilderState<T extends Object?> extends AnimatedWidgetBaseState<TweenAnimationBuilder<T>> { |
使用时需要注意,只有传入 TweenAnimationBuilder 的 Tween 是一个新的、并且 end 值和之前不一样的才会触发动画。如果 end 值一样则无动画、如果不是新的则 builder 的内容只会突然变化为 end 值对应状态而无动画。
AnimatedWidget
之前分析的 TweenAnimationBuilder 以及 ImplicitlyAnimatedWidget 的其他子类,基本上都只能定义动画的 Tween、Duration、Curve 等,动画的开始结束动都由这些 Widget 内部控制。
如果需要手动主动控制动画,可以选择使用 显式动画 —— AnimatedWidget 及其子类:
其同样也分为 2 大类:
- AnimatedBuilder, which is useful for complex animation use cases and a notable exception to the naming scheme of AnimatedWidget subclasses.
- FooTransition 子类
- AlignTransition, which is an animated version of Align.
- DecoratedBoxTransition, which is an animated version of DecoratedBox.
- DefaultTextStyleTransition, which is an animated version of DefaultTextStyle.
- PositionedTransition, which is an animated version of Positioned.
- RelativePositionedTransition, which is an animated version of Positioned.
- RotationTransition, which animates the rotation of a widget.
- ScaleTransition, which animates the scale of a widget.
- SizeTransition, which animates its own size.
- SlideTransition, which animates the position of a widget relative to its normal position.
- FadeTransition, which is an animated version of Opacity.
- AnimatedModalBarrier, which is an animated version of ModalBarrier.
AnimatedWidget 比 ImplicitlyAnimatedWidget 简单许多,其接受一个 Listenable 对象,在_AnimatedState 中监听其并触发 rebuilt。
1 | abstract class AnimatedWidget extends StatefulWidget { |
AnimatedWidget.listenable
通常是 AnimationController,当然也可以是其他实现 Listenable 的类(including ChangeNotifier and ValueNotifier)。
_AnimatedState
AnimatedWidget 的主要逻辑在对应的_AnimatedState 中:
1 | class _AnimatedState extends State<AnimatedWidget> { |
可以看到,相对于隐式动画 ImplicitlyAnimatedWidget,显示动画 AnimatedWidget 的逻辑要简单的多,只是监听传入的 Listenable 并触发 rebuilt 即可。对于动画的控制则由 Listenable(通常是 AnimationController)处理。
也就是说,显示动画 AnimatedWidget 只是替子类做了监听/移除监听 Listenable 的值变化,并触发 rebuilt 的工作,如何获取变化的值,以及展示对应的 Widget 则需要子类自己处理。
他的子类 FooTransition 实现逻辑也比较简单,只需要在在 Widget.build 根据不同的状态创建创建不同属性的 Widget 即可:
1 | class RotationTransition extends AnimatedWidget { |
AnimatedBuilder
一般来说,Flutter 内置的以 FooTransition 命名的 AnimatedWidget 的子类可以满足基本的需求,但是如果想要实现更复杂的效果,除了直接继承 AnimatedWidget 之外,还可以使用 AnimatedBuilder 实现丰富的动画:
1 | AnimatedBuilder( |
而 AnimatedBuilder 的实现也比较简单:
1 | class AnimatedBuilder extends AnimatedWidget { |
总结
ImplicitlyAnimatedWidget 隐式动画,内部创建并监听 AnimationController 以维护动画,控制动画的开始和结束,用户可以通过传入 Duration、Curve、Tween 等决定动画的时长、曲线、开始和结束值等,当动画相关的属性变化时,隐式动画会自动播放,使用者不可以直接控制动画。
Flutter 内置的隐式动画为 TweenAnimationBuilder 和 AnimatedFoo。
AnimatedWidget 显式动画,接受 Listenable(通常是 AnimationController)并监听其值变化,以触发 Widget 重新 build,其子类中一般会监听 Listenable 的值并计算设置 Widget 对应的属性。使用者需要负责创建、控制 Listenable 从而控制动画播放。Flutter 内置的显式动画为 AnimatedBuilder 和 FooTransition。
在使用Flutter实现Widget动画时,可以按照以下顺序选择实现方式:
- AnimatedFoo,选择内置的隐式动画,以实现当Padding、Alignment等属性变化时自动渐变到新值的动画效果。
- TweenAnimationBuilder/继承ImplicitlyAnimatedWidget,当上一步无法满足需求时,可以考虑进一步自定义实现隐式动画。
- FooTransition,如果不止要展示动画,还希望能够控制动画开始、结束,就使用内置的显式动画结合自己创建的AnimationController实现动画。
- AnimationBuilder/AnimatedWidget,如果没有满足条件的内置显式动画,可以使用自定义实现显式动画。
- CustomPainter,如果上述方法仍然无法满足动画需求,可以考虑使用CustomPainter自己绘制动画。