-
概述
在Flutter的使用过程中,我们总是会调用addListener方法添加每一帧的回调监听,并且动画都是体现在UI变化上的,所以在监听中我们通常要调用setState方法重新刷新UI,从而使得控件对应属性重新引用animation.value的值,这块逻辑通常都是必须的,因为你基本上不可能应用动画但是不让他刷新UI,这样动画就失去了意义。
另外,动画的构建过程也是大同小异,比如,我们都至少需要一个AnimationController,都可能需要Tween和Curve,都需要duration等。
Flutter框架中有这样的类帮我们封装了这部分逻辑,我们来看一下它们的源码。
-
ImplicitlyAnimatedWidget
凡是需要封装刷新逻辑的,通常需要继承这个类,为什么要继承呢?我这里用的是“通常”,而不是必须,其实如果要追究的话,所有的东西其实你都可以自己封装,之所以继承这个类是因为方便,不要“重复造轮子”,我们只是需要知其所以然。
先看一下一些经常使用的子类:
[TweenAnimationBuilder], which animates any property expressed by a [Tween] to a specified target value. [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.
这个类有一些基本的属性:
const ImplicitlyAnimatedWidget({ Key? key, this.curve = Curves.linear, required this.duration, this.onEnd, })
最重要的还是它的createState方法限制了State类型必须是ImplicitlyAnimatedWidgetState:
@override ImplicitlyAnimatedWidgetState
createState(); 作为规范,ImplicitlyAnimatedWidgetState要求Widget必须是ImplicitlyAnimatedWidget类型。
我们来看ImplicitlyAnimatedWidgetState中封装了那些内容。
-
ImplicitlyAnimatedWidgetState
@protected AnimationController get controller => _controller; late final AnimationController _controller = AnimationController( duration: widget.duration, debugLabel: kDebugMode ? widget.toStringShort() : null, vsync: this, ); /// The animation driving this widget's implicit animations. Animation
get animation => _animation; late Animation _animation = _createCurve(); CurvedAnimation _createCurve() { return CurvedAnimation(parent: _controller, curve: widget.curve); } 首先我们看到,它内部持有一个AnimationController还有一个CurvedAnimation,可能有人会问,那这样岂不是强制把AnimationController绑定在CurvedAnimation上了吗,如果我不需要Curve,我想要直接使用AnimationController或者把绑定在Tween上呢?其实很简单就能说通,即时你不使用Curve,系统的屏幕刷新频率是固定的,在你不使用任何包装类的情况下它默认速度其实就是线性变化的,这和你指定Curve.linear是完全一样的效果,而通过继承ImplicitlyAnimatedWidget,它默认指定的curve属性就是Curve.linear,这也是ImplicitlyAnimatedWidget的另一个作用,就是初始化一些必须的值。
其次ImplicitlyAnimatedWidgetState依赖了SingleTickerProviderStateMixin,所以我们自己的State也省去了vsync这一步。
接下来我们看initState方法:
@override void initState() { super.initState(); _controller.addStatusListener((AnimationStatus status) { switch (status) { case AnimationStatus.completed: widget.onEnd?.call(); break; case AnimationStatus.dismissed: case AnimationStatus.forward: case AnimationStatus.reverse: } }); _constructTweens(); didUpdateTweens(); }
可以看到,initState中添加了一个StatusListener,用来处理动画结束时的回调,ImplicitlyAnimatedWidget构造中只提供了动画结束时的回调接口设置入口,所以默认只能监听动画结束,但是ImplicitlyAnimatedWidgetState提供了controller方法来获取_controller,所以有需要的话你完全可以设置其他的。
接着会调用_constructTweens方法:
bool _constructTweens() { bool shouldStartAnimation = false; forEachTween((Tween
? tween, dynamic targetValue, TweenConstructor constructor) { if (targetValue != null) { tween ??= constructor(targetValue); if (_shouldAnimateTween(tween, targetValue)) shouldStartAnimation = true; } else { tween = null; } return tween; }); return shouldStartAnimation; } 这个方法稍微有些深度,我们来看forEachTween方法:
@protected void forEachTween(TweenVisitor
visitor); 这个方法在哪实现的呢?我们上面说到,ImplicitlyAnimatedWidgetState有很多子类,我们以_AnimatedPaddingState为例看看它的forEachTween方法:
@override void forEachTween(TweenVisitor
visitor) { _padding = visitor(_padding, widget.padding, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?; } 这个_padding是 _AnimatedPaddingState要用来应用到Widget的变化属性值,显然它是根据visitor函数生成的,而这里的visitor我们知道是在调用 _constructTweens方法中调用forEachTween时传入的,回到 _constructTweens方法,tween就是这里的 _padding,targetValue就是这里的widget.padding,constructor就是(dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?。所以我们可以整理一下逻辑:外面State要变化的属性是需要自定义的,因此它的Tween的构造方法也是需要自定义的,目标属性值是它的widget指定的,但是创建逻辑是由ImplicitlyAnimatedWidgetState封装好的。由此我们也可以知道,forEachTween方法是用来创建变化属性的Tween的。
didUpdateTweens方法是用来更新Tween的,没有默认实现,只是提供了一个可以在构造完Tween 之后做一些事情的接口,你可以按照需求实现自己的逻辑。
接下来看一下didUpdateWidget方法:
@override void didUpdateWidget(T oldWidget) { super.didUpdateWidget(oldWidget); if (widget.curve != oldWidget.curve) { (_animation as CurvedAnimation).dispose(); _animation = _createCurve(); } _controller.duration = widget.duration; if (_constructTweens()) { forEachTween((Tween
? tween, dynamic targetValue, TweenConstructor constructor) { _updateTween(tween, targetValue); return tween; }); _controller ..value = 0.0 ..forward(); didUpdateTweens(); } } 这个方法只有在widget.canUpdate方法返回true的时候(也即是runtimeType或Widget.key有变化的时候)才会被系统调用,所以在这个方法里做一些对动画配置可能发生变化的更新操作。
前面的就不说了,说一下 _constructTweens的返回值,前面可以看到 _constructTweens的返回值是shouldStartAnimation,它是否能返回true是根据 _shouldAnimateTween方法决定的:
bool _shouldAnimateTween(Tween
tween, dynamic targetValue) { return targetValue != (tween.end ?? tween.begin); } 可见,只要当前动画进度值不是在两个端点就会返回true。
再看 _updateTween方法:
void _updateTween(Tween
? tween, dynamic targetValue) { if (tween == null) return; tween ..begin = tween.evaluate(_animation) ..end = targetValue; } 这个方法会使Widget配置发生变化后还能从之前动画执行的当前进度值继续变化。而这里也会调用 _controller的forward方法使动画继续。
所以,结合_shouldAnimateTween方法,整体就是动画当前进度值正在进行中的时候才会调用forward使动画继续,如果在起点或者终点的话是不会自动开启的,同样在端点的时候Tween也不需要根据动画的进度值去设置起点属性值,这也能看出框架设计者的细节之处。
当然,ImplicitlyAnimatedWidgetState也封装了动画的释放工作:
@override void dispose() { (_animation as CurvedAnimation).dispose(); _controller.dispose(); super.dispose(); }
-
AnimatedWidgetBaseState
现在还有一个点没有提到,就是刷新UI的逻辑,它就在ImplicitlyAnimatedWidgetState的另一个子类AnimatedWidgetBaseState中。
abstract class AnimatedWidgetBaseState
extends ImplicitlyAnimatedWidgetState { @override void initState() { super.initState(); controller.addListener(_handleAnimationChanged); } void _handleAnimationChanged() { setState(() { /* The animation ticked. Rebuild with new animation value */ }); } } 这个类非常简单,不言而喻,不再赘述。
-
AnimatedWidget
有人说我想要完全自定义的AnimationController和Tween,只是Listener调用State部分封装就好了,那么有没有这样的封装类呢?答案是有的,他就是AnimatedWidget。
AnimatedWidget的State是_AnimatedState:
@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. }); }
可以看到只是把刷新UI回调这一步给封装了。
-
AnimatedBuilder
在上面的例子中,调用setState方法都会引起整个组件树的构建,出于性能考虑,有了AnimatedBuilder。
class AnimatedBuilder extends AnimatedWidget { /// Creates an animated builder. /// /// The [animation] and [builder] arguments must not be null. const AnimatedBuilder({ Key? key, required Listenable animation, required this.builder, this.child, }) : assert(animation != null), assert(builder != null), super(key: key, listenable: animation); /// Called every time the animation changes value. final TransitionBuilder builder; final Widget? child; @override Widget build(BuildContext context) { return builder(context, child); } }
可以看到,AnimatedBuilder是继承自AnimatedWidget的,所以它也可以自动刷新,只不过多了个builder函数参数,build方法会返回它的返回值,这就把构建范围缩小到了应用动画的组件范围,调用setState时就不会调用更上层组件的build方法了,极大的节省了渲染效率。
-
总结
经过源码的阅读,我们知道了Flutter是怎样通过封装简化我们的动画使用步骤的,继承自ImplicitlyAnimatedWidget保证了我们的State必须是ImplicitlyAnimatedWidgetState类型的,为什么不是AnimatedWidgetBaseState呢?我想是为了灵活性,因为ImplicitlyAnimatedWidgetState里封装的逻辑都是必须要有的通用逻辑,而刷新UI可能不需要(虽然我想不出应用场景...),当然我们如果想要应用动画通常继承AnimatedWidgetBaseState就好。另外,ImplicitlyAnimatedWidget也会保证ImplicitlyAnimatedWidgetState中一些必须初始化的属性一定有值,比如curve。
总之,ImplicitlyAnimatedWidgetState中涵盖了AniamtionController、CurveAnimation、Tween所有的可能需要的动画配置,我们只需要传递对应的参数就好;而AnimatedWidgetBaseState中封装了动画执行过程中刷新UI的逻辑。