Flutter动画原理
上一篇我们介绍了flutter动画的基本使用,但AnimationController是如何接受到系统frame绘制的回调?
我们发现在使用动画的时候,一定要with一个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对象。
- Ticker可以用在任何想要收到绘制回调的地方,但最常用的是
AnimationController
- 如果您正在从 [State]创建一个[AnimationController],那么您可以使用[TickerProviderStateMixin]和[SingleTickerProviderStateMixin]类来获取一个合适的 [TickerProvider]
**总结:**TickerProvider的作用创建一个Ticker对象,那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].
每个动画帧会回调一次。
- 创建时,ticker的初始状态是禁用的。调用[start]启动。
- A [Ticker]可以通过将[muted]设置为true来进行沉默。在沉默状态下,时间仍然在流逝,[start]和[stop]仍然可以被调用,但是不会回调到Ticker中。
- 按照约定,[start]和[stop]方法由[ticker]的使用者使用,[muted]属性由[TickerProvider] 控制。
- Ticker回调是由[SchedulerBinding]驱动的。[SchedulerBinding.scheduleFrameCallback]。
**总结: **每次frame绘制之前会回调到ticker,由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].
调度运行时的这些回调
- Transient callbacks,由系统的[Window.onBeginFrame]回调,用于同步应用程序的行为到系统的展示。例如,[Ticker]s和[AnimationController]s触发器来自与它。
- Persistent callbacks 由系统的[Window.onDrawFrame]方法触发回调,用于在TransientCallback执行后更新系统的展示。例如,渲染层使用他来驱动渲染管道进行build,layout,paint
- _Post-frame callbacks_在下一帧绘制前回调,主要做一些清理和准备工作
- Non-rendering tasks 非渲染的任务,在帧构造之间,他们具有优先级,通过[schedulingStrategy]的优先级进行执行,例如用户的输入
**总结:**FrameCallback:SchedulerBinding 类中有三个FrameCallback回调队列, 在一次绘制过程中,这三个回调队列会放在不同时机被执行:
- transientCallbacks:用于存放一些临时回调,一般存放动画回调。可以通过
SchedulerBinding.instance.scheduleFrameCallback
添加回调。 - persistentCallbacks:用于存放一些持久的回调,不能在此类回调中再请求新的绘制帧,持久回调一经注册则不能移除。
SchedulerBinding.instance.addPersitentFrameCallback()
,这个回调中处理了布局与绘制工作。 - postFrameCallbacks:在Frame结束时只会被调用一次,调用后会被系统移除,可由
SchedulerBinding.instance.addPostFrameCallback()
注册,注意,不要在此类回调中再触发新的Frame,这可以会导致循环刷新。
/// 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;
}
调度 transient frame callback队列
添加一个callBack,确保fram绘制之前能够回调到他
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;
}
}
- 当框架准备构建一帧的时候由engine调用,可以猜想endgine调用的实际上是window的onBegainFrame
- 它会回调[scheduleFrameCallback]中的所有方法(包括一些future的方法),当执行完成后,系统会调用[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);
- “时间戳”是自scheduler开始以来的毫秒数。使用时间戳来确定动画时间线要前进多远,以便系统中的所有动画都同步到一通用的时间线上。
**总结:**当每次系统绘制的之前,会回调到ui.windows的onBegainFrame,而这个onBegainFrame执行的是handleBeginFrame,将时间值回调给了每一个callback。这里注意的是,是在在绘制之前,因为我们一般在绘制之前去通过改变控件的属性值完成动画,而这个动作必须在绘制前完成。
Ticker
反向搜索谁调用了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);
}
这里发现ticker传入了一个_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中的一个start()的地方调用了这个函数。
每次启动动画的时候我们一般使用animation.forward(),
TickerFuture forward({ double from }) {
_direction = _AnimationDirection.forward;
//如果不为null 表示动画有起始值
if (from != null)
value = from;
return _animateToInternal(upperBound);
}
这个方法就两个作用,一个是将动画标记为开始状态,另一个作用如果为动画设置从哪一个位置开始,默认从起始点,之后调用_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的时候,最终会调到ticker的start方法,start方法调用scheduleTick开始了ticker与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)计算出value值,然后由 notifyListeners()回调给我们的具体使用。 下面是具体的调用时序