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つのコールバックキューは異なる時間に実行されます。
- transientCallbacks:一時的なコールバック(通常はアニメーションコールバック)を格納するために使用されます。を介して
SchedulerBinding.instance.scheduleFrameCallback
コールバックを追加できます。 - PersistentCallbacks:一部の永続的なコールバックを保存するために使用されます。このようなコールバックでは新しい描画フレームを要求できません。一度登録すると、永続的なコールバックを削除できません。
SchedulerBinding.instance.addPersitentFrameCallback()
、このコールバックは、レイアウトと描画の作業を処理します。 - 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()コールバックが特定の用途に使用されます。以下は特定の呼び出しシーケンスです