Flutter动画框架之SingleTickerProviderStateMixin、TickerProvider、Ticker源码解析(三)

SingleTickerProviderStateMixin

SingleTickerProviderStateMixin 是一个专门为 Flutter 应用提供单一计时器功能的混入类,主要用于需要动画控制的 StatefulWidget。

主要功能

单一计时器

  • 提供唯一的 createTicker 方法创建计时器
  • 严格限制只能创建一个 Ticker 实例,防止资源浪费
  • 适用于只需要单个 AnimationController 的场景

2. 生命周期

  • 激活管理 : activate 方法处理组件重新激活时的计时器状态
  • 资源释放 : dispose 方法确保计时器正确释放,防止内存泄漏

3. 智能状态

  • 自动静音 :根据 TickerMode 自动控制计时器的启用/静音状态
  • 状态同步 : _updateTicker 实时同步计时器与应用状态
  • 通知器管理 : _updateTickerModeNotifier 管理状态变化监听
typescript 复制代码
/// 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 动画系统中计时器的接口。

csharp 复制代码
/// 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

相关推荐
袁煦丞30 分钟前
Photopea云端修图不求人!cpolar内网穿透实验室第641个成功挑战
前端·程序员·远程工作
yk-ddm36 分钟前
JavaScript实现文件下载完整方案
前端·javascript·html
万少1 小时前
04-自然壁纸实战教程-搭建基本工程
前端·harmonyos·客户端
karl_hg1 小时前
Element Plus 自定义(动态)表单组件
前端·vue.js·element
南岸月明1 小时前
从焦虑到专注:副业半年后我才明白的3件事
前端
晓13131 小时前
JavaScript加强篇——第八章 高效渲染与正则表达式
开发语言·前端·javascript
南囝coding1 小时前
做付费社群,强烈建议大家做这件事!
前端·后端
我是若尘1 小时前
Axios 如何跨域携带 Cookie?
前端
子林super2 小时前
主从数据全量迁移到分片集群测试
前端
Spider_Man2 小时前
🚀 TypeScript从入门到React实战:前端工程师的类型安全之旅
前端·typescript