flutter动画原理详解(AnimationController、CurvedAnimation、Tween、Curve)

flutter动画类包括以下几种:

AnimationController:动画控制器,写动画必有它,例如启动动画controller.forward()。
Animatable:根据给出的开始值和结束值用来计算动画的真正的值(Tween就是它的子类)
Animation:用来监听动画的状态(AnimationContrller是它的子类)addListeneraddStatusListener
Curve:动画插值器(用来实现动画的值的变化,例如计算出动画的当前值,将它当做x值放入函数计算出y的值)自定义插值器就继承它

Curves:flutter sdk 自带的插值器集合,例如Curves.linear线性插值器

CurvedAnimation:继承自Animation,相当于对动画值得包装,通过.value可以直接获得动画的插值后的值

Tween:变化值重载器,例如颜色变化值,数值变化值,着重对动画变化值的处理(继承自AnimatableAnimationController只计算出动画进行之后的百分比,Tween计算出插值前的动画值,CurvedAnimation依赖于Curve计算出真正的动画值

先来看一下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 }) {
//将动画的时间缩短,默认动画时间你设置多少就是多少除非你调用SemanticsBinding.instance设置
    double scale = 1.0;
    if (SemanticsBinding.instance.disableAnimations) {
      switch (animationBehavior) {
        case AnimationBehavior.normal:
          scale = 0.05;
          break;
        case AnimationBehavior.preserve:
          break;
      }
    }
//如果没有传过来执行时间,就会就会计算执行时间simulationDuration 
    Duration simulationDuration = duration;
    if (simulationDuration == null) {
      final double range = upperBound - lowerBound;
      final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
      final Duration directionDuration =
        (_direction == _AnimationDirection.reverse && reverseDuration != null)
        ? reverseDuration
        : this.duration;
//将需要的时间赋值给simulationDuration,默认directionDuration =simulationDuration 
      simulationDuration = directionDuration * remainingFraction;
    } else if (target == value) {
      // Already at target, don't animate.
      simulationDuration = Duration.zero;
    }
//停止动画
    stop();
    if (simulationDuration == Duration.zero) {
      if (value != target) {
        _value = target.clamp(lowerBound, upperBound);
        notifyListeners();
      }
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
      _checkStatusChanged();
      return TickerFuture.complete();
    }
 //创建动画计算的工具类_InterpolationSimulation,默认线性插值器
    return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
  }

这个方法主要功能就是计算时间的缩放值,你设置的动画执行时间会在原来基础上实现缩放,而后根据你设置的from开始位置,计算动画需要执行的时间,如果为0不需要执行动画,直接通知动画执行完毕,最后创建_InterpolationSimulation动画数值计算器,这里动画开始值和结束值默认为0.0和1.0。

继续执行到_startSimulation方法如下

TickerFuture _startSimulation(Simulation simulation) {
    _simulation = simulation;
    _lastElapsedDuration = Duration.zero;
    //计算view的插值,返回0.0
    _value = simulation.x(0.0).clamp(lowerBound, upperBound);
    //开始订阅垂直同步信号的监听
    final TickerFuture result = _ticker.start();
    //改变动画的状态
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
    //回调动画状态通知
    _checkStatusChanged();
    return result;
  }

这个方法主要用来对动画开始值赋值为0.0,然后开始订阅垂直同步信号,确定是否改变动画的状态,最后确定是否通知动画状态改变。

最重要的就是硬件垂直同步信号的订阅,通过硬件垂直同步信号的来实时刷新动画的值,然后再渲染,以达到流畅的效果。

好,来看一下_ticker.start()的具体实现,实现在Ticker类中

 TickerFuture start() {
    
    _future = TickerFuture._();
//订阅垂直同步信号
    if (shouldScheduleTick) {
      scheduleTick();
    }
    if (SchedulerBinding.instance.schedulerPhase.index > SchedulerPhase.idle.index &&
        SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index)
      _startTime = SchedulerBinding.instance.currentFrameTimeStamp;
    return _future;
  }
void scheduleTick({ bool rescheduling = false }) {
  
    _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
  }

真正实现订阅的类是SchedulerBinding,将订阅的回调方法_tick传过去

int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
    //重新订阅垂直同步信号的下一帧
    scheduleFrame();
    _nextFrameCallbackId += 1;
    //将回调方法加入集合中
    _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
    return _nextFrameCallbackId;
  }
void scheduleFrame() {
    if (_hasScheduledFrame || !_framesEnabled)
      return;
  
    ensureFrameCallbacksRegistered();
//真正的订阅在底层引擎执行
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }

这个方法就是订阅了垂直同步信号下一帧的到来和将回调函数加入到集合中,在下一帧到来的时候回调集合中的方法。

在下一帧的垂直同步信号到来的时候会回调SchedulerBinding_handleBeginFrame方法然后是_handleDrawFrame渲染的方法

_handleBeginFrame如下:

void _handleBeginFrame(Duration rawTimeStamp) {
    if (_warmUpFrame) {
      assert(!_ignoreNextEngineDrawFrame);
      _ignoreNextEngineDrawFrame = true;
      return;
    }
    handleBeginFrame(rawTimeStamp);
  }
void handleBeginFrame(Duration rawTimeStamp) {
    Timeline.startSync('Frame', arguments: timelineWhitelistArguments);
//赋值垂直同步信号过来的时间
    _firstRawTimeStampInEpoch ??= rawTimeStamp;
//计算时间插值
    _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
    if (rawTimeStamp != null)
      _lastRawTimeStamp = rawTimeStamp;

    assert(schedulerPhase == SchedulerPhase.idle);
    _hasScheduledFrame = false;
    try {
      // TRANSIENT FRAME CALLBACKS
      Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
      _schedulerPhase = SchedulerPhase.transientCallbacks;
      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
//回调动画注册的方法
      _transientCallbacks = <int, _FrameCallbackEntry>{};
      callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
        if (!_removedIds.contains(id))
          _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
      });
      _removedIds.clear();
    } finally {
      _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
    }
  }

 

这个方法的意思就是记录当第一帧到来的时间赋值,动画来的帧以后到来的帧时间都减去这个第一帧时间形成时间插值,然后再回调给动画,也就是计算出动画距第一帧已经运行了多长时间,_handleBeginFrame是专门用来动画回调的。

然后回调到动画的时钟同步信号的处理类Ticker

void _tick(Duration timeStamp) {
    _animationId = null;
//计算已经过去真正的执行时间
    _startTime ??= timeStamp;
    _onTick(timeStamp - _startTime);

    // The onTick callback may have scheduled another tick already, for
    // example by calling stop then start again.
    if (shouldScheduleTick)
      scheduleTick(rescheduling: true);
  }

这个方法计算出动画真正的执行时间之后,回调给AnimationController_tick方法,然后通过scheduleTick重新订阅下一帧的垂直同步信号,也就是说从动画开始到动画结束需要一直订阅垂直同步信号去执行动画,直到动画执行完毕shouldScheduleTick为false,解除垂直同步信号。

 

void _tick(Duration elapsed) {
    _lastElapsedDuration = elapsed;
//将时间转化为秒
    final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
   
//通过动画数值计算工具类根据当前动画运行时间计算出数值
    _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
    if (_simulation.isDone(elapsedInSeconds)) {
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
      stop(canceled: false);
    }
//通知动画执行回调函数
    notifyListeners();
//通知状态回调方法
    _checkStatusChanged();
  }

这个方法会根据动画执行时间计算出真正的动画值,然后判断动画是否执行完毕,执行完毕的时候调用stop

void stop({ bool canceled = true }) {
    assert(
      _ticker != null,
      'AnimationController.stop() called after AnimationController.dispose()\n'
      'AnimationController methods should not be used after calling dispose.'
    );
    _simulation = null;
    _lastElapsedDuration = null;
    _ticker.stop(canceled: canceled);
  }
void stop({ bool canceled = false }) {
    if (!isActive)
      return;

    // We take the _future into a local variable so that isTicking is false
    // when we actually complete the future (isTicking uses _future to
    // determine its state).
    final TickerFuture localFuture = _future;
    _future = null;
    _startTime = null;
    assert(!isActive);

    unscheduleTick();
    if (canceled) {
      localFuture._cancel(this);
    } else {
      localFuture._complete();
    }
  }

SchedulerBinding移除监听的回调方法。而notifyListeners就是用来回调下面我们长用的方法,然后调用setState,将当前Widget放当藏数据中,接着执行_handleDrawFrame引起View树的重新渲染。

controller = AnimationController(vsync: this, duration: _duration)
      ..addListener(() {
        // Marks the widget tree as dirty
        setState(() {});
      });

从而达到动画的流畅执行和界面的显示。

 

 

 

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值