AnimationController 是 Flutter 动画系统的核心控制器,主要负责:
- 控制动画的播放、暂停、停止
- 管理动画值的变化范围(0.0-1.0 或自定义边界)
- 提供动画状态监听和值变化通知
- 与 Ticker 系统集成,实现帧同步动画
主要功能
1. 生命周期
- AnimationController() - 标准构造函数,有边界限制
- AnimationController.unbounded() - 无边界构造函数,适用物理模拟
- dispose() - 资源释放,防止内存泄漏
- resync() - 重新同步 TickerProvider
2. 基础控制方法
- forward() - 向前播放动画
- reverse() - 反向播放动画
- toggle() - 智能切换动画方向
- stop() - 停止动画
- reset() - 重置到初始状态
3. 高级动画控制
- animateTo(target) - 动画到指定值
- animateBack(target) - 反向动画到指定值
- repeat() - 循环播放动画
- fling() - 弹性动画效果
- animateWith(simulation) - 自定义物理模拟
4. 状态和属性
- value - 当前动画值
- status - 动画状态(forward/reverse/completed/dismissed)
- isAnimating - 是否正在动画
- velocity - 当前速度
- duration/reverseDuration - 动画持续时间
重要实战
1. 内存管理
scala
// ✅ 正确的生命周期管理
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
}
@override
void dispose() {
_controller.dispose(); // 🔑 关键:防止内存泄漏
super.dispose();
}
}
2. 异常处理
csharp
// ✅ 使用 TickerFuture.orCancel 处理取消
try {
await _controller.forward().orCancel;
// 动画完成后的逻辑
} on TickerCanceled {
// 动画被取消时的处理
}
scss
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// @docImport 'package:flutter/widgets.dart';
/// @docImport 'package:flutter_test/flutter_test.dart';
library;
import 'dart:ui' as ui show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart';
import 'animation.dart';
import 'curves.dart';
import 'listener_helpers.dart';
export 'package:flutter/physics.dart' show Simulation, SpringDescription;
export 'package:flutter/scheduler.dart' show TickerFuture, TickerProvider;
export 'animation.dart' show Animation, AnimationStatus;
export 'curves.dart' show Curve;
const String _flutterAnimationLibrary = 'package:flutter/animation.dart';
// Examples can assume:
// late AnimationController _controller, fadeAnimationController, sizeAnimationController;
// late bool dismissed;
// void setState(VoidCallback fn) { }
/// The direction in which an animation is running.
/// 动画运行的方向。
enum _AnimationDirection {
/// The animation is running from beginning to end.
/// 动画从开始到结束运行。
forward,
/// The animation is running backwards, from end to beginning.
/// 动画从结束到开始反向运行。
reverse,
}
final SpringDescription _kFlingSpringDescription = SpringDescription.withDampingRatio(
mass: 1.0,
stiffness: 500.0,
);
const Tolerance _kFlingTolerance = Tolerance(
velocity: double.infinity,
distance: 0.01,
);
/// Configures how an [AnimationController] behaves when animations are
/// disabled.
///
/// When [AccessibilityFeatures.disableAnimations] is true, the device is asking
/// Flutter to reduce or disable animations as much as possible. To honor this,
/// we reduce the duration and the corresponding number of frames for
/// animations. This enum is used to allow certain [AnimationController]s to opt
/// out of this behavior.
///
/// For example, the [AnimationController] which controls the physics simulation
/// for a scrollable list will have [AnimationBehavior.preserve], so that when
/// a user attempts to scroll it does not jump to the end/beginning too quickly.
/// 配置当动画被禁用时 [AnimationController] 的行为方式。
///
/// 当 [AccessibilityFeatures.disableAnimations] 为 true 时,设备要求
/// Flutter 尽可能减少或禁用动画。为了遵守这一点,
/// 我们减少动画的持续时间和相应的帧数。
/// 此枚举用于允许某些 [AnimationController] 选择退出此行为。
///
/// 例如,控制可滚动列表物理模拟的 [AnimationController]
/// 将具有 [AnimationBehavior.preserve],这样当用户尝试滚动时,
/// 它不会过快地跳到结束/开始位置。
enum AnimationBehavior {
/// The [AnimationController] will reduce its duration when
/// [AccessibilityFeatures.disableAnimations] is true.
/// 当 [AccessibilityFeatures.disableAnimations] 为 true 时,
/// [AnimationController] 将减少其持续时间。
normal,
/// The [AnimationController] will preserve its behavior.
///
/// This is the default for repeating animations in order to prevent them from
/// flashing rapidly on the screen if the widget does not take the
/// [AccessibilityFeatures.disableAnimations] flag into account.
/// [AnimationController] 将保持其行为。
///
/// 这是重复动画的默认值,以防止如果 widget 没有考虑
/// [AccessibilityFeatures.disableAnimations] 标志时,
/// 它们在屏幕上快速闪烁。
preserve,
}
/// A controller for an animation.
///
/// This class lets you perform tasks such as:
///
/// * Play an animation [forward] or in [reverse], or [stop] an animation.
/// * Set the animation to a specific [value].
/// * Define the [upperBound] and [lowerBound] values of an animation.
/// * Create a [fling] animation effect using a physics simulation.
///
/// By default, an [AnimationController] linearly produces values that range
/// from 0.0 to 1.0, during a given duration.
/// 动画的控制器。
///
/// 此类允许您执行以下任务:
///
/// * 播放动画 [forward] 或 [reverse],或 [stop] 动画。
/// * 将动画设置为特定的 [value]。
/// * 定义动画的 [upperBound] 和 [lowerBound] 值。
/// * 使用物理模拟创建 [fling] 动画效果。
///
/// 默认情况下,[AnimationController] 在给定的持续时间内
/// 线性产生从 0.0 到 1.0 的值。
///
/// When the animation is actively animating, the animation controller generates
/// a new value each time the device running your app is ready to display a new
/// frame (typically, this rate is around 60--120 values per second).
/// If the animation controller is associated with a [State]
/// through a [TickerProvider], then its updates will be silenced when that
/// [State]'s subtree is disabled as defined by [TickerMode]; time will still
/// elapse, and methods like [forward] and [stop] can still be called and
/// will change the value, but the controller will not generate new values
/// on its own.
/// 当动画正在活跃地动画时,动画控制器在运行您应用的设备
/// 准备显示新帧时生成新值(通常,此速率约为每秒 60-120 个值)。
/// 如果动画控制器通过 [TickerProvider] 与 [State] 关联,
/// 那么当该 [State] 的子树按 [TickerMode] 定义被禁用时,其更新将被静音;
/// 时间仍会流逝,像 [forward] 和 [stop] 这样的方法仍可以被调用
/// 并将改变值,但控制器不会自己生成新值。
///
/// ## Ticker providers
///
/// An [AnimationController] needs a [TickerProvider], which is configured using
/// the `vsync` argument on the constructor.
/// The constructor uses the [TickerProvider] to create a [Ticker], which
/// the [AnimationController] uses to step through the animation it controls.
///
/// For advice on obtaining a ticker provider, see [TickerProvider].
/// Typically the relevant [State] serves as the ticker provider,
/// after applying a suitable mixin (like [SingleTickerProviderStateMixin])
/// to cause the [State] subclass to implement [TickerProvider].
/// ## Ticker 提供者
///
/// [AnimationController] 需要一个 [TickerProvider],
/// 它使用构造函数上的 `vsync` 参数进行配置。
/// 构造函数使用 [TickerProvider] 创建一个 [Ticker],
/// [AnimationController] 使用它来逐步执行它控制的动画。
///
/// 有关获取 ticker 提供者的建议,请参阅 [TickerProvider]。
/// 通常相关的 [State] 作为 ticker 提供者,
/// 在应用合适的 mixin(如 [SingleTickerProviderStateMixin])后
/// 使 [State] 子类实现 [TickerProvider]。
///
/// ## Life cycle
///
/// An [AnimationController] should be [dispose]d when it is no longer needed.
/// This reduces the likelihood of leaks. When used with a [StatefulWidget], it
/// is common for an [AnimationController] to be created in the
/// [State.initState] method and then disposed in the [State.dispose] method.
/// ## 生命周期
///
/// 当不再需要 [AnimationController] 时应该 [dispose] 它。
/// 这减少了泄漏的可能性。当与 [StatefulWidget] 一起使用时,
/// 通常在 [State.initState] 方法中创建 [AnimationController],
/// 然后在 [State.dispose] 方法中释放它。
///
/// ## Using [Future]s with [AnimationController]
///
/// The methods that start animations return a [TickerFuture] object which
/// completes when the animation completes successfully, and never throws an
/// error; if the animation is canceled, the future never completes. This object
/// also has a [TickerFuture.orCancel] property which returns a future that
/// completes when the animation completes successfully, and completes with an
/// error when the animation is aborted.
///
/// This can be used to write code such as the `fadeOutAndUpdateState` method
/// below.
/// ## 将 [Future] 与 [AnimationController] 一起使用
///
/// 启动动画的方法返回一个 [TickerFuture] 对象,
/// 当动画成功完成时完成,并且永远不会抛出错误;
/// 如果动画被取消,future 永远不会完成。此对象
/// 还有一个 [TickerFuture.orCancel] 属性,它返回一个 future,
/// 当动画成功完成时完成,当动画被中止时以错误完成。
///
/// 这可以用来编写如下面的 `fadeOutAndUpdateState` 方法这样的代码。
///
/// {@tool snippet}
///
/// Here is a stateful `Foo` widget. Its [State] uses the
/// [SingleTickerProviderStateMixin] to implement the necessary
/// [TickerProvider], creating its controller in the [State.initState] method
/// and disposing of it in the [State.dispose] method. The duration of the
/// controller is configured from a property in the `Foo` widget; as that
/// changes, the [State.didUpdateWidget] method is used to update the
/// controller.
/// 这是一个有状态的 `Foo` widget。它的 [State] 使用
/// [SingleTickerProviderStateMixin] 来实现必要的 [TickerProvider],
/// 在 [State.initState] 方法中创建其控制器,
/// 并在 [State.dispose] 方法中释放它。控制器的持续时间
/// 从 `Foo` widget 中的属性配置;当该属性发生变化时,
/// 使用 [State.didUpdateWidget] 方法来更新控制器。
///
/// ```dart
/// class Foo extends StatefulWidget {
/// const Foo({ super.key, required this.duration });
///
/// final Duration duration;
///
/// @override
/// State<Foo> createState() => _FooState();
/// }
///
/// class _FooState extends State<Foo> with SingleTickerProviderStateMixin {
/// late AnimationController _controller;
///
/// @override
/// void initState() {
/// super.initState();
/// _controller = AnimationController(
/// vsync: this, // the SingleTickerProviderStateMixin
/// duration: widget.duration,
/// );
/// }
///
/// @override
/// void didUpdateWidget(Foo oldWidget) {
/// super.didUpdateWidget(oldWidget);
/// _controller.duration = widget.duration;
/// }
///
/// @override
/// void dispose() {
/// _controller.dispose();
/// super.dispose();
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Container(); // ...
/// }
/// }
/// ```
/// {@end-tool}
/// {@tool snippet}
///
/// The following method (for a [State] subclass) drives two animation
/// controllers using Dart's asynchronous syntax for awaiting [Future] objects:
/// 以下方法(用于 [State] 子类)使用 Dart 的异步语法
/// 等待 [Future] 对象来驱动两个动画控制器:
///
/// ```dart
/// Future<void> fadeOutAndUpdateState() async {
/// try {
/// await fadeAnimationController.forward().orCancel;
/// await sizeAnimationController.forward().orCancel;
/// setState(() {
/// dismissed = true;
/// });
/// } on TickerCanceled {
/// // the animation got canceled, probably because we were disposed
/// }
/// }
/// ```
/// {@end-tool}
///
/// The assumption in the code above is that the animation controllers are being
/// disposed in the [State] subclass' override of the [State.dispose] method.
/// Since disposing the controller cancels the animation (raising a
/// [TickerCanceled] exception), the code here can skip verifying whether
/// [State.mounted] is still true at each step. (Again, this assumes that the
/// controllers are created in [State.initState] and disposed in
/// [State.dispose], as described in the previous section.)
/// 上面代码中的假设是动画控制器在 [State] 子类的
/// [State.dispose] 方法重写中被释放。
/// 由于释放控制器会取消动画(抛出 [TickerCanceled] 异常),
/// 这里的代码可以跳过在每一步验证 [State.mounted] 是否仍为 true。
///(再次强调,这假设控制器在 [State.initState] 中创建
/// 并在 [State.dispose] 中释放,如前一节所述。)
///
/// {@tool dartpad}
/// This example shows how to use [AnimationController] and
/// [SlideTransition] to create an animated digit like you might find
/// on an old pinball machine our your car's odometer. New digit
/// values slide into place from below, as the old value slides
/// upwards and out of view. Taps that occur while the controller is
/// already animating cause the controller's
/// [AnimationController.duration] to be reduced so that the visuals
/// don't fall behind.
/// 此示例展示如何使用 [AnimationController] 和 [SlideTransition]
/// 创建动画数字,就像您可能在老式弹球机或汽车里程表上找到的那样。
/// 新的数字值从下方滑入位置,而旧值向上滑动并消失在视野之外。
/// 在控制器已经在动画时发生的点击会导致控制器的
/// [AnimationController.duration] 被减少,以便视觉效果不会落后。
///
/// ** See code in examples/api/lib/animation/animation_controller/animated_digit.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [Tween], the base class for converting an [AnimationController] to a
/// range of values of other types.
/// 另请参阅:
///
/// * [Tween],将 [AnimationController] 转换为其他类型值范围的基类。
class AnimationController extends Animation<double>
with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
/// Creates an animation controller.
///
/// * `value` is the initial value of the animation. If defaults to the lower
/// bound.
///
/// * [duration] is the length of time this animation should last.
///
/// * [debugLabel] is a string to help identify this animation during
/// debugging (used by [toString]).
///
/// * [lowerBound] is the smallest value this animation can obtain and the
/// value at which this animation is deemed to be dismissed.
///
/// * [upperBound] is the largest value this animation can obtain and the
/// value at which this animation is deemed to be completed.
///
/// * `vsync` is the required [TickerProvider] for the current context. It can
/// be changed by calling [resync]. See [TickerProvider] for advice on
/// obtaining a ticker provider.
/// 创建动画控制器。
///
/// * `value` 是动画的初始值。默认为下界。
///
/// * [duration] 是此动画应持续的时间长度。
///
/// * [debugLabel] 是在调试期间帮助识别此动画的字符串(由 [toString] 使用)。
///
/// * [lowerBound] 是此动画可以获得的最小值,
/// 也是此动画被视为已解除的值。
///
/// * [upperBound] 是此动画可以获得的最大值,
/// 也是此动画被视为已完成的值。
///
/// * `vsync` 是当前上下文所需的 [TickerProvider]。
/// 可以通过调用 [resync] 来更改它。有关获取 ticker 提供者的建议,
/// 请参阅 [TickerProvider]。
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(upperBound >= lowerBound),
_direction = _AnimationDirection.forward {
if (kFlutterMemoryAllocationsEnabled) {
_maybeDispatchObjectCreation();
}
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
/// Creates an animation controller with no upper or lower bound for its
/// value.
///
/// * [value] is the initial value of the animation.
///
/// * [duration] is the length of time this animation should last.
///
/// * [debugLabel] is a string to help identify this animation during
/// debugging (used by [toString]).
///
/// * `vsync` is the required [TickerProvider] for the current context. It can
/// be changed by calling [resync]. See [TickerProvider] for advice on
/// obtaining a ticker provider.
///
/// This constructor is most useful for animations that will be driven using a
/// physics simulation, especially when the physics simulation has no
/// pre-determined bounds.
/// 创建一个对其值没有上界或下界的动画控制器。
///
/// * [value] 是动画的初始值。
///
/// * [duration] 是此动画应持续的时间长度。
///
/// * [debugLabel] 是在调试期间帮助识别此动画的字符串(由 [toString] 使用)。
///
/// * `vsync` 是当前上下文所需的 [TickerProvider]。
/// 可以通过调用 [resync] 来更改它。有关获取 ticker 提供者的建议,
/// 请参阅 [TickerProvider]。
///
/// 此构造函数对于将使用物理模拟驱动的动画最有用,
/// 特别是当物理模拟没有预定边界时。
AnimationController.unbounded({
double value = 0.0,
this.duration,
this.reverseDuration,
this.debugLabel,
required TickerProvider vsync,
this.animationBehavior = AnimationBehavior.preserve,
}) : lowerBound = double.negativeInfinity,
upperBound = double.infinity,
_direction = _AnimationDirection.forward {
if (kFlutterMemoryAllocationsEnabled) {
_maybeDispatchObjectCreation();
}
_ticker = vsync.createTicker(_tick);
_internalSetValue(value);
}
/// Dispatches event of object creation to [FlutterMemoryAllocations.instance].
/// 将对象创建事件分发到 [FlutterMemoryAllocations.instance]。
void _maybeDispatchObjectCreation() {
if (kFlutterMemoryAllocationsEnabled) {
FlutterMemoryAllocations.instance.dispatchObjectCreated(
library: _flutterAnimationLibrary,
className: '$AnimationController',
object: this,
);
}
}
/// The value at which this animation is deemed to be dismissed.
/// 此动画被视为已解除的值。
final double lowerBound;
/// The value at which this animation is deemed to be completed.
/// 此动画被视为已完成的值。
final double upperBound;
/// A label that is used in the [toString] output. Intended to aid with
/// identifying animation controller instances in debug output.
/// 在 [toString] 输出中使用的标签。旨在帮助在调试输出中识别动画控制器实例。
final String? debugLabel;
/// The behavior of the controller when [AccessibilityFeatures.disableAnimations]
/// is true.
///
/// Defaults to [AnimationBehavior.normal] for the [AnimationController.new]
/// constructor, and [AnimationBehavior.preserve] for the
/// [AnimationController.unbounded] constructor.
/// 当 [AccessibilityFeatures.disableAnimations] 为 true 时控制器的行为。
///
/// 对于 [AnimationController.new] 构造函数默认为 [AnimationBehavior.normal],
/// 对于 [AnimationController.unbounded] 构造函数默认为 [AnimationBehavior.preserve]。
final AnimationBehavior animationBehavior;
/// Returns an [Animation<double>] for this animation controller, so that a
/// pointer to this object can be passed around without allowing users of that
/// pointer to mutate the [AnimationController] state.
/// 为此动画控制器返回一个 [Animation<double>],以便可以传递此对象的指针
/// 而不允许该指针的用户改变 [AnimationController] 状态。
Animation<double> get view => this;
/// The length of time this animation should last.
///
/// If [reverseDuration] is specified, then [duration] is only used when going
/// [forward]. Otherwise, it specifies the duration going in both directions.
/// 此动画应持续的时间长度。
///
/// 如果指定了 [reverseDuration],则 [duration] 仅在 [forward] 时使用。
/// 否则,它指定两个方向的持续时间。
Duration? duration;
/// The length of time this animation should last when going in [reverse].
///
/// The value of [duration] is used if [reverseDuration] is not specified or
/// set to null.
/// 此动画在 [reverse] 时应持续的时间长度。
///
/// 如果未指定 [reverseDuration] 或设置为 null,则使用 [duration] 的值。
Duration? reverseDuration;
Ticker? _ticker;
/// Recreates the [Ticker] with the new [TickerProvider].
/// 使用新的 [TickerProvider] 重新创建 [Ticker]。
void resync(TickerProvider vsync) {
final Ticker oldTicker = _ticker!;
_ticker = vsync.createTicker(_tick);
_ticker!.absorbTicker(oldTicker);
}
Simulation? _simulation;
/// The current value of the animation.
///
/// Setting this value notifies all the listeners that the value
/// changed.
///
/// Setting this value also stops the controller if it is currently
/// running; if this happens, it also notifies all the status
/// listeners.
/// 动画的当前值。
///
/// 设置此值会通知所有监听器值已更改。
///
/// 设置此值还会停止控制器(如果它当前正在运行);
/// 如果发生这种情况,它还会通知所有状态监听器。
@override
double get value => _value;
late double _value;
/// Stops the animation controller and sets the current value of the
/// animation.
///
/// The new value is clamped to the range set by [lowerBound] and
/// [upperBound].
///
/// Value listeners are notified even if this does not change the value.
/// Status listeners are notified if the animation was previously playing.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
///
/// See also:
///
/// * [reset], which is equivalent to setting [value] to [lowerBound].
/// * [stop], which aborts the animation without changing its value or status
/// and without dispatching any notifications other than completing or
/// canceling the [TickerFuture].
/// * [forward], [reverse], [animateTo], [animateWith], [fling], and [repeat],
/// which start the animation controller.
/// 停止动画控制器并设置动画的当前值。
///
/// 新值被限制在由 [lowerBound] 和 [upperBound] 设置的范围内。
///
/// 即使这不会改变值,也会通知值监听器。
/// 如果动画之前正在播放,则会通知状态监听器。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
///
/// 另请参阅:
///
/// * [reset],等同于将 [value] 设置为 [lowerBound]。
/// * [stop],中止动画而不改变其值或状态,
/// 除了完成或取消 [TickerFuture] 外不分发任何通知。
/// * [forward]、[reverse]、[animateTo]、[animateWith]、[fling] 和 [repeat],
/// 启动动画控制器。
set value(double newValue) {
stop();
_internalSetValue(newValue);
notifyListeners();
_checkStatusChanged();
}
/// Sets the controller's value to [lowerBound], stopping the animation (if
/// in progress), and resetting to its beginning point, or dismissed state.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
///
/// See also:
///
/// * [value], which can be explicitly set to a specific value as desired.
/// * [forward], which starts the animation in the forward direction.
/// * [stop], which aborts the animation without changing its value or status
/// and without dispatching any notifications other than completing or
/// canceling the [TickerFuture].
/// 将控制器的值设置为 [lowerBound],停止动画(如果正在进行),
/// 并重置到其开始点或已解除状态。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
///
/// 另请参阅:
///
/// * [value],可以根据需要显式设置为特定值。
/// * [forward],在前进方向启动动画。
/// * [stop],中止动画而不改变其值或状态,
/// 除了完成或取消 [TickerFuture] 外不分发任何通知。
void reset() {
value = lowerBound;
}
/// The rate of change of [value] per second.
///
/// If [isAnimating] is false, then [value] is not changing and the rate of
/// change is zero.
/// 每秒 [value] 的变化率。
///
/// 如果 [isAnimating] 为 false,则 [value] 没有变化,变化率为零。
double get velocity {
if (!isAnimating) {
return 0.0;
}
return _simulation!.dx(lastElapsedDuration!.inMicroseconds.toDouble() / Duration.microsecondsPerSecond);
}
void _internalSetValue(double newValue) {
_value = clampDouble(newValue, lowerBound, upperBound);
if (_value == lowerBound) {
_status = AnimationStatus.dismissed;
} else if (_value == upperBound) {
_status = AnimationStatus.completed;
} else {
_status = switch (_direction) {
_AnimationDirection.forward => AnimationStatus.forward,
_AnimationDirection.reverse => AnimationStatus.reverse,
};
}
}
/// The amount of time that has passed between the time the animation started
/// and the most recent tick of the animation.
///
/// If the controller is not animating, the last elapsed duration is null.
/// 动画开始时间与动画最近一次滴答之间经过的时间量。
///
/// 如果控制器没有在动画,则最后经过的持续时间为 null。
Duration? get lastElapsedDuration => _lastElapsedDuration;
Duration? _lastElapsedDuration;
/// Whether this animation is currently animating in either the forward or reverse direction.
///
/// This is separate from whether it is actively ticking. An animation
/// controller's ticker might get muted, in which case the animation
/// controller's callbacks will no longer fire even though time is continuing
/// to pass. See [Ticker.muted] and [TickerMode].
///
/// If the animation was stopped (e.g. with [stop] or by setting a new [value]),
/// [isAnimating] will return `false` but the [status] will not change,
/// so the value of [AnimationStatus.isAnimating] might still be `true`.
/// 此动画当前是否正在前进或反向方向动画。
///
/// 这与是否正在活跃地滴答是分开的。动画控制器的 ticker 可能会被静音,
/// 在这种情况下,即使时间继续流逝,动画控制器的回调也不会再触发。
/// 请参阅 [Ticker.muted] 和 [TickerMode]。
///
/// 如果动画被停止(例如使用 [stop] 或设置新的 [value]),
/// [isAnimating] 将返回 `false`,但 [status] 不会改变,
/// 因此 [AnimationStatus.isAnimating] 的值可能仍为 `true`。
@override
bool get isAnimating => _ticker != null && _ticker!.isActive;
_AnimationDirection _direction;
@override
AnimationStatus get status => _status;
late AnimationStatus _status;
/// Starts running this animation forwards (towards the end).
///
/// Returns a [TickerFuture] that completes when the animation is complete.
///
/// If [from] is non-null, it will be set as the current [value] before running
/// the animation.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
///
/// During the animation, [status] is reported as [AnimationStatus.forward],
/// which switches to [AnimationStatus.completed] when [upperBound] is
/// reached at the end of the animation.
/// 开始向前运行此动画(朝向结束)。
///
/// 返回一个在动画完成时完成的 [TickerFuture]。
///
/// 如果 [from] 不为 null,它将在运行动画之前设置为当前 [value]。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
///
/// 在动画期间,[status] 报告为 [AnimationStatus.forward],
/// 当在动画结束时达到 [upperBound] 时切换到 [AnimationStatus.completed]。
TickerFuture forward({ double? from }) {
assert(() {
if (duration == null) {
throw FlutterError(
'AnimationController.forward() called with no default duration.\n'
'The "duration" property should be set, either in the constructor or later, before '
'calling the forward() function.',
);
}
return true;
}());
assert(
_ticker != null,
'AnimationController.forward() called after AnimationController.dispose()\n'
'AnimationController methods should not be used after calling dispose.',
);
_direction = _AnimationDirection.forward;
if (from != null) {
value = from;
}
return _animateToInternal(upperBound);
}
/// Starts running this animation in reverse (towards the beginning).
///
/// Returns a [TickerFuture] that completes when the animation is dismissed.
///
/// If [from] is non-null, it will be set as the current [value] before running
/// the animation.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
///
/// During the animation, [status] is reported as [AnimationStatus.reverse],
/// which switches to [AnimationStatus.dismissed] when [lowerBound] is
/// reached at the end of the animation.
/// 开始反向运行此动画(朝向开始)。
///
/// 返回一个在动画被解除时完成的 [TickerFuture]。
///
/// 如果 [from] 不为 null,它将在运行动画之前设置为当前 [value]。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
///
/// 在动画期间,[status] 报告为 [AnimationStatus.reverse],
/// 当在动画结束时达到 [lowerBound] 时切换到 [AnimationStatus.dismissed]。
TickerFuture reverse({ double? from }) {
assert(() {
if (duration == null && reverseDuration == null) {
throw FlutterError(
'AnimationController.reverse() called with no default duration or reverseDuration.\n'
'The "duration" or "reverseDuration" property should be set, either in the constructor or later, before '
'calling the reverse() function.',
);
}
return true;
}());
assert(
_ticker != null,
'AnimationController.reverse() called after AnimationController.dispose()\n'
'AnimationController methods should not be used after calling dispose.',
);
_direction = _AnimationDirection.reverse;
if (from != null) {
value = from;
}
return _animateToInternal(lowerBound);
}
/// Toggles the direction of this animation, based on whether it [isForwardOrCompleted].
///
/// Specifically, this function acts the same way as [reverse] if the [status] is
/// either [AnimationStatus.forward] or [AnimationStatus.completed], and acts as
/// [forward] for [AnimationStatus.reverse] or [AnimationStatus.dismissed].
///
/// If [from] is non-null, it will be set as the current [value] before running
/// the animation.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
/// 根据 [isForwardOrCompleted] 切换此动画的方向。
///
/// 具体来说,如果 [status] 是 [AnimationStatus.forward] 或 [AnimationStatus.completed],
/// 此函数的行为与 [reverse] 相同,对于 [AnimationStatus.reverse] 或 [AnimationStatus.dismissed]
/// 则表现为 [forward]。
///
/// 如果 [from] 不为 null,它将在运行动画之前设置为当前 [value]。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
TickerFuture toggle({ double? from }) {
assert(() {
Duration? duration = this.duration;
if (isForwardOrCompleted) {
duration ??= reverseDuration;
}
if (duration == null) {
throw FlutterError(
'AnimationController.toggle() called with no default duration.\n'
'The "duration" property should be set, either in the constructor or later, before '
'calling the toggle() function.',
);
}
return true;
}());
assert(
_ticker != null,
'AnimationController.toggle() called after AnimationController.dispose()\n'
'AnimationController methods should not be used after calling dispose.',
);
_direction = isForwardOrCompleted ? _AnimationDirection.reverse : _AnimationDirection.forward;
if (from != null) {
value = from;
}
return _animateToInternal(switch (_direction) {
_AnimationDirection.forward => upperBound,
_AnimationDirection.reverse => lowerBound,
});
}
/// Drives the animation from its current value to the given target, "forward".
///
/// Returns a [TickerFuture] that completes when the animation is complete.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
///
/// During the animation, [status] is reported as [AnimationStatus.forward]
/// regardless of whether `target` > [value] or not. At the end of the
/// animation, when `target` is reached, [status] is reported as
/// [AnimationStatus.completed].
///
/// If the `target` argument is the same as the current [value] of the
/// animation, then this won't animate, and the returned [TickerFuture] will
/// be already complete.
/// 将动画从当前值驱动到给定目标,"向前"。
///
/// 返回一个在动画完成时完成的 [TickerFuture]。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
///
/// 在动画期间,无论 `target` > [value] 与否,[status] 都报告为 [AnimationStatus.forward]。
/// 在动画结束时,当达到 `target` 时,[status] 报告为 [AnimationStatus.completed]。
///
/// 如果 `target` 参数与动画的当前 [value] 相同,
/// 则不会进行动画,返回的 [TickerFuture] 将已经完成。
TickerFuture animateTo(double target, { Duration? duration, Curve curve = Curves.linear }) {
assert(() {
if (this.duration == null && duration == null) {
throw FlutterError(
'AnimationController.animateTo() called with no explicit duration and no default duration.\n'
'Either the "duration" argument to the animateTo() method should be provided, or the '
'"duration" property should be set, either in the constructor or later, before '
'calling the animateTo() function.',
);
}
return true;
}());
assert(
_ticker != null,
'AnimationController.animateTo() called after AnimationController.dispose()\n'
'AnimationController methods should not be used after calling dispose.',
);
_direction = _AnimationDirection.forward;
return _animateToInternal(target, duration: duration, curve: curve);
}
/// Drives the animation from its current value to the given target, "backward".
///
/// Returns a [TickerFuture] that completes when the animation is complete.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
///
/// During the animation, [status] is reported as [AnimationStatus.reverse]
/// regardless of whether `target` < [value] or not. At the end of the
/// animation, when `target` is reached, [status] is reported as
/// [AnimationStatus.dismissed].
///
/// If the `target` argument is the same as the current [value] of the
/// animation, then this won't animate, and the returned [TickerFuture] will
/// be already complete.
/// 将动画从当前值驱动到给定目标,"向后"。
///
/// 返回一个在动画完成时完成的 [TickerFuture]。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
///
/// 在动画期间,无论 `target` < [value] 与否,[status] 都报告为 [AnimationStatus.reverse]。
/// 在动画结束时,当达到 `target` 时,[status] 报告为 [AnimationStatus.dismissed]。
///
/// 如果 `target` 参数与动画的当前 [value] 相同,
/// 则不会进行动画,返回的 [TickerFuture] 将已经完成。
TickerFuture animateBack(double target, { Duration? duration, Curve curve = Curves.linear }) {
assert(() {
if (this.duration == null && reverseDuration == null && duration == null) {
throw FlutterError(
'AnimationController.animateBack() called with no explicit duration and no default duration or reverseDuration.\n'
'Either the "duration" argument to the animateBack() method should be provided, or the '
'"duration" or "reverseDuration" property should be set, either in the constructor or later, before '
'calling the animateBack() function.',
);
}
return true;
}());
assert(
_ticker != null,
'AnimationController.animateBack() called after AnimationController.dispose()\n'
'AnimationController methods should not be used after calling dispose.',
);
_direction = _AnimationDirection.reverse;
return _animateToInternal(target, duration: duration, curve: curve);
}
TickerFuture _animateToInternal(double target, { Duration? duration, Curve curve = Curves.linear }) {
final double scale = switch (animationBehavior) {
// Since the framework cannot handle zero duration animations, we run it at 5% of the normal
// duration to limit most animations to a single frame.
// Ideally, the framework would be able to handle zero duration animations, however, the common
// pattern of an eternally repeating animation might cause an endless loop if it weren't delayed
// for at least one frame.
AnimationBehavior.normal when SemanticsBinding.instance.disableAnimations => 0.05,
AnimationBehavior.normal || AnimationBehavior.preserve => 1.0,
};
Duration? simulationDuration = duration;
if (simulationDuration == null) {
assert(!(this.duration == null && _direction == _AnimationDirection.forward));
assert(!(this.duration == null && _direction == _AnimationDirection.reverse && reverseDuration == null));
final double range = upperBound - lowerBound;
final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
final Duration directionDuration =
(_direction == _AnimationDirection.reverse && reverseDuration != null)
? reverseDuration!
: this.duration!;
simulationDuration = directionDuration * remainingFraction;
} else if (target == value) {
// Already at target, don't animate.
simulationDuration = Duration.zero;
}
stop();
if (simulationDuration == Duration.zero) {
if (value != target) {
_value = clampDouble(target, lowerBound, upperBound);
notifyListeners();
}
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
_checkStatusChanged();
return TickerFuture.complete();
}
assert(simulationDuration > Duration.zero);
assert(!isAnimating);
return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
}
/// Starts running this animation in the forward direction, and
/// restarts the animation when it completes.
///
/// Defaults to repeating between the [lowerBound] and [upperBound] of the
/// [AnimationController] when no explicit value is set for [min] and [max].
///
/// With [reverse] set to true, instead of always starting over at [min]
/// the starting value will alternate between [min] and [max] values on each
/// repeat. The [status] will be reported as [AnimationStatus.reverse] when
/// the animation runs from [max] to [min].
///
/// Each run of the animation will have a duration of `period`. If `period` is not
/// provided, [duration] will be used instead, which has to be set before [repeat] is
/// called either in the constructor or later by using the [duration] setter.
///
/// If a value is passed to [count], the animation will perform that many
/// iterations before stopping. Otherwise, the animation repeats indefinitely.
///
/// Returns a [TickerFuture] that never completes, unless a [count] is specified.
/// The [TickerFuture.orCancel] future completes with an error when the animation is
/// stopped (e.g. with [stop]).
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
/// 开始向前运行此动画,并在完成时重新启动动画。
///
/// 当没有为 [min] 和 [max] 设置明确值时,默认在 [AnimationController] 的
/// [lowerBound] 和 [upperBound] 之间重复。
///
/// 当 [reverse] 设置为 true 时,起始值将在每次重复时在 [min] 和 [max] 值之间交替,
/// 而不是总是从 [min] 开始。当动画从 [max] 运行到 [min] 时,
/// [status] 将报告为 [AnimationStatus.reverse]。
///
/// 动画的每次运行将具有 `period` 的持续时间。如果未提供 `period`,
/// 将使用 [duration] 代替,必须在调用 [repeat] 之前设置,
/// 无论是在构造函数中还是稍后使用 [duration] setter。
///
/// 如果向 [count] 传递值,动画将在停止前执行那么多次迭代。
/// 否则,动画将无限重复。
///
/// 返回一个永不完成的 [TickerFuture],除非指定了 [count]。
/// 当动画停止时(例如使用 [stop]),[TickerFuture.orCancel] future 以错误完成。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
TickerFuture repeat({
double? min,
double? max,
bool reverse = false,
Duration? period,
int? count,
}) {
min ??= lowerBound;
max ??= upperBound;
period ??= duration;
assert(() {
if (period == null) {
throw FlutterError(
'AnimationController.repeat() called without an explicit period and with no default Duration.\n'
'Either the "period" argument to the repeat() method should be provided, or the '
'"duration" property should be set, either in the constructor or later, before '
'calling the repeat() function.',
);
}
return true;
}());
assert(max >= min);
assert(max <= upperBound && min >= lowerBound);
assert(count == null || count > 0, 'Count shall be greater than zero if not null');
stop();
return _startSimulation(_RepeatingSimulation(_value, min, max, reverse, period!, _directionSetter, count));
}
void _directionSetter(_AnimationDirection direction) {
_direction = direction;
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.forward :
AnimationStatus.reverse;
_checkStatusChanged();
}
/// Drives the animation with a spring (within [lowerBound] and [upperBound])
/// and initial velocity.
///
/// If velocity is positive, the animation will complete, otherwise it will
/// dismiss. The velocity is specified in units per second. If the
/// [SemanticsBinding.disableAnimations] flag is set, the velocity is somewhat
/// arbitrarily multiplied by 200.
///
/// The [springDescription] parameter can be used to specify a custom
/// [SpringType.criticallyDamped] or [SpringType.overDamped] spring with which
/// to drive the animation. By default, a [SpringType.criticallyDamped] spring
/// is used. See [SpringDescription.withDampingRatio] for how to create a
/// suitable [SpringDescription].
///
/// The resulting spring simulation cannot be of type [SpringType.underDamped];
/// such a spring would oscillate rather than fling.
///
/// Returns a [TickerFuture] that completes when the animation is complete.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
/// 使用弹簧(在 [lowerBound] 和 [upperBound] 内)和初始速度驱动动画。
///
/// 如果速度为正,动画将完成,否则将解除。速度以每秒单位指定。
/// 如果设置了 [SemanticsBinding.disableAnimations] 标志,
/// 速度会被任意地乘以 200。
///
/// [springDescription] 参数可用于指定自定义的 [SpringType.criticallyDamped]
/// 或 [SpringType.overDamped] 弹簧来驱动动画。默认情况下,
/// 使用 [SpringType.criticallyDamped] 弹簧。
/// 请参阅 [SpringDescription.withDampingRatio] 了解如何创建合适的 [SpringDescription]。
///
/// 生成的弹簧模拟不能是 [SpringType.underDamped] 类型;
/// 这样的弹簧会振荡而不是抛掷。
///
/// 返回一个在动画完成时完成的 [TickerFuture]。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
TickerFuture fling({ double velocity = 1.0, SpringDescription? springDescription, AnimationBehavior? animationBehavior }) {
springDescription ??= _kFlingSpringDescription;
_direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
final double target = velocity < 0.0 ? lowerBound - _kFlingTolerance.distance
: upperBound + _kFlingTolerance.distance;
final AnimationBehavior behavior = animationBehavior ?? this.animationBehavior;
final double scale = switch (behavior) {
// This is arbitrary (it was chosen because it worked for the drawer widget).
AnimationBehavior.normal when SemanticsBinding.instance.disableAnimations => 200.0,
AnimationBehavior.normal || AnimationBehavior.preserve => 1.0,
};
final SpringSimulation simulation = SpringSimulation(springDescription, value, target, velocity * scale)
..tolerance = _kFlingTolerance;
assert(
simulation.type != SpringType.underDamped,
'The specified spring simulation is of type SpringType.underDamped.\n'
'An underdamped spring results in oscillation rather than a fling. '
'Consider specifying a different springDescription, or use animateWith() '
'with an explicit SpringSimulation if an underdamped spring is intentional.',
);
stop();
return _startSimulation(simulation);
}
/// Drives the animation according to the given simulation.
///
/// The values from the simulation are clamped to the [lowerBound] and
/// [upperBound]. To avoid this, consider creating the [AnimationController]
/// using the [AnimationController.unbounded] constructor.
///
/// Returns a [TickerFuture] that completes when the animation is complete.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
///
/// The [status] is always [AnimationStatus.forward] for the entire duration
/// of the simulation.
/// 根据给定的模拟驱动动画。
///
/// 来自模拟的值被限制在 [lowerBound] 和 [upperBound] 之间。
/// 要避免这种情况,请考虑使用 [AnimationController.unbounded] 构造函数
/// 创建 [AnimationController]。
///
/// 返回一个在动画完成时完成的 [TickerFuture]。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
///
/// 在整个模拟持续时间内,[status] 始终为 [AnimationStatus.forward]。
TickerFuture animateWith(Simulation simulation) {
assert(
_ticker != null,
'AnimationController.animateWith() called after AnimationController.dispose()\n'
'AnimationController methods should not be used after calling dispose.',
);
stop();
_direction = _AnimationDirection.forward;
return _startSimulation(simulation);
}
TickerFuture _startSimulation(Simulation simulation) {
assert(!isAnimating);
_simulation = simulation;
_lastElapsedDuration = Duration.zero;
_value = clampDouble(simulation.x(0.0), lowerBound, upperBound);
final TickerFuture result = _ticker!.start();
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.forward :
AnimationStatus.reverse;
_checkStatusChanged();
return result;
}
/// Stops running this animation.
///
/// This does not trigger any notifications. The animation stops in its
/// current state.
///
/// By default, the most recently returned [TickerFuture] is marked as having
/// been canceled, meaning the future never completes and its
/// [TickerFuture.orCancel] derivative future completes with a [TickerCanceled]
/// error. By passing the `canceled` argument with the value false, this is
/// reversed, and the futures complete successfully.
///
/// See also:
///
/// * [reset], which stops the animation and resets it to the [lowerBound],
/// and which does send notifications.
/// * [forward], [reverse], [animateTo], [animateWith], [fling], and [repeat],
/// which restart the animation controller.
/// 停止运行此动画。
///
/// 这不会触发任何通知。动画在其当前状态下停止。
///
/// 默认情况下,最近返回的 [TickerFuture] 被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
/// 通过传递值为 false 的 `canceled` 参数,这种情况会被逆转,
/// future 会成功完成。
///
/// 另请参阅:
///
/// * [reset],停止动画并将其重置为 [lowerBound],并发送通知。
/// * [forward]、[reverse]、[animateTo]、[animateWith]、[fling] 和 [repeat],
/// 重新启动动画控制器。
void stop({ bool canceled = true }) {
assert(
_ticker != null,
'AnimationController.stop() called after AnimationController.dispose()\n'
'AnimationController methods should not be used after calling dispose.',
);
_simulation = null;
_lastElapsedDuration = null;
_ticker!.stop(canceled: canceled);
}
/// Release the resources used by this object. The object is no longer usable
/// after this method is called.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
/// 释放此对象使用的资源。调用此方法后,对象不再可用。
///
/// 最近返回的 [TickerFuture](如果有)被标记为已取消,
/// 意味着 future 永远不会完成,其 [TickerFuture.orCancel]
/// 派生 future 以 [TickerCanceled] 错误完成。
@override
void dispose() {
assert(() {
if (_ticker == null) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('AnimationController.dispose() called more than once.'),
ErrorDescription('A given $runtimeType cannot be disposed more than once.\n'),
DiagnosticsProperty<AnimationController>(
'The following $runtimeType object was disposed multiple times',
this,
style: DiagnosticsTreeStyle.errorProperty,
),
]);
}
return true;
}());
if (kFlutterMemoryAllocationsEnabled) {
FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
}
_ticker!.dispose();
_ticker = null;
clearStatusListeners();
clearListeners();
super.dispose();
}
AnimationStatus _lastReportedStatus = AnimationStatus.dismissed;
void _checkStatusChanged() {
final AnimationStatus newStatus = status;
if (_lastReportedStatus != newStatus) {
_lastReportedStatus = newStatus;
notifyStatusListeners(newStatus);
}
}
void _tick(Duration elapsed) {
_lastElapsedDuration = elapsed;
final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
assert(elapsedInSeconds >= 0.0);
_value = clampDouble(_simulation!.x(elapsedInSeconds), lowerBound, upperBound);
if (_simulation!.isDone(elapsedInSeconds)) {
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
stop(canceled: false);
}
notifyListeners();
_checkStatusChanged();
}
@override
String toStringDetails() {
final String paused = isAnimating ? '' : '; paused';
final String ticker = _ticker == null ? '; DISPOSED' : (_ticker!.muted ? '; silenced' : '');
String label = '';
assert(() {
if (debugLabel != null) {
label = '; for $debugLabel';
}
return true;
}());
final String more = '${super.toStringDetails()} ${value.toStringAsFixed(3)}';
return '$more$paused$ticker$label';
}
}
class _InterpolationSimulation extends Simulation {
_InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale)
: assert(duration.inMicroseconds > 0),
_durationInSeconds = (duration.inMicroseconds * scale) / Duration.microsecondsPerSecond;
final double _durationInSeconds;
final double _begin;
final double _end;
final Curve _curve;
@override
double x(double timeInSeconds) {
final double t = clampDouble(timeInSeconds / _durationInSeconds, 0.0, 1.0);
return switch (t) {
0.0 => _begin,
1.0 => _end,
_ => _begin + (_end - _begin) * _curve.transform(t),
};
}
@override
double dx(double timeInSeconds) {
final double epsilon = tolerance.time;
return (x(timeInSeconds + epsilon) - x(timeInSeconds - epsilon)) / (2 * epsilon);
}
@override
bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
}
typedef _DirectionSetter = void Function(_AnimationDirection direction);
class _RepeatingSimulation extends Simulation {
_RepeatingSimulation(
double initialValue,
this.min,
this.max,
this.reverse,
Duration period,
this.directionSetter,
this.count,
) : assert(
count == null || count > 0,
'Count shall be greater than zero if not null',
),
_periodInSeconds = period.inMicroseconds / Duration.microsecondsPerSecond,
_initialT = (max == min) ? 0.0 : ((clampDouble(initialValue, min, max) - min) / (max - min)) * (period.inMicroseconds / Duration.microsecondsPerSecond) {
assert(_periodInSeconds > 0.0);
assert(_initialT >= 0.0);
}
final double min;
final double max;
final bool reverse;
final int? count;
final _DirectionSetter directionSetter;
final double _periodInSeconds;
final double _initialT;
late final double _exitTimeInSeconds = (count! * _periodInSeconds) - _initialT;
@override
double x(double timeInSeconds) {
assert(timeInSeconds >= 0.0);
final double totalTimeInSeconds = timeInSeconds + _initialT;
final double t = (totalTimeInSeconds / _periodInSeconds) % 1.0;
final bool isPlayingReverse = (totalTimeInSeconds ~/ _periodInSeconds).isOdd;
if (reverse && isPlayingReverse) {
directionSetter(_AnimationDirection.reverse);
return ui.lerpDouble(max, min, t)!;
} else {
directionSetter(_AnimationDirection.forward);
return ui.lerpDouble(min, max, t)!;
}
}
@override
double dx(double timeInSeconds) => (max - min) / _periodInSeconds;
@override
bool isDone(double timeInSeconds) {
// if [timeInSeconds] elapsed the [_exitTimeInSeconds] && [count] is not null,
// consider marking the simulation as "DONE"
return count != null && (timeInSeconds >= _exitTimeInSeconds);
}
}