_AnimatedState 介绍
以AnimatedBuilder为例子,AnimatedBuilder继承自AnimatedWidget,而AnimatedWidget是继承自StatefulWidget,其由_AnimatedState来管理状态。
在_AnimatedState的initState方法里,我们对Animation对象(即此处的listenable对象)进行了监听,当其值改变时,调用了setState方法来进行状态更新。
class _AnimatedState extends State<AnimatedWidget> {
@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.
});
}
@override
Widget build(BuildContext context) => widget.build(context);
}
Ticker 和 TickerProvider 介绍
Ticker在动画的每一帧时都会调用设定好的一个回调函数,源码如下:
/// Calls its callback once per animation frame.
///
/// When created, a ticker is initially disabled. Call [start] to
/// enable the ticker.
///
/// A [Ticker] can be silenced by setting [muted] to true. While silenced, time
/// still elapses, and [start] and [stop] can still be called, but no callbacks
/// are called.
///
/// By convention, the [start] and [stop] methods are used by the ticker's
/// consumer, and the [muted] property is controlled by the [TickerProvider]
/// that created the ticker.
///
/// Tickers are driven by the [SchedulerBinding]. See
/// [SchedulerBinding.scheduleFrameCallback].
class Ticker {
/// Creates a ticker that will call the provided callback once per frame while
/// running.
///
/// An optional label can be provided for debugging purposes. That label
/// will appear in the [toString] output in debug builds.
Ticker(this._onTick, { this.debugLabel }) {
assert(() {
_debugCreationStack = StackTrace.current;
return true;
}());
}
...
按照文档注释,Ticker内的muted属性可以设置成true,然后可以使得Ticker对象变成静默状态,即此时start和stop方法仍可以调用,但是_onTick回调函数不再调用,而TickerProvider管理了muted属性,这在我们使用AnimationController时传入的vsync对象就是一个TickerProvider对象。
/// * `vsync` is the [TickerProvider] for the current context. It can be
/// changed by calling [resync]. It is required and must not be null. See
/// [TickerProvider] for advice on obtaining a ticker provider.
AnimationController({
double value,
this.duration,
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
@required TickerProvider vsync,
}) : assert(lowerBound != null),
assert(upperBound != null),
assert(upperBound >= lowerBound),
assert(vsync != null),
_direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
而我们通常在使用SingleTickerProviderStateMixin,这种mixin解决了管理ticker的麻烦。 只需在Widget的State上添加该mixin,现在的State就秘密地变成了TickerProvider,意思就是Flutter framework会向我们的State对象寻求一个ticker对象,重要的是AnimationController可以向State寻求一个ticker。这样当我们调用AnimationController的dispose方法时,也就是页面退出(动画不应该再继续显示)的时候,可以不再继续调用Ticker的回调函数,来避免内存泄漏和提高性能。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
}
SingleTickerProviderStateMixin的源码:
/// Provides a single [Ticker] that is configured to only tick while the current
/// tree is enabled, as defined by [TickerMode].
///
/// To create the [AnimationController] in a [State] that only uses a single
/// [AnimationController], mix in this class, then pass `vsync: this`
/// to the animation controller constructor.
///
/// This mixin only supports vending a single ticker. If you might have multiple
/// [AnimationController] objects over the lifetime of the [State], use a full
/// [TickerProviderStateMixin] instead.
@optionalTypeArgs
mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
Ticker _ticker;
@override
Ticker createTicker(TickerCallback onTick) {
assert(() {
if (_ticker == null)
return true;
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('$runtimeType is a SingleTickerProviderStateMixin but multiple tickers were created.'),
ErrorDescription('A SingleTickerProviderStateMixin can only be used as a TickerProvider once.'),
ErrorHint(
'If a State is used for multiple AnimationController objects, or if it is passed to other '
'objects and those objects might use it more than one time in total, then instead of '
'mixing in a SingleTickerProviderStateMixin, use a regular TickerProviderStateMixin.'
)
]);
}());
_ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null);
// We assume that this is called from initState, build, or some sort of
// event handler, and that thus TickerMode.of(context) would return true. We
// can't actually check that here because if we're in initState then we're
// not allowed to do inheritance checks yet.
return _ticker;
}
关于Ticker和AnimationController的值在动画过程中的关系:
![1*nKjFR7DVd-2r7_sSgBprfA.gif]
总结
虽然我们可以直接使用AnimationController并手动调用setState来进行动画,就像上面代码那样,但是这增加了代码复杂度,通常我们应该使用隐式动画或者显式动画,或者使用TweenAnimationBuilder来自定义隐式动画,或者使用AnimatedBuilder和继承AnimatedWidget的方式来自定义显式动画。
比如上面随着时间变化的文字动画,可以通过下面的TweenAnimationBuilder来简化代码:
class MyPragmaticWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
tween: IntTween(begin: 0, end: 299792458),
duration: const Duration(seconds: 1),
builder: (BuildContext context, int i, Widget child) {
return Text('$i m/s');
},
);
}
}