动画也是UI组成的一部分,好的动画能够给用户更好的体验(动效工程师的要求,不得不做)。动画的本质其实就是一组不同的图像,连续不断的进行播放,从而体现出动态效果。例如要实现一个圆形的逐渐变大的动画效果,实际上就是将它的半径逐渐变大,然后在每一帧绘制时按照半径进行绘制就实现了。
动画 在Android中,做动画其实也是生成一组连续变化的数值,然后设置给对应的View,我们最常用的就是ValueAnimator或ObjectAnimator,他们本身即是动画的控制器,又是动画的执行者。而在Flutter中,将动画分为了两个部分:Animatable和Animation。
Animatable:控制动画的值,即对于动画数值的计算
Animation:控制动画的执行,即开始暂停等操作
Animatable 在Flutter中,通过这种形式做动画的叫做补间动画,因为它的实现类就是Tween。它所做的,就是根据动画的进度来计算实际的值。例如需要在1秒钟之内将某个圆的半径逐渐从0扩大到60,那么对于刷新率为60帧的手机,将会在每次刷新时计算新值,也就是每16ms这个值增加1。
要实现每16ms计算一次,就必须要能够知道什么时候屏幕刷新,即需要一个vsync信号,每次收到信号后开始计算,这样对于不同屏幕刷新率的手机而言,动画也是通用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 abstract class Animatable <T > { T transform(double t); T evaluate(Animation<double > animation) => transform(animation.value); Animation<T> animate(Animation<double > parent) { return _AnimatedEvaluation<T>(parent, this ); } }
以上是摘抄的部分的Animatable的源码,它定义了常用的三个方法。一个是transform接收的是一个double的值,这个参数就是动画的进度。实际上的计算过程是Animation接收vsync信号触发计算,然后根据时间计算出当前动画的进度,再交由给Animatable计算新值。如果我们想要实现自定义的计算逻辑,只需要重写这个方法即可。
eveluate用于评估当前动画对应的进度的值,它的参数是一个Animation,实际的逻辑就是直接调用了transform方法。一般不要重写这个方法。
animate方法代表构建一个动画,它接受一个Animation对象,然后将二者进行组合,这样整个动画就完整了:有计算新值的Animatable部分,有控制动画执行暂停的Animation部分。
Animation Animation控制动画的执行,接收vsync信号,根据时间计算进度值等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 abstract class Animation <T > extends Listenable implements ValueListenable <T > { @override void addListener(VoidCallback listener); void removeListener(VoidCallback listener); void addStatusListener(AnimationStatusListener listener); void removeStatusListener(AnimationStatusListener listener); @override T get value; AnimationStatus get status; bool get isDismissed => status.isDismissed; bool get isCompleted => status.isCompleted; bool get isAnimating => status.isAnimating; bool get isForwardOrCompleted => status.isForwardOrCompleted; @optionalTypeArgs Animation<U> drive<U>(Animatable<U> child) { assert (this is Animation<double >); return child.animate(this as Animation<double >); } }
Animation实际上是一个老朋友,也是我们在状态管理中常用的Listenable,并且还是一个特殊的ValueListenable,其中的value也就是我们实际的动画值。
然后就是老一套添加和移除监听,比较特殊的是它还额外增加了对于动画状态的添加和移除监听。
最后是drive方法,翻译过来就是驱动,通过Animation来驱动一个Animatable,从而完成值的变化。看它的实现其实就是直接调用了Animatable的animate方法。
1 2 3 Animation<T> animate(Animation<double > parent) { return _AnimatedEvaluation<T>(parent, this ); }
就是通过_AnimatedEvaluation将二者合并起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class _AnimatedEvaluation <T > extends Animation <T > with AnimationWithParentMixin <double > { _AnimatedEvaluation(this .parent, this ._evaluatable); @override final Animation<double > parent; final Animatable<T> _evaluatable; @override T get value => _evaluatable.evaluate(parent); }
虽然新的_AnimatedEvaluation也是继承了Animation,但它的实现由AnimationWithParentMixin混入进行实现,实现逻辑其实就是定义一个parent,然后方法全部由parent实现,类似于代理模式,即Animation的示例由parent代理实现。
而parent则是通过构造方法传入的,它们也没做什么,就是获取值的时候是走的evaluate方法。
Tween 其实到这里整个逻辑就很清晰了,Animation通过调用Animatable#evaluate来计算新值。**也就是说Animatable实际上与Android动画中的估值器是一样的。**继续回到Animatable,它的一个重要实现就是Tween,也称为补间动画:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Tween <T extends Object ?> extends Animatable <T > { T? begin; T? end; Tween({this .begin, this .end}); @override T transform(double t) { if (t == 0.0 ) { return begin as T; } if (t == 1.0 ) { return end as T; } return lerp(t); } @protected T lerp(double t) { ... return (begin as dynamic ) + ((end as dynamic ) - (begin as dynamic )) * t as T; } }
Tween定义了两个属性,begin和end代表着动画的起始值与终点值,而计算过程就更加简单了直接根据进度t来计算:lerp(t) = begin + (end - begin) * t,也就是普通的线性计算。这里注意lerp其实前面加了很多的assert,因为它是直接线性计算的,但是它的泛型又只是限定为Object,因此如果要想使用Tween,则必须传入的是实现了+、-、*三个操作符的对象(通常是基本变量)。否则的话,就需要继承自Tween然后自己实现lerp函数。
ReverseTween Tween的子类之一,反转的Tween。
1 2 3 4 5 6 7 8 9 class ReverseTween <T extends Object ?> extends Tween <T > { ReverseTween(this .parent) : super (begin: parent.end, end: parent.begin); final Tween<T> parent; @override T lerp(double t) => parent.lerp(1.0 - t); }
ColorTween 用于实现颜色变化的Tween:
1 2 3 4 5 6 7 8 class ColorTween extends Tween <Color ?> { ColorTween({super .begin, super .end}); @override Color? lerp(double t) => Color.lerp(begin, end, t); }
SizeTween 用于实现对Size变化的Tween:
1 2 3 4 5 6 7 8 class SizeTween extends Tween <Size ?> { SizeTween({super .begin, super .end}); @override Size? lerp(double t) => Size.lerp(begin, end, t); }
RectTween 用于实现对Rect变化的Tween:
1 2 3 4 5 6 7 8 class RectTween extends Tween <Rect ?> { RectTween({super .begin, super .end}); @override Rect? lerp(double t) => Rect.lerp(begin, end, t); }
IntTween & StepTween 用于实现对int变化的Tween:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class IntTween extends Tween <int > { IntTween({super .begin, super .end}); @override int lerp(double t) => (begin! + (end! - begin!) * t).round(); }class StepTween extends Tween <int > { StepTween({super .begin, super .end}); @override int lerp(double t) => (begin! + (end! - begin!) * t).floor(); }
这两个Tween逻辑都一样,也都是取的int泛型,它们的区别就是一个是对结果值四舍五入,一个是对结果值取整。如果想保留原始值,则需要用DoubleTween(不存在这个 ,因为默认的Tween就可以直接传入double)。
ConstantTween 对常量做变化的Tween(实际上没有任何变化,这个类有什么使用场景呢?)。
1 2 3 4 5 6 7 8 class ConstantTween <T > extends Tween <T > { ConstantTween(T value) : super (begin: value, end: value); @override T lerp(double t) => begin as T; }
CurveTween 实现了一个曲线Curve的变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class CurveTween extends Animatable <double > { CurveTween({required this .curve}); Curve curve; @override double transform(double t) { if (t == 0.0 || t == 1.0 ) { return t; } return curve.transform(t); } }
注意这个CurveTween比较特殊,因为它代表的本身就是一个曲线。前面的各种Tween中值的变化跟进度t都是线性的,而Curve本身代表的一个曲线,或者说是一个函数,这个函数可以是线性的也可以是非线性的,具体根据它内部的transform实现。
但是这个Curve又是一个特殊的曲线,它要求当进度为0时,值为0,进度为1时,值为1。也就是说,我使用CurveTween时,整个值的变化肯定是从0到1,但是具体中间值如何就不一定了,可能是0到1之间的数,也可能大于1或者小于0。
AnimationController 前面看了Animatable的一些实现类,其实就是各种Tween,它们主要逻辑就是通过进度t来计算出一个新值。那么回到Animation中,看下进度t是如何被获取到的。AnimationController就是它的一个实现类:
1 2 3 4 5 6 7 class AnimationController extends Animation <double > with AnimationEagerListenerMixin , AnimationLocalListenersMixin , AnimationLocalStatusListenersMixin { ... }
Animation本质上也是一个泛型类,因为它是继承自Listenable并且实现了ValueListenable的。这里的AnimationController也可以看出,它是作为动画的控制器来实现的,因此它将泛型固定成了double,而这个double类型的值,就是它当前动画的进度。
这里先不看具体的实现,先看下它混入的三个混入类:
1 2 3 4 5 6 7 8 9 10 11 12 mixin AnimationEagerListenerMixin { @protected void didRegisterListener() {} @protected void didUnregisterListener() {} @mustCallSuper void dispose() {} }
第一个混入类没什么可看的,主要做的就是定义几个方法用于注册和反注册时的回调,当然它的实现全部是空实现。然后看下一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 mixin AnimationLocalListenersMixin { final HashedObserverList<VoidCallback> _listeners = HashedObserverList<VoidCallback>(); @protected void didRegisterListener(); @protected void didUnregisterListener(); void addListener(VoidCallback listener) { didRegisterListener(); _listeners.add(listener); } void removeListener(VoidCallback listener) { final bool removed = _listeners.remove(listener); if (removed) { didUnregisterListener(); } } @protected void clearListeners() { _listeners.clear(); } @protected @pragma ('vm:notify-debugger-on-exception' ) void notifyListeners() { final List <VoidCallback> localListeners = _listeners.toList(growable: false ); for (final VoidCallback listener in localListeners) { InformationCollector? collector; try { if (_listeners.contains(listener)) { listener(); } } catch (exception, stack) } } } }
在AnimationLocalListenersMixin混入类中,实现的是监听者的注册与反注册和通知逻辑,其实就是实现的Listenable的逻辑。当混入这个类后,在AnimationController中就不需要重新实现这些逻辑了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 mixin AnimationLocalStatusListenersMixin { final ObserverList<AnimationStatusListener> _statusListeners = ObserverList<AnimationStatusListener>(); @protected void didRegisterListener(); @protected void didUnregisterListener(); void addStatusListener(AnimationStatusListener listener) { didRegisterListener(); _statusListeners.add(listener); } void removeStatusListener(AnimationStatusListener listener) { final bool removed = _statusListeners.remove(listener); if (removed) { didUnregisterListener(); } } @protected void clearStatusListeners() { _statusListeners.clear(); } @protected @pragma ('vm:notify-debugger-on-exception' ) void notifyStatusListeners(AnimationStatus status) { final List <AnimationStatusListener> localListeners = _statusListeners.toList(growable: false ); for (final AnimationStatusListener listener in localListeners) { try { if (_statusListeners.contains(listener)) { listener(status); } } catch (exception, stack) { } } } }
实际上这三个混入类是共同作用的,它们实现了Animation中的监听者逻辑、状态监听者逻辑,以及引入了这两个监听者在添加和移除时的回调函数。通过混入类的定义,可以将Animation的实现拆分,使得功能界限更加清晰,提高了复用性,也可以让AnimationController只关注它要关注的部分。
重新回到Animation,它定义了内容中,处理值监听与状态监听的逻辑由混入类完成了,就还剩值处理与状态处理,以及进度的计算,这部分逻辑都是在AnimationController中实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class AnimationController extends Animation <double > { AnimationController({ ... required TickerProvider vsync, }) : assert (upperBound >= lowerBound), ... _ticker = vsync.createTicker(_tick); ... void _tick(Duration elapsed) { _lastElapsedDuration = elapsed; final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration .microsecondsPerSecond; _value = clampDouble(_simulation!.x(elapsedInSeconds), lowerBound, upperBound); if (_simulation!.isDone(elapsedInSeconds)) { _status = (_direction == _AnimationDirection.forward) ? AnimationStatus.completed : AnimationStatus.dismissed; stop(canceled: false ); } notifyListeners(); _checkStatusChanged(); } }
对于进度的计算,实际上就是注册vsync信号的监听,当vsync信号到达时会触发对应的回调,从而在回调中处理进度计算逻辑。这里需要通过构造方法传入TickerProvider实例,从而调用它的createTicker方法创建一个Ticker并传入回调方法,当Ticker#start时,就会进行注册,每当接收到vsync信号后就会触发回调方法。
TickerProvider的提供基本上都是通过SingleTickerProviderStateMixin或者TickerProviderStateMixin的混入来实现的,即我们正常只需要在State上混入这两个类中的一个就可以了。它们限定了混入的类必须是State类,也是基于此 ,动画通常需要在有状态组件中的State里面实现。
1 2 3 mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider { ... }
当然,如果我们使用GetX框架的话,它也为我们提供了GetTickerProviderStateMixin和GetSingleTickerProviderStateMixin这两个混入类,它们限定的混入基类是GetxController,这样我们就可以在Controller中作动画的实现了。
而在回调方法中,值的计算实际上又是交由_simulation.x()来进行计算的。所以先看下Simulation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 abstract class Simulation { Simulation({this .tolerance = Tolerance.defaultTolerance}); double x(double time); double dx(double time); bool isDone(double time); Tolerance tolerance; @override String toString() => objectRuntimeType(this , 'Simulation' ); }
它是在动画开启时构建实例的,即在AnimationController.formard()时创建的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class AnimationController extends Animation <double > { TickerFuture forward({double? from}) { ... return _animateToInternal(upperBound); } TickerFuture _animateToInternal( double target, { Duration? duration, Curve curve = Curves.linear, }) { ... Duration? simulationDuration = duration; return _startSimulation( _InterpolationSimulation(_value, target, simulationDuration, curve, scale), ); } ... }
在启动动画的逻辑forward中,最终会创建一个_InterpolationSimulation的示例来执行动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class _InterpolationSimulation extends Simulation { _InterpolationSimulation(this ._begin, this ._end, Duration duration, this ._curve, double scale) : assert (duration.inMicroseconds > 0 ), _durationInSeconds = (duration.inMicroseconds * scale) / Duration .microsecondsPerSecond; final double _durationInSeconds; final double _begin; final double _end; final Curve _curve; @override double x(double timeInSeconds) { final double t = clampDouble(timeInSeconds / _durationInSeconds, 0.0 , 1.0 ); return switch (t) { 0.0 => _begin, 1.0 => _end, _ => _begin + (_end - _begin) * _curve.transform(t), }; } @override double dx(double timeInSeconds) { final double epsilon = tolerance.time; return (x(timeInSeconds + epsilon) - x(timeInSeconds - epsilon)) / (2 * epsilon); } @override bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds; }
即在Simulation中,还是以时间为单位,计算进度。但这样的进度其实是属于线性进度,于是它又通过Curve将线性进度转换成Curve对应的曲线进度。到这里,能够拿到进度后,获取值就简单了,直接将进度传入Animatable中由其根据进度计算新值。
1 2 3 4 5 6 7 8 9 10 AnimationController({ double? value, this .duration, this .reverseDuration, this .debugLabel, this .lowerBound = 0.0 , this .upperBound = 1.0 , this .animationBehavior = AnimationBehavior.normal, required TickerProvider vsync, })
在构造方法中,我们可以传入的参数非常多,都是与动画相关的,一般情况下我们只需要传duration时长、vsync提供者即可。简单总结下,AnimationController就是一个简单的动画控制器,它的主要作用就是监听vsync信号,然后在收到信号后计算进度值,所以它变化的一直都是进度值,即0~1范围内进行变化,如果想要对应到具体的值,则需要再配合Animatable。
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 final animCtrl = AnimationController( duration: Duration (seconds: 2 ), vsync: this , );final intAnimation = IntTween(begin: 10 , end: 100 ).animate(animCtrl);final colorAnimation = ColorTween( begin: Colors.red, end: Colors.blue, ).animate(animCtrl); animCtrl ..addListener(() { print ('int = ${intAnimation.value} , color = ${colorAnimation.value} ' ); }) ..addStatusListener((status) { print ('status change to ${status} ' ); }) ..forward();
其实在整个动画逻辑中,AnimtionController作为动画控制器能够响应时间的线性变化,它内部的Curve作为估值器来根据时间的线性变化计算出进度的变化,Animatable作为插值器根据进度的变化来计算出值的变化。
AnimationController内部的Curve默认是线性的,并且没有暴露给我们自定义,因此我们需要通过别的方式引入Curve。
CurvedAnimation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class CurvedAnimation extends Animation <double > with AnimationWithParentMixin <double > { CurvedAnimation({required this .parent, required this .curve, this .reverseCurve}) { ... } ... @override double get value { final Curve? activeCurve = _useForwardCurve ? curve : reverseCurve; final double t = parent.value; if (activeCurve == null ) { return t; } if (t == 0.0 || t == 1.0 ) { return t; } return activeCurve.transform(t); } ... }
这里只看获取value的方法,其实也没做什么,就是在拿到进度值后,再通过Curve进行转换,这样我们就可以使用自定义的Curve了。
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 final animCtrl = AnimationController( duration: Duration (seconds: 2 ), vsync: this , );final curvedAnim = CurvedAnimation(parent: animCtrl, curve: Curves.linear);final intAnimation = IntTween(begin: 10 , end: 100 ).animate(curvedAnim);final colorAnimation = ColorTween( begin: Colors.red, end: Colors.blue, ).animate(curvedAnim); animCtrl ..addListener(() { print ('int = ${intAnimation.value} , color = ${colorAnimation.value} ' ); }) ..addStatusListener((status) { print ('status change to ${status} ' ); }) ..forward();
其实变化的部分就是在animCtrl之后又定义了一个CurvedAnim,并且其他Animatable直接与curvedAnim关联,而不是直接与animCtrl关联。
Curve Curve是一个曲线类,它内部实际上定义的是一个函数,这个函数的输入x的取值范围是0 ≤ X ≤ 1,输出值f(x)的起点是0,终点是1,但是中间值可能会小于0或者大于1。
1 2 3 4 5 6 7 8 9 10 11 12 abstract class Curve extends ParametricCurve <double > { ... @override double transform(double t) { if (t == 0.0 || t == 1.0 ) { return t; } return super .transform(t); } ... }
它内部已经为我们定义了很多的Curve,我们可以通过Curves来直接使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [Curves.fastLinearToSlowEaseIn] [Curves.ease] [Curves.easeIn] [Curves.easeInToLinear] [Curves.easeInSine] [Curves.easeInQuad] [Curves.easeInCubic] [Curves.easeInQuart] [Curves.easeInQuint] [Curves.easeInExpo] [Curves.easeInCirc] [Curves.easeInBack] [Curves.easeOut] [Curves.linearToEaseOut] [Curves.easeOutSine] [Curves.easeOutQuad] [Curves.easeOutCubic] [Curves.easeOutQuart] [Curves.easeOutQuint] [Curves.easeOutExpo] [Curves.easeOutCirc] [Curves.easeOutBack] [Curves.easeInOut] [Curves.easeInOutSine] [Curves.easeInOutQuad] [Curves.easeInOutCubic] [Curves.easeInOutQuart] [Curves.easeInOutQuint] [Curves.easeInOutExpo] [Curves.easeInOutCirc] [Curves.easeInOutBack] [Curves.fastOutSlowIn] [Curves.slowMiddle]
实际上我们用这些并不多,线性的可能用的比较多一些,其他的就需要看动效工程师给我们的参数来自定义Curve了。
使用动画 其实前面的几个实例已经表明了如何去使用动画,只是没有实际应用到界面中去,这里简单展示一个用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class MyAnimationWidget extends StatefulWidget { const MyAnimationWidget({super .key}); @override State<MyAnimationWidget> createState() => _MyAnimationWidgetState(); }class _MyAnimationWidgetState extends State <MyAnimationWidget > with TickerProviderStateMixin { double _size = 100 ; Color _color = Colors.blue; void _startAnim() { final animCtrl = AnimationController( duration: Duration (seconds: 2 ), vsync: this , ); final curveAnim = CurvedAnimation(parent: animCtrl, curve: Curves.linear); final sizeAnim = Tween<double >(begin: 100 , end: 300 ).animate(curveAnim); final colorAnimation = ColorTween( begin: Colors.blue, end: Colors.red, ).animate(curveAnim); animCtrl ..addListener(() { setState(() { _size = sizeAnim.value; _color = colorAnimation.value!; }); }) ..forward(); } @override Widget build(BuildContext context) { return Container( width: _size, height: _size, color: _color, child: TextButton(onPressed: _startAnim, child: Text('Animation' )), ); } }
这种方式属于比较底层的用法,主要就是触发动画去对状态值做一个逐渐变化的效果,然后触发刷新使界面进行响应,由此形成动画。并且,还需要在每个动画执行的地方自定义一个有状态组件,非常麻烦。而Flutter则帮我们内置了很多常用的组件可以让我们直接进行动画,也就是常说的隐式动画:
隐式动画说的就是ImplicitlyAnimatedWidget,其实就是将动画的执行细节封装在了内部,这样我们其实只需要传入几个关键的属性就能完成动画的执行效果,而不需要重头去写AnimationController、Tween或者CurvedAnimation了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 abstract class ImplicitlyAnimatedWidget extends StatefulWidget { const ImplicitlyAnimatedWidget({ super .key, this .curve = Curves.linear, required this .duration, this .onEnd, }); final Curve curve; final Duration duration; final VoidCallback? onEnd; @override ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState }
也就是使用隐式动画实际上只需要传入curve和时长即可,其他的都由隐式动画组件内部进行操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 abstract class ImplicitlyAnimatedWidgetState <T extends ImplicitlyAnimatedWidget > extends State <T > // 混入这个类来提供vsync with SingleTickerProviderStateMixin <T > { @protected late final AnimationController controller = AnimationController( duration: widget.duration, debugLabel: kDebugMode ? widget.toStringShort() : null , vsync: this , ); Animation<double > get animation => _animation; late CurvedAnimation _animation = _createCurve(); @protected @override void initState() { super .initState(); controller.addStatusListener((AnimationStatus status) { if (status.isCompleted) { widget.onEnd?.call(); } }); _constructTweens(); didUpdateTweens(); } @protected @override void didUpdateWidget(T oldWidget) { super .didUpdateWidget(oldWidget); if (widget.curve != oldWidget.curve) { _animation.dispose(); _animation = _createCurve(); } controller.duration = widget.duration; if (_constructTweens()) { forEachTween(( Tween<dynamic >? tween, dynamic targetValue, TweenConstructor<dynamic > constructor, ) { return tween ?..begin = tween.evaluate(_animation) ..end = targetValue; }); controller.forward(from: 0.0 ); didUpdateTweens(); } } CurvedAnimation _createCurve() { return CurvedAnimation(parent: controller, curve: widget.curve); } @protected @override void dispose() { _animation.dispose(); controller.dispose(); super .dispose(); } bool _constructTweens() { bool shouldStartAnimation = false ; forEachTween(( Tween<dynamic >? tween, dynamic targetValue, TweenConstructor<dynamic > constructor, ) { if (targetValue != null ) { tween ??= constructor(targetValue); if (targetValue != (tween.end ?? tween.begin)) { shouldStartAnimation = true ; } else { tween.end ??= tween.begin; } } else { tween = null ; } return tween; }); return shouldStartAnimation; } @protected void forEachTween(TweenVisitor<dynamic > visitor); @protected void didUpdateTweens() {} }
所有的动画逻辑ImplicitlyAnimatedWidgetState进行封装,包括动画的开始与执行,界面刷新时的重新设置的处理逻辑等,如果我们需要做动效,则只需要继承它们即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 class VisibleWidget extends ImplicitlyAnimatedWidget { final bool _show ; final Widget child; const VisibleWidget({ super .key, required super .duration, required bool show , required this .child, }) : _show = show ; @override ImplicitlyAnimatedWidgetState<VisibleWidget> createState() { return _VisibleState(); } }class _VisibleState extends ImplicitlyAnimatedWidgetState <VisibleWidget > { Tween<double >? visibleTween; late Animation<double > visibleAnim; @override void forEachTween(TweenVisitor<dynamic > visitor) { visibleTween = visitor.call( visibleTween, widget._show ? 1.0 : 0.0 , (dynamic target) => Tween<double >(begin: target), ) as Tween<double >?; } @override void didUpdateTweens() { visibleAnim = animation.drive(visibleTween!); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: visibleAnim, builder: (_, _) { return Opacity(opacity: visibleAnim.value, child: widget.child); }, ); } }
这样,就定义好了一个用于做渐隐渐现的动画效果,使用起来也很简单,当然也是需要基于有状态组件的,因为它的状态仍是由外部传递进来的,而不是自己处理的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class MyAnimationWidget extends StatefulWidget { const MyAnimationWidget({super .key}); @override State<MyAnimationWidget> createState() => _MyAnimationWidgetState(); }class _MyAnimationWidgetState extends State <MyAnimationWidget > { bool show = true ; @override Widget build(BuildContext context) { return Column( children: [ VisibleWidget( duration: Duration (seconds: 2 ), show : show , child: Container(width: 100 , height: 100 , color: Colors.red), ), ElevatedButton( onPressed: () { setState(() { show = !show ; }); }, child: Text('Button' ), ), ], ); } }
当然,其实这些基本动画的组件都被内置到Flutter中了,它们都是以Animated开头的组件,如:AnimatedOpacity、AnimatedPadding等等。
总结 到这里关于Flutter的动画部分就已经讲完了,它的核心逻辑就是AnimationController来接收vsync信号并记录当前动画应该要执行的进度值,然后通过Tween来定义动画的初始值和目标值,最后通过controller.drive或者tween.animate方法将二者进行关联形成一个新的Animation,然后就能通过读取这个新的Animation的value来获取到动画的值了。
基于此,Flutter还封装了很多常用的组件并且帮我们实现了对应的动画效果,使得我们可以快速使用。它们在命名上采用Animated开头,如AnimatedOpacity等,这一类的组件基本上都是封装好的带动画的组件。