SingleTickerProviderStateMixin
SingleTickerProviderStateMixin 是一个专门为 Flutter 应用提供单一计时器功能的混入类,主要用于需要动画控制的 StatefulWidget。
主要功能
1. 单一计时器
- 提供唯一的
createTicker方法创建计时器 - 严格限制只能创建一个 Ticker 实例,防止资源浪费
- 适用于只需要单个 AnimationController 的场景
2. 生命周期
- 激活管理 :
activate方法处理组件重新激活时的计时器状态 - 资源释放 :
dispose方法确保计时器正确释放,防止内存泄漏
3. 智能状态
- 自动静音 :根据 TickerMode 自动控制计时器的启用/静音状态
- 状态同步 :
_updateTicker实时同步计时器与应用状态 - 通知器管理 :
_updateTickerModeNotifier管理状态变化监听
/// Provides a single [Ticker] that is configured to only tick while the current
/// 提供一个单独的 [Ticker],配置为仅在当前树启用时才进行计时,由 [TickerMode] 定义。
/// tree is enabled, as defined by [TickerMode].
///
/// To create the [AnimationController] in a [State] that only uses a single
/// 在只使用单个 [AnimationController] 的 [State] 中创建 [AnimationController] 时,
/// [AnimationController], mix in this class, then pass `vsync: this`
/// 混入此类,然后将 `vsync: this` 传递给动画控制器构造函数。
/// to the animation controller constructor.
///
/// This mixin only supports vending a single ticker. If you might have multiple
/// 此混入仅支持提供单个计时器。如果在 [State] 的生命周期内可能有多个
/// [AnimationController] objects over the lifetime of the [State], use a full
/// [AnimationController] 对象,请使用完整的 [TickerProviderStateMixin] 代替。
/// [TickerProviderStateMixin] instead.
@optionalTypeArgs
mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
Ticker? _ticker;
@override
/// 创建一个新的计时器
/// Creates a new ticker
Ticker createTicker(TickerCallback onTick) {
assert(() {
if (_ticker == null) {
return true;
}
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('$runtimeType is a SingleTickerProviderStateMixin but multiple tickers were created.'),
// $runtimeType 是一个 SingleTickerProviderStateMixin,但创建了多个计时器。
ErrorDescription('A SingleTickerProviderStateMixin can only be used as a TickerProvider once.'),
// SingleTickerProviderStateMixin 只能作为 TickerProvider 使用一次。
ErrorHint(
'If a State is used for multiple AnimationController objects, or if it is passed to other '
'objects and those objects might use it more than one time in total, then instead of '
'mixing in a SingleTickerProviderStateMixin, use a regular TickerProviderStateMixin.',
// 如果一个 State 用于多个 AnimationController 对象,或者它被传递给其他对象,
// 而这些对象总共可能使用它超过一次,那么不要混入 SingleTickerProviderStateMixin,
// 而应该使用常规的 TickerProviderStateMixin。
),
]);
}());
_ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by ${describeIdentity(this)}' : null);
_updateTickerModeNotifier();
_updateTicker(); // Sets _ticker.mute correctly. // 正确设置 _ticker.mute
return _ticker!;
}
@override
/// 释放资源
/// Dispose of resources
void dispose() {
assert(() {
if (_ticker == null || !_ticker!.isActive) {
return true;
}
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('$this was disposed with an active Ticker.'),
// $this 在有活跃计时器时被释放。
ErrorDescription(
'$runtimeType created a Ticker via its SingleTickerProviderStateMixin, but at the time '
'dispose() was called on the mixin, that Ticker was still active. The Ticker must '
'be disposed before calling super.dispose().',
// $runtimeType 通过其 SingleTickerProviderStateMixin 创建了一个 Ticker,
// 但在混入上调用 dispose() 时,该 Ticker 仍然活跃。
// 必须在调用 super.dispose() 之前释放 Ticker。
),
ErrorHint(
'Tickers used by AnimationControllers '
'should be disposed by calling dispose() on the AnimationController itself. '
'Otherwise, the ticker will leak.',
// AnimationController 使用的计时器应该通过在 AnimationController 本身上调用 dispose() 来释放。
// 否则,计时器将泄漏。
),
_ticker!.describeForError('The offending ticker was'),
// 有问题的计时器是
]);
}());
_tickerModeNotifier?.removeListener(_updateTicker);
_tickerModeNotifier = null;
super.dispose();
}
ValueListenable<bool>? _tickerModeNotifier;
@override
/// 激活状态
/// Activate state
void activate() {
super.activate();
// We may have a new TickerMode ancestor.
// 我们可能有一个新的 TickerMode 祖先。
_updateTickerModeNotifier();
_updateTicker();
}
/// 更新计时器状态
/// Update ticker state
void _updateTicker() => _ticker?.muted = !_tickerModeNotifier!.value;
/// 更新计时器模式通知器
/// Update ticker mode notifier
void _updateTickerModeNotifier() {
final ValueListenable<bool> newNotifier = TickerMode.getNotifier(context);
if (newNotifier == _tickerModeNotifier) {
return;
}
_tickerModeNotifier?.removeListener(_updateTicker);
newNotifier.addListener(_updateTicker);
_tickerModeNotifier = newNotifier;
}
@override
/// 填充调试属性
/// Fill debug properties
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
final String? tickerDescription = switch ((_ticker?.isActive, _ticker?.muted)) {
(true, true) => 'active but muted', // 活跃但静音
(true, _) => 'active', // 活跃
(false, true) => 'inactive and muted', // 非活跃且静音
(false, _) => 'inactive', // 非活跃
(null, _) => null,
};
properties.add(DiagnosticsProperty<Ticker>('ticker', _ticker, description: tickerDescription, showSeparator: false, defaultValue: null));
}
}
TickerProvider
提供了 Flutter 动画系统中计时器的接口。
/// Signature for the callback passed to the [Ticker] class's constructor.
/// 传递给 [Ticker] 类构造函数的回调函数签名。
///
/// The argument is the time elapsed from
/// 参数是从计时器上次启动时的帧时间戳到当前帧时间戳之间经过的时间。
/// the frame timestamp when the ticker was last started
/// to the current frame timestamp.
typedef TickerCallback = void Function(Duration elapsed);
/// An interface implemented by classes that can vend [Ticker] objects.
/// 由可以提供 [Ticker] 对象的类实现的接口。
///
/// To obtain a [TickerProvider], consider mixing in either
/// 要获取 [TickerProvider],请考虑混入
/// [TickerProviderStateMixin] (which always works)
/// [TickerProviderStateMixin](总是有效)
/// or [SingleTickerProviderStateMixin] (which is more efficient when it works)
/// 或 [SingleTickerProviderStateMixin](在有效时更高效)
/// to make a [State] subclass implement [TickerProvider].
/// 来使 [State] 子类实现 [TickerProvider]。
/// That [State] can then be passed to lower-level widgets
/// 然后可以将该 [State] 传递给较低级别的组件
/// or other related objects.
/// 或其他相关对象。
/// This ensures the resulting [Ticker]s will only tick when that [State]'s
/// 这确保生成的 [Ticker] 只有在该 [State] 的
/// subtree is enabled, as defined by [TickerMode].
/// 子树启用时才会计时,由 [TickerMode] 定义。
///
/// In widget tests, the [WidgetTester] object is also a [TickerProvider].
/// 在组件测试中,[WidgetTester] 对象也是一个 [TickerProvider]。
///
/// 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]. [AnimationController]s need a [TickerProvider] to
/// [AnimationController] 需要一个 [TickerProvider] 来
/// obtain their [Ticker].
/// 获取它们的 [Ticker]。
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.
/// 提供的计时器类型取决于计时器提供者的类型。
@factory
Ticker createTicker(TickerCallback onTick);
}
Ticker
/// Calls its callback once per animation frame, when enabled.
/// 在启用时,每个动画帧调用一次其回调函数。
///
/// To obtain a ticker, consider [TickerProvider].
/// 要获取计时器,请考虑使用 [TickerProvider]。
///
/// When created, a ticker is initially disabled. Call [start] to
/// enable the ticker.
/// 创建时,计时器最初是禁用的。调用 [start] 来启用计时器。
///
/// 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.
/// 可以通过将 [muted] 设置为 true 来静音 [Ticker]。在静音时,
/// 时间仍在流逝,仍可以调用 [start] 和 [stop],但不会调用回调函数。
///
/// By convention, the [start] and [stop] methods are used by the ticker's
/// consumer (for example, an [AnimationController]), and the [muted] property
/// is controlled by the [TickerProvider] that created the ticker (for example,
/// a [State] that uses [TickerProviderStateMixin] to silence the ticker when
/// the state's subtree is disabled as defined by [TickerMode]).
/// 按照惯例,[start] 和 [stop] 方法由计时器的使用者使用
/// (例如 [AnimationController]),而 [muted] 属性由创建计时器的
/// [TickerProvider] 控制(例如,使用 [TickerProviderStateMixin] 的 [State]
/// 在状态的子树被禁用时静音计时器,如 [TickerMode] 所定义)。
///
/// See also:
///
/// * [TickerProvider], for obtaining a ticker.
/// * [SchedulerBinding.scheduleFrameCallback], which drives tickers.
// TODO(jacobr): make Ticker use Diagnosticable to simplify reporting errors
// related to a ticker.
class Ticker {
/// Creates a ticker that will call the provided callback once per frame while
/// running.
/// 创建一个在运行时每帧调用一次提供的回调函数的计时器。
///
/// An optional label can be provided for debugging purposes. That label
/// will appear in the [toString] output in debug builds.
/// 可以提供一个可选的标签用于调试目的。该标签将在调试构建的
/// [toString] 输出中出现。
Ticker(this._onTick, { this.debugLabel }) {
assert(() {
_debugCreationStack = StackTrace.current;
return true;
}());
// TODO(polina-c): stop duplicating code across disposables
// https://github.com/flutter/flutter/issues/137435
if (kFlutterMemoryAllocationsEnabled) {
FlutterMemoryAllocations.instance.dispatchObjectCreated(
library: 'package:flutter/scheduler.dart',
className: '$Ticker',
object: this,
);
}
}
TickerFuture? _future;
/// Whether this ticker has been silenced.
/// 此计时器是否已被静音。
///
/// While silenced, a ticker's clock can still run, but the callback will not
/// be called.
/// 在静音时,计时器的时钟仍可以运行,但不会调用回调函数。
bool get muted => _muted;
bool _muted = false;
/// When set to true, silences the ticker, so that it is no longer ticking. If
/// a tick is already scheduled, it will unschedule it. This will not
/// unschedule the next frame, though.
/// 当设置为 true 时,静音计时器,使其不再计时。如果已经安排了一个计时,
/// 它将取消安排。但这不会取消安排下一帧。
///
/// When set to false, unsilences the ticker, potentially scheduling a frame
/// to handle the next tick.
/// 当设置为 false 时,取消静音计时器,可能会安排一个帧来处理下一个计时。
///
/// By convention, the [muted] property is controlled by the object that
/// created the [Ticker] (typically a [TickerProvider]), not the object that
/// listens to the ticker's ticks.
/// 按照惯例,[muted] 属性由创建 [Ticker] 的对象(通常是 [TickerProvider])
/// 控制,而不是由监听计时器计时的对象控制。
set muted(bool value) {
if (value == muted) {
return;
}
_muted = value;
if (value) {
unscheduleTick();
} else if (shouldScheduleTick) {
scheduleTick();
}
}
/// Whether this [Ticker] has scheduled a call to call its callback
/// on the next frame.
/// 此 [Ticker] 是否已安排在下一帧调用其回调函数。
///
/// A ticker that is [muted] can be active (see [isActive]) yet not be
/// ticking. In that case, the ticker will not call its callback, and
/// [isTicking] will be false, but time will still be progressing.
/// 被 [muted] 的计时器可以是活动的(参见 [isActive])但不计时。
/// 在这种情况下,计时器不会调用其回调函数,[isTicking] 将为 false,
/// 但时间仍在流逝。
///
/// This will return false if the [SchedulerBinding.lifecycleState] is one
/// that indicates the application is not currently visible (e.g. if the
/// device's screen is turned off).
/// 如果 [SchedulerBinding.lifecycleState] 表示应用程序当前不可见
/// (例如,如果设备屏幕关闭),这将返回 false。
bool get isTicking {
if (_future == null) {
return false;
}
if (muted) {
return false;
}
if (SchedulerBinding.instance.framesEnabled) {
return true;
}
if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.idle) {
return true;
} // for example, we might be in a warm-up frame or forced frame
return false;
}
/// Whether time is elapsing for this [Ticker]. Becomes true when [start] is
/// called and false when [stop] is called.
/// 此 [Ticker] 的时间是否在流逝。当调用 [start] 时变为 true,
/// 当调用 [stop] 时变为 false。
///
/// A ticker can be active yet not be actually ticking (i.e. not be calling
/// the callback). To determine if a ticker is actually ticking, use
/// [isTicking].
/// 计时器可以是活动的但实际上不在计时(即不调用回调函数)。
/// 要确定计时器是否实际在计时,请使用 [isTicking]。
bool get isActive => _future != null;
/// The frame timestamp when the ticker was last started,
/// as reported by [SchedulerBinding.currentFrameTimestamp].
/// 计时器上次启动时的帧时间戳,
/// 由 [SchedulerBinding.currentFrameTimestamp] 报告。
Duration? _startTime;
/// Starts the clock for this [Ticker]. If the ticker is not [muted], then this
/// also starts calling the ticker's callback once per animation frame.
/// 启动此 [Ticker] 的时钟。如果计时器未被 [muted],
/// 那么这也会开始每个动画帧调用一次计时器的回调函数。
///
/// The returned future resolves once the ticker [stop]s ticking. If the
/// ticker is disposed, the future does not resolve. A derivative future is
/// available from the returned [TickerFuture] object that resolves with an
/// error in that case, via [TickerFuture.orCancel].
/// 返回的 future 在计时器 [stop] 计时后解析。如果计时器被释放,
/// future 不会解析。在这种情况下,可以从返回的 [TickerFuture] 对象
/// 获得一个派生的 future,通过 [TickerFuture.orCancel] 以错误解析。
///
/// Calling this sets [isActive] to true.
/// 调用此方法将 [isActive] 设置为 true。
///
/// This method cannot be called while the ticker is active. To restart the
/// ticker, first [stop] it.
/// 在计时器活动时不能调用此方法。要重新启动计时器,请先 [stop] 它。
///
/// By convention, this method is used by the object that receives the ticks
/// (as opposed to the [TickerProvider] which created the ticker).
/// 按照惯例,此方法由接收计时的对象使用
/// (而不是创建计时器的 [TickerProvider])。
TickerFuture start() {
assert(() {
if (isActive) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('A ticker was started twice.'),
ErrorDescription('A ticker that is already active cannot be started again without first stopping it.'),
describeForError('The affected ticker was'),
]);
}
return true;
}());
assert(_startTime == null);
_future = TickerFuture._();
if (shouldScheduleTick) {
scheduleTick();
}
if (SchedulerBinding.instance.schedulerPhase.index > SchedulerPhase.idle.index &&
SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index) {
_startTime = SchedulerBinding.instance.currentFrameTimeStamp;
}
return _future!;
}
/// Adds a debug representation of a [Ticker] optimized for including in error
/// messages.
/// 添加针对包含在错误消息中而优化的 [Ticker] 调试表示。
DiagnosticsNode describeForError(String name) {
// TODO(jacobr): make this more structured.
return DiagnosticsProperty<Ticker>(name, this, description: toString(debugIncludeStack: true));
}
/// Stops calling this [Ticker]'s callback.
/// 停止调用此 [Ticker] 的回调函数。
///
/// If called with the `canceled` argument set to false (the default), causes
/// the future returned by [start] to resolve. If called with the `canceled`
/// argument set to true, the future does not resolve, and the future obtained
/// from [TickerFuture.orCancel], if any, resolves with a [TickerCanceled]
/// error.
/// 如果使用 `canceled` 参数设置为 false(默认值)调用,
/// 会导致 [start] 返回的 future 解析。如果使用 `canceled` 参数设置为 true 调用,
/// future 不会解析,从 [TickerFuture.orCancel] 获得的 future(如果有)
/// 会以 [TickerCanceled] 错误解析。
///
/// Calling this sets [isActive] to false.
/// 调用此方法将 [isActive] 设置为 false。
///
/// This method does nothing if called when the ticker is inactive.
/// 如果在计时器不活动时调用,此方法不执行任何操作。
///
/// By convention, this method is used by the object that receives the ticks
/// (as opposed to the [TickerProvider] which created the ticker).
/// 按照惯例,此方法由接收计时的对象使用
/// (而不是创建计时器的 [TickerProvider])。
void stop({ bool canceled = false }) {
if (!isActive) {
return;
}
// We take the _future into a local variable so that isTicking is false
// when we actually complete the future (isTicking uses _future to
// determine its state).
final TickerFuture localFuture = _future!;
_future = null;
_startTime = null;
assert(!isActive);
unscheduleTick();
if (canceled) {
localFuture._cancel(this);
} else {
localFuture._complete();
}
}
final TickerCallback _onTick;
int? _animationId;
/// Whether this [Ticker] has already scheduled a frame callback.
/// 此 [Ticker] 是否已经安排了帧回调。
@protected
bool get scheduled => _animationId != null;
/// Whether a tick should be scheduled.
/// 是否应该安排一个计时。
///
/// If this is true, then calling [scheduleTick] should succeed.
/// 如果这是 true,那么调用 [scheduleTick] 应该成功。
///
/// Reasons why a tick should not be scheduled include:
/// 不应该安排计时的原因包括:
///
/// * A tick has already been scheduled for the coming frame.
/// * 已经为即将到来的帧安排了一个计时。
/// * The ticker is not active ([start] has not been called).
/// * 计时器不活动([start] 尚未被调用)。
/// * The ticker is not ticking, e.g. because it is [muted] (see [isTicking]).
/// * 计时器不在计时,例如因为它被 [muted](参见 [isTicking])。
@protected
bool get shouldScheduleTick => !muted && isActive && !scheduled;
void _tick(Duration timeStamp) {
assert(isTicking);
assert(scheduled);
_animationId = null;
_startTime ??= timeStamp; // 第一次调用时记录开始时间
_onTick(timeStamp - _startTime!); // 计算elapsed时间并回调
// The onTick callback may have scheduled another tick already, for
// example by calling stop then start again.
if (shouldScheduleTick) {
scheduleTick(rescheduling: true);
}
}
/// Schedules a tick for the next frame.
/// 为下一帧安排一个计时。
///
/// This should only be called if [shouldScheduleTick] is true.
/// 只有当 [shouldScheduleTick] 为 true 时才应该调用此方法。
@protected
void scheduleTick({ bool rescheduling = false }) {
assert(!scheduled);
assert(shouldScheduleTick);
_animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}
/// Cancels the frame callback that was requested by [scheduleTick], if any.
/// 取消由 [scheduleTick] 请求的帧回调(如果有)。
///
/// Calling this method when no tick is [scheduled] is harmless.
/// 当没有计时被 [scheduled] 时调用此方法是无害的。
///
/// This method should not be called when [shouldScheduleTick] would return
/// true if no tick was scheduled.
/// 当如果没有安排计时 [shouldScheduleTick] 会返回 true 时,
/// 不应该调用此方法。
@protected
void unscheduleTick() {
if (scheduled) {
SchedulerBinding.instance.cancelFrameCallbackWithId(_animationId!);
_animationId = null;
}
assert(!shouldScheduleTick);
}
/// Makes this [Ticker] take the state of another ticker, and disposes the
/// other ticker.
/// 使此 [Ticker] 接管另一个计时器的状态,并释放另一个计时器。
///
/// This is useful if an object with a [Ticker] is given a new
/// [TickerProvider] but needs to maintain continuity. In particular, this
/// maintains the identity of the [TickerFuture] returned by the [start]
/// function of the original [Ticker] if the original ticker is active.
/// 如果具有 [Ticker] 的对象被给予新的 [TickerProvider] 但需要保持连续性,
/// 这很有用。特别是,如果原始计时器是活动的,
/// 这会保持原始 [Ticker] 的 [start] 函数返回的 [TickerFuture] 的身份。
///
/// This ticker must not be active when this method is called.
/// 调用此方法时,此计时器不能是活动的。
void absorbTicker(Ticker originalTicker) {
assert(!isActive);
assert(_future == null);
assert(_startTime == null);
assert(_animationId == null);
assert((originalTicker._future == null) == (originalTicker._startTime == null), 'Cannot absorb Ticker after it has been disposed.');
if (originalTicker._future != null) {
_future = originalTicker._future;
_startTime = originalTicker._startTime;
if (shouldScheduleTick) {
scheduleTick();
}
originalTicker._future = null; // so that it doesn't get disposed when we dispose of originalTicker
originalTicker.unscheduleTick();
}
originalTicker.dispose();
}
/// Release the resources used by this object. The object is no longer usable
/// after this method is called.
/// 释放此对象使用的资源。调用此方法后,对象不再可用。
///
/// It is legal to call this method while [isActive] is true, in which case:
/// 在 [isActive] 为 true 时调用此方法是合法的,在这种情况下:
///
/// * The frame callback that was requested by [scheduleTick], if any, is
/// canceled.
/// * 由 [scheduleTick] 请求的帧回调(如果有)被取消。
/// * The future that was returned by [start] does not resolve.
/// * 由 [start] 返回的 future 不会解析。
/// * The future obtained from [TickerFuture.orCancel], if any, resolves
/// with a [TickerCanceled] error.
/// * 从 [TickerFuture.orCancel] 获得的 future(如果有)
/// 以 [TickerCanceled] 错误解析。
@mustCallSuper
void dispose() {
// TODO(polina-c): stop duplicating code across disposables
// https://github.com/flutter/flutter/issues/137435
if (kFlutterMemoryAllocationsEnabled) {
FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
}
if (_future != null) {
final TickerFuture localFuture = _future!;
_future = null;
assert(!isActive);
unscheduleTick();
localFuture._cancel(this);
}
assert(() {
// We intentionally don't null out _startTime. This means that if start()
// was ever called, the object is now in a bogus state. This weakly helps
// catch cases of use-after-dispose.
_startTime = Duration.zero;
return true;
}());
}
/// An optional label can be provided for debugging purposes.
/// 可以提供一个可选的标签用于调试目的。
///
/// This label will appear in the [toString] output in debug builds.
/// 此标签将在调试构建的 [toString] 输出中出现。
final String? debugLabel;
late StackTrace _debugCreationStack;
@override
String toString({ bool debugIncludeStack = false }) {
final StringBuffer buffer = StringBuffer();
buffer.write('${objectRuntimeType(this, 'Ticker')}(');
assert(() {
buffer.write(debugLabel ?? '');
return true;
}());
buffer.write(')');
assert(() {
if (debugIncludeStack) {
buffer.writeln();
buffer.writeln('The stack trace when the $runtimeType was actually created was:');
FlutterError.defaultStackFilter(_debugCreationStack.toString().trimRight().split('\n')).forEach(buffer.writeln);
}
return true;
}());
return buffer.toString();
}
}
/// An object representing an ongoing [Ticker] sequence.
/// 表示正在进行的 [Ticker] 序列的对象。
///
/// The [Ticker.start] method returns a [TickerFuture]. The [TickerFuture] will
/// complete successfully if the [Ticker] is stopped using [Ticker.stop] with
/// the `canceled` argument set to false (the default).
/// [Ticker.start] 方法返回一个 [TickerFuture]。如果使用 [Ticker.stop]
/// 停止 [Ticker] 且 `canceled` 参数设置为 false(默认值),
/// [TickerFuture] 将成功完成。
///
/// If the [Ticker] is disposed without being stopped, or if it is stopped with
/// `canceled` set to true, then this Future will never complete.
/// 如果 [Ticker] 在未停止的情况下被释放,或者如果它在 `canceled`
/// 设置为 true 的情况下停止,那么此 Future 将永远不会完成。
///
/// This class works like a normal [Future], but has an additional property,
/// [orCancel], which returns a derivative [Future] that completes with an error
/// if the [Ticker] that returned the [TickerFuture] was stopped with `canceled`
/// set to true, or if it was disposed without being stopped.
/// 此类的工作方式类似于普通的 [Future],但有一个额外的属性 [orCancel],
/// 它返回一个派生的 [Future],如果返回 [TickerFuture] 的 [Ticker]
/// 在 `canceled` 设置为 true 的情况下停止,或者如果它在未停止的情况下被释放,
/// 则该 Future 会以错误完成。
///
/// To run a callback when either this future resolves or when the ticker is
/// canceled, use [whenCompleteOrCancel].
/// 要在此 future 解析或计时器被取消时运行回调,请使用 [whenCompleteOrCancel]。
class TickerFuture implements Future<void> {
TickerFuture._();
/// Creates a [TickerFuture] instance that represents an already-complete
/// [Ticker] sequence.
/// 创建一个表示已完成的 [Ticker] 序列的 [TickerFuture] 实例。
///
/// This is useful for implementing objects that normally defer to a [Ticker]
/// but sometimes can skip the ticker because the animation is of zero
/// duration, but which still need to represent the completed animation in the
/// form of a [TickerFuture].
/// 这对于实现通常延迟到 [Ticker] 但有时可以跳过计时器的对象很有用,
/// 因为动画的持续时间为零,但仍需要以 [TickerFuture] 的形式
/// 表示已完成的动画。
TickerFuture.complete() {
_complete();
}
final Completer<void> _primaryCompleter = Completer<void>();
Completer<void>? _secondaryCompleter;
bool? _completed; // null means unresolved, true means complete, false means canceled
void _complete() {
assert(_completed == null);
_completed = true;
_primaryCompleter.complete();
_secondaryCompleter?.complete();
}
void _cancel(Ticker ticker) {
assert(_completed == null);
_completed = false;
_secondaryCompleter?.completeError(TickerCanceled(ticker));
}
/// Calls `callback` either when this future resolves or when the ticker is
/// canceled.
/// 在此 future 解析或计时器被取消时调用 `callback`。
///
/// Calling this method registers an exception handler for the [orCancel]
/// future, so even if the [orCancel] property is accessed, canceling the
/// ticker will not cause an uncaught exception in the current zone.
/// 调用此方法为 [orCancel] future 注册异常处理程序,
/// 因此即使访问了 [orCancel] 属性,取消计时器也不会在当前区域中
/// 引起未捕获的异常。
void whenCompleteOrCancel(VoidCallback callback) {
void thunk(dynamic value) {
callback();
}
orCancel.then<void>(thunk, onError: thunk);
}
/// A future that resolves when this future resolves or throws when the ticker
/// is canceled.
/// 一个在此 future 解析时解析或在计时器被取消时抛出异常的 future。
///
/// If this property is never accessed, then canceling the ticker does not
/// throw any exceptions. Once this property is accessed, though, if the
/// corresponding ticker is canceled, then the [Future] returned by this
/// getter will complete with an error, and if that error is not caught, there
/// will be an uncaught exception in the current zone.
/// 如果从未访问此属性,那么取消计时器不会抛出任何异常。
/// 但是,一旦访问了此属性,如果相应的计时器被取消,
/// 那么此 getter 返回的 [Future] 将以错误完成,
/// 如果该错误未被捕获,当前区域中将出现未捕获的异常。
Future<void> get orCancel {
if (_secondaryCompleter == null) {
_secondaryCompleter = Completer<void>();
if (_completed != null) {
if (_completed!) {
_secondaryCompleter!.complete();
} else {
_secondaryCompleter!.completeError(const TickerCanceled());
}
}
}
return _secondaryCompleter!.future;
}
@override
Stream<void> asStream() {
return _primaryCompleter.future.asStream();
}
@override
Future<void> catchError(Function onError, { bool Function(Object)? test }) {
return _primaryCompleter.future.catchError(onError, test: test);
}
@override
Future<R> then<R>(FutureOr<R> Function(void value) onValue, { Function? onError }) {
return _primaryCompleter.future.then<R>(onValue, onError: onError);
}
@override
Future<void> timeout(Duration timeLimit, { FutureOr<void> Function()? onTimeout }) {
return _primaryCompleter.future.timeout(timeLimit, onTimeout: onTimeout);
}
@override
Future<void> whenComplete(dynamic Function() action) {
return _primaryCompleter.future.whenComplete(action);
}
@override
String toString() => '${describeIdentity(this)}(${ _completed == null ? "active" : _completed! ? "complete" : "canceled" })';
}
/// Exception thrown by [Ticker] objects on the [TickerFuture.orCancel] future
/// when the ticker is canceled.
/// 当计时器被取消时,[Ticker] 对象在 [TickerFuture.orCancel] future 上
/// 抛出的异常。
class TickerCanceled implements Exception {
/// Creates a canceled-ticker exception.
/// 创建一个取消计时器异常。
const TickerCanceled([this.ticker]);
/// Reference to the [Ticker] object that was canceled.
/// 对被取消的 [Ticker] 对象的引用。
///
/// This may be null in the case that the [Future] created for
/// [TickerFuture.orCancel] was created after the ticker was canceled.
/// 在为 [TickerFuture.orCancel] 创建的 [Future] 在计时器被取消后创建的情况下,
/// 这可能为 null。
final Ticker? ticker;
@override
String toString() {
if (ticker != null) {
return 'This ticker was canceled: $ticker';
}
return 'The ticker was canceled before the "orCancel" property was first used.';
}
}
Ticker的工作原理
Ticker是如何工作的?
Ticker是Flutter动画系统的核心组件,它的工作原理可以概括为一个精密的帧同步机制。当你调用 start() 方法启动Ticker时,它会通过 scheduleTick() 方法向SchedulerBinding注册一个帧回调,这个回调就是内部的 _tick 方法。每当系统准备渲染新的一帧时,Flutter引擎会触发所有注册的帧回调,此时 _tick 方法被调用。
_tick方法具体做了什么?
_tick 方法是Ticker的心脏,它接收当前帧的时间戳作为参数。首先,它会检查并设置 _startTime (如果这是第一次调用),然后计算从启动时间到当前时间的elapsed时间差,最后调用用户提供的 _onTick 回调函数,将这个时间差传递给它。这样,用户的回调函数就能知道动画已经运行了多长时间,从而更新动画状态。
_onTick回调的作用是什么?
_onTick 是用户在创建Ticker时提供的回调函数,它是连接Ticker和具体动画逻辑的桥梁。每次 _tick 被调用时, _onTick 都会收到一个Duration参数,表示动画已经运行的时间。AnimationController等高级动画组件正是通过这个回调来更新动画值、触发监听器、重建UI等。可以说, _onTick 是动画真正"动起来"的地方。
scheduleTick(rescheduling: true)的意义何在?
这行代码体现了Ticker的自我维持机制。当 _tick 方法执行完用户的 _onTick 回调后,它会检查 shouldScheduleTick 属性。如果Ticker仍然处于活动状态且未被静音,它会再次调用 scheduleTick(rescheduling: true) 为下一帧注册回调。 rescheduling: true 参数告诉调度器这是一个重新安排的回调,而不是全新的请求。这种机制确保了动画能够持续运行,直到显式调用 stop() 方法或Ticker被释放。这就是为什么一个简单的 start() 调用就能让动画持续运行60fps的原因——每一帧都会自动安排下一帧的执行。