天天看點

深入了解Flutter動畫原理

一、概述[]( http://gityuan.com/2019/07/13/flutter_animator/# 一概述)

動畫效果對于系統的使用者體驗非常重要,好的動畫能讓使用者感覺界面更加順暢,提升使用者體驗。

1.1 動畫類型[]( http://gityuan.com/2019/07/13/flutter_animator/#11- 動畫類型)

Flutter動畫大的分類來說主要分為兩大類:

  • 補間動畫:給定初值與終值,系統自動補齊中間幀的動畫
  • 實體動畫:遵循實體學定律的動畫,實作了彈簧、阻尼、重力三種實體效果

在應用使用過程中常見動畫模式:

  • 動畫清單或者網格:例如元素的添加或者删除操作;
  • 轉場動畫Shared element transition:例如從目前頁面打開另一頁面的過渡動畫;
  • 交錯動畫Staggered animations:比如部分或者完全交錯的動畫。

1.2 類圖[]( http://gityuan.com/2019/07/13/flutter_animator/#12- 類圖)

核心類:

  • Animation對象是整個動畫中非常核心的一個類;
  • AnimationController用于管理Animation;
  • CurvedAnimation過程是非線性曲線;
  • Tween補間動畫
  • Listeners和StatusListeners用于監聽動畫狀态改變。

AnimationStatus是枚舉類型,有4個值;

取值 解釋
dismissed 動畫在開始時停止
forward 動畫從頭到尾繪制
reverse 動畫反向繪制,從尾到頭
completed 動畫在結束時停止

1.3 動畫執行個體[]( http://gityuan.com/2019/07/13/flutter_animator/#13- 動畫執行個體)

//[見小節2.1]
AnimationController animationController = AnimationController(
    vsync: this, duration: Duration(milliseconds: 1000));
Animation animation = Tween(begin: 0.0,end: 10.0).animate(animationController);
animationController.addListener(() {
  setState(() {});
});
 //[見小節2.2]
animationController.forward();
           

該過程說明:

  • AnimationController作為Animation子類,在螢幕重新整理時生成一系列值,預設情況下從0到1區間的取值。
  • Tween的animate()方法來自于父類Animatable,該方法傳回的對象類型為_AnimatedEvaluation,而該對象最核心的工作就是通過value來調用Tween的transform();

調用鍊:

AnimationController.forward
  AnimationController.\_animateToInternal
    AnimationController.\_startSimulation
      Ticker.start()
        Ticker.scheduleTick()
          SchedulerBinding.scheduleFrameCallback()
            SchedulerBinding.scheduleFrame()
              ...
                Ticker.\_tick
                  AnimationController.\_tick
                  Ticker.scheduleTick
           

二、原理分析[]( 二原理分析)

2.1 AnimationController初始化[]( http://gityuan.com/2019/07/13/flutter_animator/#21-animationcontroller 初始化)

[-> lib/src/animation/animation_controller.dart]

AnimationController({
  double value,
  this.duration,
  this.debugLabel,
  this.lowerBound = 0.0,
  this.upperBound = 1.0,
  this.animationBehavior = AnimationBehavior.normal,
  @required TickerProvider vsync,
}) : _direction = _AnimationDirection.forward {
  _ticker = vsync.createTicker(_tick);  //[見小節2.1.1]
  _internalSetValue(value ?? lowerBound); //[見小節2.1.3]
}
           

該方法說明:

  • AnimationController初始化過程,一般都設定duration和vsync初值;
  • upperBound(上邊界值)和lowerBound(下邊界值)都不能為空,且upperBound必須大于等于lowerBound;
  • 建立預設的動畫方向為向前(_AnimationDirection.forward);
  • 調用類型為TickerProvider的vsync對象的createTicker()方法來建立Ticker對象;

TickerProvider作為抽象類,主要的子類有SingleTickerProviderStateMixin和TickerProviderStateMixin,這兩個類的差別就是是否支援建立多個TickerProvider,這裡SingleTickerProviderStateMixin為例展開。

2.1.1 createTicker[]( http://gityuan.com/2019/07/13/flutter_animator/#211-createticker)

[-> lib/src/widgets/ticker_provider.dart]

mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
  Ticker _ticker;

  Ticker createTicker(TickerCallback onTick) {
     //[見小節2.1.2]
    _ticker = Ticker(onTick, debugLabel: 'created by $this');
    return _ticker;
  }
           

2.1.2 Ticker初始化[]( http://gityuan.com/2019/07/13/flutter_animator/#212-ticker

[-> lib/src/scheduler/ticker.dart]

class Ticker {
  Ticker(this._onTick, { this.debugLabel }) {
  }  

  final TickerCallback _onTick;
}
           

将AnimationControllerd對象中的_tick()方法,指派給Ticker對象的_onTick成員變量,再來看看該_tick方法。

2.1.3 _internalSetValue[]( http://gityuan.com/2019/07/13/flutter_animator/#213-_internalsetvalue)

[-> lib/src/animation/animation_controller.dart ::AnimationController]

void _internalSetValue(double newValue) {
  _value = newValue.clamp(lowerBound, upperBound);
  if (_value == lowerBound) {
    _status = AnimationStatus.dismissed;
  } else if (_value == upperBound) {
    _status = AnimationStatus.completed;
  } else {
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
  }
}
           

根據目前的value值來初始化動畫狀态_status

2.2 forward[]( http://gityuan.com/2019/07/13/flutter_animator/#22-forward)

TickerFuture forward({ double from }) {
  //預設采用向前的動畫方向
  _direction = _AnimationDirection.forward;
  if (from != null)
    value = from;
  return _animateToInternal(upperBound); //[見小節2.3]
}
           

_AnimationDirection是枚舉類型,有forward(向前)和reverse(向後)兩個值,也就是說該方法的功能是指從from開始向前滑動,

2.3 _animateToInternal[]( http://gityuan.com/2019/07/13/flutter_animator/#23-_animatetointernal)

TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear }) {
  double scale = 1.0;
  if (SemanticsBinding.instance.disableAnimations) {
    switch (animationBehavior) {
      case AnimationBehavior.normal:
        scale = 0.05;
        break;
      case AnimationBehavior.preserve:
        break;
    }
  }
  Duration simulationDuration = duration;
  if (simulationDuration == null) {
    final double range = upperBound - lowerBound;
    final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
    //根據剩餘動畫的百分比來評估仿真動畫剩餘時長
    simulationDuration = this.duration * remainingFraction;
  } else if (target == value) {
    //已到達動畫終點,不再執行動畫
    simulationDuration = Duration.zero;
  }
  //停止老的動畫[見小節2.3.1]
  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();
  }
  //[見小節2.4]
  return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
}
           

預設采用的是線性動畫曲線Curves.linear。

2.3.1 AnimationController.stop[]( http://gityuan.com/2019/07/13/flutter_animator/#231-animationcontrollerstop)

void stop({ bool canceled = true }) {
  _simulation = null;
  _lastElapsedDuration = null;
  //[見小節2.3.2]
  _ticker.stop(canceled: canceled);
}
           

2.3.2 Ticker.stop[]( http://gityuan.com/2019/07/13/flutter_animator/#232-tickerstop)

void stop({ bool canceled = false }) {
  if (!isActive) //已經不活躍,則直接傳回
    return;

  final TickerFuture localFuture = _future;
  _future = null;
  _startTime = null;

  //[見小節2.3.3]
  unscheduleTick();
  if (canceled) {
    localFuture._cancel(this);
  } else {
    localFuture._complete();
  }
}
           

2.3.3 Ticker.unscheduleTick[]( http://gityuan.com/2019/07/13/flutter_animator/#233-tickerunscheduletick)

void unscheduleTick() {
  if (scheduled) {
    SchedulerBinding.instance.cancelFrameCallbackWithId(_animationId);
    _animationId = null;
  }
}
           

2.3.4 _InterpolationSimulation初始化[]( http://gityuan.com/2019/07/13/flutter_animator/#234-_interpolationsimulation

[-> lib/src/animation/animation_controller.dart ::_InterpolationSimulation]

class _InterpolationSimulation extends Simulation {
  _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale)
    : _durationInSeconds = (duration.inMicroseconds * scale) / Duration.microsecondsPerSecond;

  final double _durationInSeconds;
  final double _begin;
  final double _end;
  final Curve _curve;
}
           

該方法建立插值模拟器對象,并初始化起點、終點、動畫曲線以及時長。這裡用的Curve是線性模型,也就是說采用的是勻速運動。

2.4 _startSimulation[]( http://gityuan.com/2019/07/13/flutter_animator/#24-_startsimulation)

TickerFuture _startSimulation(Simulation simulation) {
  _simulation = simulation;
  _lastElapsedDuration = Duration.zero;
  _value = simulation.x(0.0).clamp(lowerBound, upperBound);
  //[見小節2.5]
  final TickerFuture result = _ticker.start();
  _status = (_direction == _AnimationDirection.forward) ?
    AnimationStatus.forward :
    AnimationStatus.reverse;
  //[見小節2.4.1]
  _checkStatusChanged();
  return result;
}
           

2.4.1 _checkStatusChanged[]( http://gityuan.com/2019/07/13/flutter_animator/#241-_checkstatuschanged)

void _checkStatusChanged() {
  final AnimationStatus newStatus = status;
  if (_lastReportedStatus != newStatus) {
    _lastReportedStatus = newStatus;
    notifyStatusListeners(newStatus); //通知狀态改變
  }
}
           

這裡會回調_statusListeners中的所有狀态監聽器,這裡的狀态就是指AnimationStatus的dismissed、forward、reverse以及completed。

2.5 Ticker.start[]( http://gityuan.com/2019/07/13/flutter_animator/#25-tickerstart)

TickerFuture start() {
  _future = TickerFuture._();
  if (shouldScheduleTick) {
    scheduleTick();   //[見小節2.6]
  }
  if (SchedulerBinding.instance.schedulerPhase.index > SchedulerPhase.idle.index &&
      SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index)
    _startTime = SchedulerBinding.instance.currentFrameTimeStamp;
  return _future;
}
           

此處的shouldScheduleTick等于!muted && isActive && !scheduled,也就是沒有排程過的活躍狀态才會調用Tick。

2.6 Ticker.scheduleTick[]( http://gityuan.com/2019/07/13/flutter_animator/#26-tickerscheduletick)

void scheduleTick({ bool rescheduling = false }) {
  //[見小節2.7]
  _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}
           

此處的_tick會在下一次vysnc觸發時回調執行,見小節2.10。

2.7 scheduleFrameCallback[]( http://gityuan.com/2019/07/13/flutter_animator/#27-scheduleframecallback)

[-> lib/src/scheduler/binding.dart]

int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
    //[見小節2.8]
  scheduleFrame();
  _nextFrameCallbackId += 1;
  _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
  return _nextFrameCallbackId;
}
           

将前面傳遞過來的Ticker._tick()方法儲存在_FrameCallbackEntry的callback中,然後将_FrameCallbackEntry記錄在Map類型的_transientCallbacks,

2.8 scheduleFrame[]( http://gityuan.com/2019/07/13/flutter_animator/#28-scheduleframe)

void scheduleFrame() {
  if (_hasScheduledFrame || !_framesEnabled)
    return;
  ui.window.scheduleFrame();
  _hasScheduledFrame = true;
}
           

從文章

Flutter之setState更新機制

,可知此處調用的ui.window.scheduleFrame(),會注冊vsync監聽。當當下一次vsync信号的到來時會執行handleBeginFrame()。

2.9 handleBeginFrame[]( http://gityuan.com/2019/07/13/flutter_animator/#29-handlebeginframe)

[-> lib/src/scheduler/binding.dart:: SchedulerBinding]

void handleBeginFrame(Duration rawTimeStamp) {
  Timeline.startSync('Frame', arguments: timelineWhitelistArguments);
  _firstRawTimeStampInEpoch ??= rawTimeStamp;
  _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
  if (rawTimeStamp != null)
    _lastRawTimeStamp = rawTimeStamp;
  ...

  //此時階段等于SchedulerPhase.idle;
  _hasScheduledFrame = false;
  try {
    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;
  }
}
           

該方法主要功能是周遊_transientCallbacks,從前面小節[2.7],可知該過程會執行Ticker._tick()方法。

2.10 Ticker._tick[]( http://gityuan.com/2019/07/13/flutter_animator/#210-ticker_tick)

void _tick(Duration timeStamp) {
  _animationId = null;
  _startTime ??= timeStamp;
  //[見小節2.11]
  _onTick(timeStamp - _startTime);
  //根據活躍狀态來決定是否再次排程
  if (shouldScheduleTick)
    scheduleTick(rescheduling: true);
}
           

該方法主要功能:

  • 小節[2.1.2]的Ticker初始化中,可知此處_onTick便是AnimationController的_tick()方法;
  • 小節[2.5]已介紹當仍處于活躍狀态,則會再次排程,回到小節[2.6]的scheduleTick(),進而形成動畫的連續繪制過程。

2.11 AnimationController._tick[]( http://gityuan.com/2019/07/13/flutter_animator/#211-animationcontroller_tick)

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();   //通知監聽器[見小節2.11.1]
  _checkStatusChanged(); //通知狀态監聽器[見小節2.11.2]
}
           

2.11.1 notifyListeners[]( http://gityuan.com/2019/07/13/flutter_animator/#2111-notifylisteners)

[-> lib/src/animation/listener_helpers.dart ::AnimationLocalListenersMixin]

void notifyListeners() {
  final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
  for (VoidCallback listener in localListeners) {
    try {
      if (_listeners.contains(listener))
        listener();
    } catch (exception, stack) {
      ...
    }
  }
}
           

AnimationLocalListenersMixin的addListener()會向_listeners中添加監聽器

2.11.2 _checkStatusChanged[]( http://gityuan.com/2019/07/13/flutter_animator/#2112-_checkstatuschanged)

[-> lib/src/animation/listener_helpers.dart ::AnimationLocalStatusListenersMixin]

void notifyStatusListeners(AnimationStatus status) {
  final List<AnimationStatusListener> localListeners = List<AnimationStatusListener>.from(_statusListeners);
  for (AnimationStatusListener listener in localListeners) {
    try {
      if (_statusListeners.contains(listener))
        listener(status);
    } catch (exception, stack) {
      ...
    }
  }
}
           

從前面的小節[2.4.1]可知,當狀态改變時會調用notifyStatusListeners方法。AnimationLocalStatusListenersMixin的addStatusListener()會向_statusListeners添加狀态監聽器。

三、總結[]( 三總結)

3.1 動畫流程圖[]( http://gityuan.com/2019/07/13/flutter_animator/#31- 動畫流程圖)

推薦閱讀:

騰訊技術團隊整理,萬字長文輕松徹底入門 Flutter,秒變大前端 2017-2020曆年位元組跳動Android面試真題解析(累計下載下傳1082萬次,持續更新中)

原文作者:gityuan

原文連結:

http://gityuan.com/2019/07/13/flutter_animator/

繼續閱讀