Android开发Flutter圈子Flutter中文社区

从源码解析Flutter的动画业务逻辑

2020-05-03  本文已影响0人  岛上码农

概述

Flutter提供了丰富的动画形式,其中Curve负责动画切换路径,Tween用于构造动画的插值方式,AnimationController控制组件动画。AnimationController接收一个TickerProvider(抽象类)的对象,控制其动画。普通的Widget并不是TickerProvider的实现实例,因此需要通过mixin的方式,如下所示:

class FontAnimation extends StatefulWidget {
  FontAnimation({Key key}) : super(key: key);

  @override
  _FontAnimationState createState() => _FontAnimationState();
}

//mixin SingleTickerProviderStateMixin实现TickerProvider接口
class _FontAnimationState extends State<FontAnimation> with SingleTickerProviderStateMixin{
  Animation<double> tween;
  AnimationController animationController;

  @override
  void initState() {
    super.initState();
    //vsync参数需要是一个TickerProvider的实现对象
    animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 2000));
  }
  
  //...代码省略

AnimatedBuilder类

AnimatedBuilder类继承于AnimatedWidget,AnimatedWidget继承自StatefulWidget。AnimatedBuilder构造函数接收一个实现了抽象类Listenable的对象animation,以及一个builder方法,其中builder方法会在AnimatedBuilder的build方法中调用。每次animation的值改变时就会调用build方法(实际调用了传递过来的builder方法),从而实现动画。AniamtedBuilder的代码很简单,如下所示:

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;

  /// The child widget to pass to the [builder].
  ///
  /// If a [builder] callback's return value contains a subtree that does not
  /// depend on the animation, it's more efficient to build that subtree once
  /// instead of rebuilding it on every animation tick.
  ///
  /// If the pre-built subtree is passed as the [child] parameter, the
  /// [AnimatedBuilder] will pass it back to the [builder] function so that it
  /// can be incorporated into the build.
  ///
  /// Using this pre-built child is entirely optional, but can improve
  /// performance significantly in some cases and is therefore a good practice.
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return builder(context, child);
  }
}

注意构造函数的child是可选的,但是注释中建议如果builder方法返回值不依赖于动画,则建议通过构造函数的child参数设置,以免每次动画时都通过builder方法重绘与动画无关的组件。
**

AnimatedWidget类

AnimatedWidget是一个抽象类,主要是通过listenable增加listenable值变化的监听处理,这里只是简单地通过setState通知重绘组建。

abstract class AnimatedWidget extends StatefulWidget {
  /// Creates a widget that rebuilds when the given listenable changes.
  ///
  /// The [listenable] argument is required.
  const AnimatedWidget({
    Key key,
    @required this.listenable,
  }) : assert(listenable != null),
       super(key: key);

  /// The [Listenable] to which this widget is listening.
  ///
  /// Commonly an [Animation] or a [ChangeNotifier].
  final Listenable listenable;

  /// Override this method to build widgets that depend on the state of the
  /// listenable (e.g., the current value of the animation).
  @protected
  Widget build(BuildContext context);

  /// Subclasses typically do not override this method.
  @override
  _AnimatedState createState() => _AnimatedState();

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<Listenable>('animation', listenable));
  }
}

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);
}

AnimationController类

AnimationController继承自Animation<double>,在构造函数中接收一个TickerProvider对象,并调用TickerProvider的createTicker绑定一个动画执行方法_tick。_tick方法接收一个定时过去的时间elapsed参数,通过该参数计算当前动画进行的值(如果存在边界会被截断),如果动画结束则更新状态为完成(AnimationStatus.completed)或消失(AnimationStatus.dismissed)。同时通知动画监听器动画值已经改变。同时调用_checkStatusChanged方法更新状态(该方法会检测状态变化再更新)。

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 {
    //调用TickerProvider绑定定时执行方法_tick
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value ?? lowerBound);
  }
    //...
void _tick(Duration elapsed) {
    _lastElapsedDuration = elapsed;
    final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
    assert(elapsedInSeconds >= 0.0);
    _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
    if (_simulation.isDone(elapsedInSeconds)) {
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
      stop(canceled: false);
    }
    notifyListeners();
    _checkStatusChanged();
  }

Animation的Tween或CurvedAnimation等动画插值类通过指定一个parent绑定到AnimationController,实际时执行了AnimationController的addListener和addStatusListener方法绑定Animation监听回调方法,当AnimationController中的定时器导致AnimationController的value变更时,可以通过AnimationController的监听回调同时变更Animation的动画插值。只是不同的动画插值新插入的值的算法不同。
在_tick方法中有notifyListeners的调用,事实上在设置value时也有调用,notifyListener定义在
AnimationLocalListenersMixin,是一个mixin类型,方便需要的其他类直接使用。从notifyListener方法可以看到,实际上就是遍历全部的listener监听器,调用对应的callback回调方法。

void notifyListeners() {
    final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
    for (VoidCallback listener in localListeners) {
      try {
        if (_listeners.contains(listener))
          listener();
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'animation library',
          context: ErrorDescription('while notifying listeners for $runtimeType'),
          informationCollector: () sync* {
            yield DiagnosticsProperty<AnimationLocalListenersMixin>(
              'The $runtimeType notifying listeners was',
              this,
              style: DiagnosticsTreeStyle.errorProperty,
            );
          },
        ));
      }
    }
  }
}

AnimationController的dispose方法用于释放资源,其实是调用定时器ticker(TickerProvider)的dispose方法取消可能还在执行的TickerFuture任务。

//AnimationController
void dispose() {
    assert(() {
      if (_ticker == null) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('AnimationController.dispose() called more than once.'),
          ErrorDescription('A given $runtimeType cannot be disposed more than once.\n'),
          DiagnosticsProperty<AnimationController>(
            'The following $runtimeType object was disposed multiple times',
            this,
            style: DiagnosticsTreeStyle.errorProperty,
          ),
        ]);
      }
      return true;
    }());
    _ticker.dispose();
    _ticker = null;
    super.dispose();
  }

//TickerProvider的dispose方法
void dispose() {
    if (_future != null) {
      final TickerFuture localFuture = _future;
      _future = null;
      assert(!isActive);
      unscheduleTick();
      localFuture._cancel(this);
    }
    assert(() {
      // We intentionally don't null out _startTime. This means that if start()
      // was ever called, the object is now in a bogus state. This weakly helps
      // catch cases of use-after-dispose.
      _startTime = Duration.zero;
      return true;
    }());
  }


上一篇下一篇

猜你喜欢

热点阅读