美文网首页
Flutter系列九:Flutter动画源码解析

Flutter系列九:Flutter动画源码解析

作者: chonglingliu | 来源:发表于2021-04-09 12:13 被阅读0次

    上篇文章我们详细介绍了动画的使用,本文我们将从源码的角度解析动画的底层逻辑。

    动画的实现机制

    动画的控制是由AnimationController来实现的,这样我们就先从AnimationController来入手研究。

    • AnimationController构造函数
    <!-- AnimationController -->
    AnimationController({
        double? value,
        this.duration,
        this.reverseDuration,
        this.debugLabel,
        this.lowerBound = 0.0,
        this.upperBound = 1.0,
        this.animationBehavior = AnimationBehavior.normal,
        required TickerProvider vsync,
    }) : _direction = _AnimationDirection.forward {
         _ticker = vsync.createTicker(_tick);
         _internalSetValue(value ?? lowerBound);
    }
    
    void _tick(Duration elapsed) {
        // 省略内容...
    }
    
    Ticker? _ticker;
    

    AnimationController的构造函数中用vsync.createTicker(_tick)方法初始化了属性_ticker,并设置了动画的初始值。

    _ticker中一个重要的作用是持有了一个回调函数void _tick(Duration elapsed),这个_tick回调函数的作用我们后面会详细解释。

    • AnimationControllerforward方法
    <!-- AnimationController -->
    TickerFuture forward({ double? from }) {
        _direction = _AnimationDirection.forward;
        if (from != null)
          value = from;
        return _animateToInternal(upperBound);
    }
    
    TickerFuture _animateToInternal(double target, { Duration? duration, Curve curve = Curves.linear }) {
        stop();
        // 省略内容...
        return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
    }
    
    TickerFuture _startSimulation(Simulation simulation) {
        // start
        final TickerFuture result = _ticker!.start();
        return result;
    }
    
    void stop({ bool canceled = true }) {
        // stop
        _ticker!.stop(canceled: canceled);
    }
    

    forward方法中先调用了_ticker!.stop(canceled: true),然后调用了_ticker!.start()方法。

    上面提到的两个方法都是_ticker的方法,那它们做了什么工作呢?

    • Ticker
    <!-- Ticker -->
    void stop({ bool canceled = false }) {
        // 取消调度Tick
        unscheduleTick();
    }
      
    TickerFuture start() {
        // 省略内容...
        if (shouldScheduleTick) {
          // 1 开始调度Tick
          scheduleTick();
        }
        if (SchedulerBinding.instance!.schedulerPhase.index > SchedulerPhase.idle.index &&
            SchedulerBinding.instance!.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index)
          // 2 记录动画开始时间
          _startTime = SchedulerBinding.instance!.currentFrameTimeStamp;
        return _future!;
    }  
    
    1. stop方法调用了unscheduleTick方法取消Tick调度
    2. start方法调用了scheduleTick方法开始Tick调度,并且记录了开始时间。

    Tick调度 是什么意思呢?

    <!-- Ticker -->
    void scheduleTick({ bool rescheduling = false }) {
        _animationId = SchedulerBinding.instance!.scheduleFrameCallback(_tick, rescheduling: rescheduling);
    }
    
    void unscheduleTick() {
        if (scheduled) {
          SchedulerBinding.instance!.cancelFrameCallbackWithId(_animationId!);
          _animationId = null;
        }
    }
    
    <!-- SchedulerBinding -->
    int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
        scheduleFrame();
        _nextFrameCallbackId += 1;
        _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
        return _nextFrameCallbackId;
    }
    
    void cancelFrameCallbackWithId(int id) {
        _transientCallbacks.remove(id);
        _removedIds.add(id);
    }
    
    1. scheduleTick是将AnimationController_tick加入到了SchedulerBinding_transientCallbacks数组中,然后返回了一个对应的回调ID,然后请求刷新界面。
    2. unscheduleTick是根据回调IDAnimationController_tickSchedulerBinding_transientCallbacks数组中移除。
    • SchedulerBinding

    进入了我们熟悉的SchedulerBinding了,如果你有阅读过前面的文章应该对它的功能和调用逻辑有印象。

    void handleBeginFrame(Duration? rawTimeStamp) {
        // 1. 时间
        _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
        if (rawTimeStamp != null)
          _lastRawTimeStamp = rawTimeStamp;
    
        _hasScheduledFrame = false;
        try {
          
          final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
          _transientCallbacks = <int, _FrameCallbackEntry>{};
          callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
            if (!_removedIds.contains(id))
              // 2 回调
              _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
          });
          _removedIds.clear();
        } finally {
        }
    }
    

    每次进行界面刷新的时候,Flutter Engine会回调SchedulerBindinghandleBeginFrame方法,还传过来了一个时间戳。这时候会一一调用_transientCallbacks数组中的回调函数,并且将时间戳传进去。

    handleBeginFrame是在刷新界面函数_handleDrawFrame之前调用的,这里我们就可以知道handleBeginFrame主要是为了在绘制之前处理和设置好动画的中间值,便于重新绘制。

    我们回过来再看看AnimationController_tick的逻辑。

    • _tick方法
    void _tick(Duration elapsed) {
        
        _lastElapsedDuration = elapsed;
        // 1. 计算已经动画的时间
        final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
        // 2. 计算当前时间对应的动画的值
        _value = _simulation!.x(elapsedInSeconds).clamp(lowerBound, upperBound);
        // 3. 如果动画已经完成进行状态的设置和回调函数的取消
        if (_simulation!.isDone(elapsedInSeconds)) {
          _status = (_direction == _AnimationDirection.forward) ?
            AnimationStatus.completed :
            AnimationStatus.dismissed;
          stop(canceled: false);
        }
        // 4. 通知监听者value发生了变化
        notifyListeners();
        // 5. 通知监听者status发生了变化
        _checkStatusChanged();
    }
    

    _tick方法的逻辑是

    1. 先根据SchedulerBinding回调过来的时间戳算出当前动画的时间;
    2. 然后根据时间算出对应的value值;
    3. 如果动画已经完成进行状态的设置和回调函数的取消;
    4. 通知监听者value发生了变化
    5. 通知监听者status发生了变化

    至此,动画的实现逻辑就清晰了。

    动画逻辑

    动画的中间值的计算

    我们从_tick方法中看到了动画的中间值是根据时间elapsed来计算的。AnimationController默认是从0-1,假设动画的时长是2s。如果曲线是速率变化线性的,那么elapsed是1的时候,AnimationControllervalue就变成了0.5。这个很好理解。

    动画时间 动画值
    0 0
    0.5 0.25
    1.0 0.5
    1.5 0.75
    2 1
    • 设置了CurvedAnimation之后呢?
    class CurvedAnimation extends Animation<double> {
        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);
        }
    }
    

    设置了CurvedAnimation后,动画的值会调用Curvetransform方法,转换成新的值。

    Curve.decelerate为例:

    double transformInternal(double t) {
        t = 1.0 - t;
        return 1.0 - t * t;
    }
    
    动画时间 动画值
    0 0
    0.5 0.4375
    1.0 0.75
    1.5 0.9375
    2 1
    • 设置了Tween之后呢?

    Tween其实也是调用了transform方法进行了一次转换,例如:

    Tween(begin: 100.0, end: 200.0).animate(_animation);

    动画时间 动画值
    0 100
    0.5 143.75
    1.0 175
    1.5 193.75
    2 200

    上面的逻辑就是AnimationControllerCurvedAnimationTween一起共同决定动画中间值的一个逻辑。

    AnimatedWidget为什么不需要手动刷新?

    abstract class AnimatedWidget extends StatefulWidget {
    
      @override
      _AnimatedState createState() => _AnimatedState();
    
    }
    
    class _AnimatedState extends State<AnimatedWidget> {
      @override
      void initState() {
        super.initState();
        widget.listenable.addListener(_handleChange);
      }
    
      void _handleChange() {
        setState(() {
        });
      }
    
    }
    

    代码中我们看到AnimatedWidget继承自StatefulWidget_AnimatedStateinitState加入了一个动画的监听_handleChange函数。_handleChange函数中调用了setState进行刷新。

    AnimatedBuilder如何避免了子Widget的重构?

    class AnimatedBuilder extends AnimatedWidget {
      
      const AnimatedBuilder({
        Key? key,
        required Listenable animation,
        required this.builder,
        this.child,
      }) : assert(animation != null),
           assert(builder != null),
           super(key: key, listenable: animation);
    
      final Widget? child;
    
      @override
      Widget build(BuildContext context) {
        return builder(context, child);
      }
    }
    

    代码中我们看到构造函数传入的childbuilder方法传出去的child是同一个,这样就达到了child的复用逻辑。

    是否对这种复用方式有印象?Provider其实也有类似的设计。

    AnimatedBuilder

    ImplicitlyAnimatedWidget如何实现自动动画的?

    abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
    
      @override
      ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState();
    }
    
    abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> {
      
      @protected
      // 1
      AnimationController get controller => _controller;
      late final AnimationController _controller = AnimationController(
        duration: widget.duration,
        debugLabel: kDebugMode ? widget.toStringShort() : null,
        vsync: this,
      );
    
      // 2
      Animation<double> get animation => _animation;
      late Animation<double> _animation = _createCurve();
    
      @override
      void initState() {
        super.initState();
        _controller.addStatusListener((AnimationStatus status) {
          switch (status) {
            case AnimationStatus.completed:
              if (widget.onEnd != null)
                widget.onEnd!();
              break;
            case AnimationStatus.dismissed:
            case AnimationStatus.forward:
            case AnimationStatus.reverse:
          }
        });
        _constructTweens();
        didUpdateTweens();
      }
      
      // 2.
      CurvedAnimation _createCurve() {
        return CurvedAnimation(parent: _controller, curve: widget.curve);
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    
      bool _shouldAnimateTween(Tween<dynamic> tween, dynamic targetValue) {
        return targetValue != (tween.end ?? tween.begin);
      }
    
      void _updateTween(Tween<dynamic>? tween, dynamic targetValue) {
        if (tween == null)
          return;
        tween
          ..begin = tween.evaluate(_animation)
          ..end = targetValue;
      }
    
      void didUpdateWidget(T oldWidget) {
        super.didUpdateWidget(oldWidget);
        if (widget.curve != oldWidget.curve)
          _animation = _createCurve();
        _controller.duration = widget.duration;
        if (_constructTweens()) {
          forEachTween((Tween<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
            _updateTween(tween, targetValue);
            return tween;
          });
          _controller
            ..value = 0.0
            ..forward();
          didUpdateTweens();
        }
      }
      
      bool _constructTweens() {
        bool shouldStartAnimation = false;
        forEachTween((Tween<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
          if (targetValue != null) {
            tween ??= constructor(targetValue);
            if (_shouldAnimateTween(tween, targetValue))
              shouldStartAnimation = true;
          } else {
            tween = null;
          }
          return tween;
        });
        return shouldStartAnimation;
      }
    
      @protected
      void forEachTween(TweenVisitor<dynamic> visitor);
    
      @protected
      void didUpdateTweens() { }
    }
    

    ImplicitlyAnimatedWidget的代码也很清晰,使用的也是AnimationControllerCurvedAnimationTween的组合,只是内部实现了。

    当属性发生变化的时候,会调用didUpdateWidget,然后调用AnimationControllerforward方法开始动画。

    总结

    本文通过源码的分析,解读了动画的一些相关内容。后面我们将会进入状态管理的使用和分析,欢迎点赞和关注。

    相关文章

      网友评论

          本文标题:Flutter系列九:Flutter动画源码解析

          本文链接:https://www.haomeiwen.com/subject/wkuikltx.html