Flutter AnimationController回调原理

本文深入探讨了Flutter中AnimationController的回调原理,详细解释了SchedulerBinding如何驱动Ticker和AnimationController的回调。总结了SchedulerBinding的三个关键回调阶段:transientCallbacks、persistentCallbacks和postFrameCallbacks,以及它们在绘制过程中的作用。文章还分析了Ticker的调度过程,如何通过scheduleFrameCallback注册回调,并在每个frame绘制前执行。最后,提到了AnimationController如何利用Ticker进行动画更新,并在状态变化时通知监听者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

每个动画帧会回调一次。

  • 创建时,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回调队列, 在一次绘制过程中,这三个回调队列会放在不同时机被执行:

  1. transientCallbacks:用于存放一些临时回调,一般存放动画回调。可以通过SchedulerBinding.instance.scheduleFrameCallback 添加回调。

  2. persistentCallbacks:用于存放一些持久的回调,不能在此类回调中再请求新的绘制帧,持久回调一经注册则不能移除。SchedulerBinding.instance.addPersitentFrameCallback(),这个回调中处理了布局与绘制工作。

  3. 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();

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。

以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持)

部分资料一览:

  • 330页PDF Android学习核心笔记(内含8大板块)

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

  • Android BAT大厂面试题(有解析)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

src=“https://i-blog.csdnimg.cn/blog_migrate/59530ff4e3b5ccdb9bf917af06bb2597.jpeg” />

文末

我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。

以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持)

部分资料一览:

  • 330页PDF Android学习核心笔记(内含8大板块)

[外链图片转存中…(img-ryVrKDyM-1713307467694)]

[外链图片转存中…(img-dJV9lXRU-1713307467695)]

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

[外链图片转存中…(img-mxgZsPMg-1713307467696)]

  • Android BAT大厂面试题(有解析)

[外链图片转存中…(img-zsD9HGBr-1713307467697)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值