FlutterAnimationControllerコールバックの原則

FlutterAnimationControllerコールバックの原則

フラッターアニメーションの原理

AnimationControllerは、システムフレーム描画のコールバックをどのように受け取りますか?

アニメーションを使用する場合は、TickerProviderを使用して実装する必要があることがわかりました。TickProviderのソースコードを確認してください。

TickerProvider

/// An interface implemented by classes that can vend [Ticker] objects.
///
/// Tickers can be used by any object that wants to be notified whenever a frame
/// triggers, but are most commonly used indirectly via an
/// [AnimationController]. [AnimationController]s need a [TickerProvider] to
/// obtain their [Ticker]. If you are creating an [AnimationController] from a
/// [State], then you can use the [TickerProviderStateMixin] and
/// [SingleTickerProviderStateMixin] classes to obtain a suitable
/// [TickerProvider]. The widget test framework [WidgetTester] object can be
/// used as a ticker provider in the context of tests. In other contexts, you
/// will have to either pass a [TickerProvider] from a higher level (e.g.
/// indirectly from a [State] that mixes in [TickerProviderStateMixin]), or
/// create a custom [TickerProvider] subclass.
abstract class TickerProvider {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const TickerProvider();

  /// Creates a ticker with the given callback.
  ///
  /// The kind of ticker provided depends on the kind of ticker provider.
  Ticker createTicker(TickerCallback onTick);
}
  • クラスによって実装されたインターフェイスは、Tickerオブジェクトを提供できます。
  • ティッカーは、描画コールバックを受信したい場所ならどこでも使用できますが、最も一般的に使用されるのはAnimationController
  • あなたが作成している場合は、[AnimationController] [状態]から、あなたは、[使用できるTickerProviderStateMixinを]と[ SingleTickerProviderStateMixin ]クラスは、適切な[TickerProvider]を得るために、

**概要:** TickerProviderの役割により、Tickerオブジェクトが作成されます。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].

各アニメーションフレームは1回コールバックされます。

  • 作成されると、ティッカーの初期状態は無効になります。[start]を呼び出して開始します。
  • [ミュート]をtrueに設定すると、[ティッカー]を消音できます。サイレント状態では、まだ時間が経過しており、[start]と[stop]を呼び出すことはできますが、Tickerにコールバックされることはありません。
  • 慣例により、[start]メソッドと[stop]メソッドは[ticker]のユーザーによって使用され、[muted]プロパティは[TickerProvider]によって制御されます。
  • ティッカーコールバックは[SchedulerBinding]によって駆動されます。[SchedulerBinding.scheduleFrameCallback]。

**概要:**各フレームが描画される前に、SchedulerBindingによって駆動されるティッカーにコールバックされます

ScheduleBinding

/// Scheduler for running the following:
///
/// * _Transient callbacks_, triggered by the system's [Window.onBeginFrame]
///   callback, for synchronizing the application's behavior to the system's
///   display. For example, [Ticker]s and [AnimationController]s trigger from
///   these.
///
/// * _Persistent callbacks_, triggered by the system's [Window.onDrawFrame]
///   callback, for updating the system's display after transient callbacks have
///   executed. For example, the rendering layer uses this to drive its
///   rendering pipeline.
///
/// * _Post-frame callbacks_, which are run after persistent callbacks, just
///   before returning from the [Window.onDrawFrame] callback.
///
/// * Non-rendering tasks, to be run between frames. These are given a
///   priority and are executed in priority order according to a
///   [schedulingStrategy].

実行時にこれらのコールバックをスケジュールする

  • システムの[Window.onBeginFrame]によってコールバックされる一時的なコールバックは、アプリケーションの動作をシステムに同期するために使用されます。たとえば、[Ticker]と[AnimationController]のトリガーはそこから取得されます。
  • 永続的なコールバックは、システムの[Window.onDrawFrame]メソッドによってトリガーされ、TransientCallbackの実行後にシステムの表示を更新します。たとえば、レンダリングレイヤーはそれを使用して、ビルド、レイアウト、ペイントのレンダリングパイプラインを駆動します
  • _フレーム後のコールバック_主にクリーニングと準備作業を行うために、次のフレームが描画される前にコールバックします
  • 非レンダリングタスク非レンダリングタスクは、フレーム構築の合間に優先され、ユーザー入力などの[schedulingStrategy]の優先度によって実行されます。

**概要:** FrameCallback:SchedulerBindingクラスには3つのFrameCallbackコールバックキューがあります。描画プロセス中、これら3つのコールバックキューは異なる時間に実行されます。

  1. transientCallbacks:一時的なコールバック(通常はアニメーションコールバック)を格納するために使用されます。を介してSchedulerBinding.instance.scheduleFrameCallbackコールバック追加できます。
  2. PersistentCallbacks:一部の永続的なコールバックを保存するために使用されます。このようなコールバックでは新しい描画フレームを要求できません。一度登録すると、永続的なコールバックを削除できません。SchedulerBinding.instance.addPersitentFrameCallback()、このコールバックは、レイアウトと描画の作業を処理します。
  3. postFrameCallbacks:フレームの終了時に一度だけ呼び出され、システムは呼び出し後に削除され、SchedulerBinding.instance.addPostFrameCallback()登録できます。注意してください。このようなコールバックで新しいフレームをトリガーしないでください。これにより、更新サイクルが発生する可能性があります。
/// Schedules the given transient frame callback.
///
/// Adds the given callback to the list of frame callbacks and ensures that a
/// frame is scheduled.
///
/// If this is a one-off registration, ignore the `rescheduling` argument.
///
/// If this is a callback that will be re-registered each time it fires, then
/// when you re-register the callback, set the `rescheduling` argument to
/// true. This has no effect in release builds, but in debug builds, it
/// ensures that the stack trace that is stored for this callback is the
/// original stack trace for when the callback was _first_ registered, rather
/// than the stack trace for when the callback is re-registered. This makes it
/// easier to track down the original reason that a particular callback was
/// called. If `rescheduling` is true, the call must be in the context of a
/// frame callback.
///
/// Callbacks registered with this method can be canceled using
/// [cancelFrameCallbackWithId].
int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
  scheduleFrame();
  _nextFrameCallbackId += 1;
  _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
  return _nextFrameCallbackId;
}

一時フレームコールバックキューのスケジューリング

callBackを追加して、描画する前にフレームが彼にコールバックできることを確認します

void scheduleFrame() {
  if (_hasScheduledFrame || !_framesEnabled)
    return;
  assert(() {
    if (debugPrintScheduleFrameStacks)
      debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
    return true;
  }());
  ensureFrameCallbacksRegistered();
  window.scheduleFrame();
  _hasScheduledFrame = true;
}
@protected
void ensureFrameCallbacksRegistered() {
  window.onBeginFrame ??= _handleBeginFrame;
  window.onDrawFrame ??= _handleDrawFrame;
}

window.onBeginFramメソッドに割り当てられます

/// Called by the engine to prepare the framework to produce a new frame.
///
/// This function calls all the transient frame callbacks registered by
/// [scheduleFrameCallback]. It then returns, any scheduled microtasks are run
/// (e.g. handlers for any [Future]s resolved by transient frame callbacks),
/// and [handleDrawFrame] is called to continue the frame.
///
/// If the given time stamp is null, the time stamp from the last frame is
/// reused.
///
/// To have a banner shown at the start of every frame in debug mode, set
/// [debugPrintBeginFrameBanner] to true. The banner will be printed to the
/// console using [debugPrint] and will contain the frame number (which
/// increments by one for each frame), and the time stamp of the frame. If the
/// given time stamp was null, then the string "warm-up frame" is shown
/// instead of the time stamp. This allows frames eagerly pushed by the
/// framework to be distinguished from those requested by the engine in
/// response to the "Vsync" signal from the operating system.
///
/// You can also show a banner at the end of every frame by setting
/// [debugPrintEndFrameBanner] to true. This allows you to distinguish log
/// statements printed during a frame from those printed between frames (e.g.
/// in response to events or timers).
void handleBeginFrame(Duration rawTimeStamp) {
  Timeline.startSync('Frame', arguments: timelineWhitelistArguments);
  _firstRawTimeStampInEpoch ??= rawTimeStamp;
  _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
  if (rawTimeStamp != null)
    _lastRawTimeStamp = rawTimeStamp;
  _hasScheduledFrame = false;
  try {
    // TRANSIENT FRAME CALLBACKS
    Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
    _schedulerPhase = SchedulerPhase.transientCallbacks;
    final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
  	//************调用callBack******************
    _transientCallbacks = <int, _FrameCallbackEntry>{};
    callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
      if (!_removedIds.contains(id))
        _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
      //**************************callback(timeStamp);
    });
    _removedIds.clear();
  } finally {
    _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
  }
}
  • フレームがフレームを構築する準備ができると、エンジンによって呼び出されます。endgine呼び出しは、実際にはウィンドウのonBegainFrameであると推測できます。
  • [scheduleFrameCallback]内のすべてのメソッド(将来のいくつかのメソッドを含む)をコールバックし、実行が完了すると、システムは[handleDrawFrame]を呼び出します。
/// Signature for frame-related callbacks from the scheduler.
///
/// The `timeStamp` is the number of milliseconds since the beginning of the
/// scheduler's epoch. Use timeStamp to determine how far to advance animation
/// timelines so that all the animations in the system are synchronized to a
/// common time base.
typedef FrameCallback = void Function(Duration timeStamp);
 callback(timeStamp);
  • 「タイムスタンプ」は、スケジューラーの開始からのミリ秒数です。タイムスタンプを使用して、システム内のすべてのアニメーションが共通のタイムラインに同期されるように、アニメーションのタイムラインをどこまで進めるかを決定します。

**概要:**システムが描画する前に、システムはui.windowsのonBegainFrameにコールバックし、このonBegainFrameはhandleBeginFrameを実行し、時間値は各コールバックにコールバックされます。通常、アニメーションは描画前にコントロールのプロパティ値を変更して完了するため、描画前であることに注意してください。このアクションは描画前に完了する必要があります。


ティッカー

誰がscheduleFrameCallbackを呼び出したかを逆検索すると、それがTickerのscheduleTickであり、scheduleTickには後で呼び出す場所がいくつかあることがわかりました。

/// Schedules a tick for the next frame.
///
/// This should only be called if [shouldScheduleTick] is true.
@protected
void scheduleTick({ bool rescheduling = false }) {
  assert(!scheduled);
  assert(shouldScheduleTick);
  _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}

ここで、ティッカーが_tickオブジェクトをscheduleFrameCallbackに渡したことがわかります。

void _tick(Duration timeStamp) {
  assert(isTicking);
  assert(scheduled);
  _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);
}
final TickerCallback _onTick;
Ticker(this._onTick, { this.debugLabel }) {
  assert(() {
    _debugCreationStack = StackTrace.current;
    return true;
  }());
}

ここで、_tickeメソッドがタイムスタンプを相対時間に変換し、onTickにコールバックすることがわかります。onTickerは、Tickerが構築されるときに作成されます。Tickerの作成は、実際にはTickProviderでのみ行われます。

@override
Ticker createTicker(TickerCallback onTick) {
  _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null);
  return _ticker;
}

つまり、このcreateTickerを誰が呼んだのか、実際、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,
}) : assert(lowerBound != null),
     assert(upperBound != null),
     assert(upperBound >= lowerBound),
     assert(vsync != null),
     _direction = _AnimationDirection.forward {
  _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()`方法,果然AnimationController继承自Animation继承自Listenable。
  notifyListeners();
  _checkStatusChanged();
}

**概要:**特定のメソッドがscheduleTickを呼び出した後、Tickerのコールバック関数が_tick(Duration elapsed)transientCallbacksに追加され、各フレームが描画される前に、handleBeginFrameを介してこのメ​​ソッドにコールバックされます。

次に、誰がscheduleTickを呼び出したか、ソースコードを介して開始()の場所でTickerを検索し、この関数を呼び出します。

アニメーションを開始するたびに、通常、animation.forward()を使用します。

TickerFuture forward({ double from }) {
  _direction = _AnimationDirection.forward;
  //如果不为null 表示动画有起始值
  if (from != null)
    value = from;
  return _animateToInternal(upperBound);
}

このメソッドには2つの機能があります。1つはアニメーションを開始状態としてマークする機能で、もう1つはアニメーションの開始位置を設定する機能です。デフォルトは開始点からで、_animateToInternalを呼び出します。

TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear }) {
  //****************//
  return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
}
TickerFuture _startSimulation(Simulation simulation) {
  _simulation = simulation;
  _lastElapsedDuration = Duration.zero;
  _value = simulation.x(0.0).clamp(lowerBound, upperBound);
 // 此处调用ticker的start
  final TickerFuture result = _ticker.start();
  _status = (_direction == _AnimationDirection.forward) ?
    AnimationStatus.forward :
    AnimationStatus.reverse;
  _checkStatusChanged();
  return result;
}

**概要:** animationControllerのforwardが呼び出されると、最終的にはティッカーのstartメソッドに転送され、startメソッドはscheduleTickを呼び出して、ScheduleBindingへのティッカーのバインドを開始します。プロセス全体は、Windows-> ScheduleBinding-> Ticker-> AnimationControllerです。

バインディングが確立された後、各フレームが描画される前に、AnimationControllerの_tickメソッドが呼び出されます。

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

値は_simulation.x(elapsedInSeconds).clamp(lowerBound、upperBound)によって計算され、notifyListeners()コールバックが特定の用途に使用されます。以下は特定の呼び出しシーケンスです

おすすめ

転載: blog.csdn.net/u013491829/article/details/109330278