_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');
      },
    );
  }
}

参考:Animation deep dive

如果觉得我的文章对你有用,请随意赞赏