Flutter 扒一扒动画机制

一、 关键类

1、AnimationController

AnimationController 类是Flutter中动画的基础类,它提供了一个动画控制器,用于控制动画的播放、暂停、停止等操作。

2、TickerProvider

TickerProvider 是一个接口,它定义了创建 Ticker 对象的方法。Ticker 对象用于控制动画的播放速度和时间。

3、SingleTickerProviderStateMixin

dart 复制代码
 mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider

SingleTickerProviderStateMixin 是一个 mixin 类,它实现了 TickerProvider 接口,并提供了创建 Ticker 对象的方法。

单一Ticker提供者: 当你的 State 类只需要一个 AnimationController(即一个 Ticker)时,应使用 SingleTickerProviderStateMixin。 此 Mixin 保证在该 State 实例的生命期内仅创建和管理一个 Ticker。 如果尝试在此 State 中创建多个 AnimationController,会抛出异常,提示"multiple tickers were created"。 适用于包含单个简单动画的场景,如页面过渡动画、单个控件的旋转、淡入淡出等。 当你确信一个 State 中不会有多个并发动画时,使用此 Mixin 可以避免不必要的资源消耗。

4、TickerProviderStateMixin

dart 复制代码
mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider 

TickerProviderStateMixin 是一个 mixin 类,它实现了 TickerProvider 接口,并提供了创建 Ticker 对象的方法。 多Ticker提供者: 当你的 State 类需要管理多个独立的 AnimationController(每个对应一个 Ticker)时,应使用 TickerProviderStateMixin。 这个 Mixin 允许你在同一 State 实例中创建任意数量的 AnimationController,为每个动画控制器分配单独的 Ticker。 适用于复杂场景,比如一个页面中有多个独立动画需要同时运行或按需控制 适用于需要管理多个并发动画的复杂场景,如多元素协同动画、不同触发条件下运行的不同动画序列等。 当页面或者组件内包含多个相互独立或有依赖关系的动画控制器时,使用此 Mixin 可确保所有动画都能正确创建和管理各自的 Ticker。

二、 深入源码

初始化一个AnimationController的时候 注册了一个回调函数,并创建了一个Ticker对象

ini 复制代码
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 {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value ?? lowerBound);
}

这个_tick函数的代码

ini 复制代码
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();
}

Ticker类

scss 复制代码
@override
Ticker createTicker(TickerCallback onTick) {
  assert(() {
    if (_ticker == null) {
      return true;
    }
  _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by ${describeIdentity(this)}' : null);
  _updateTickerModeNotifier();
  _updateTicker(); // Sets _ticker.mute correctly.
  return _ticker!;
}

_tick函数被封装成了Ticker对象,往后看在哪调用的,后面有解释

php 复制代码
  void notifyListeners() {
    final List<VoidCallback> localListeners = _listeners.toList(growable: false);
    for (final VoidCallback listener in localListeners) {  
    
      try {
        if (_listeners.contains(listener)) {
          listener();
        }
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'animation library',
          context: ErrorDescription('while notifying listeners for $runtimeType'),
          informationCollector: collector,
        ));
      }
    }
  }
}

notifyListeners()这个就是通知监听,就是我们调用的 addListener的方法

我们看看_checkStatusChanged方法

ini 复制代码
void _checkStatusChanged() {
  final AnimationStatus newStatus = status;
  if (_lastReportedStatus != newStatus) {
    _lastReportedStatus = newStatus;
    notifyStatusListeners(newStatus);
  }
}

这个方法就是通知状态的监听,就是我们调用的addStatusListener的方法。

而真正开启动画的,就是调用controller.forward()

javascript 复制代码
TickerFuture forward({ double? from }) {
    _direction = _AnimationDirection.forward;
    if (from != null) {
    value = from;
    }
    return _animateToInternal(upperBound);
}

_animateToInternal 方法又调用了_startSimulation方法

ini 复制代码
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;
}

从上面的代码我们可以看到调用了,我们刚才在AnimationController初始化创建的Ticker对象的start方法

ini 复制代码
TickerFuture start() {
    _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!;
}

我们看看scheduleTick();

ini 复制代码
void scheduleTick({ bool rescheduling = false }) {
    _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}

该函数用于安排一个帧回调函数_tick,以便在下一帧被执行。rescheduling参数决定是否允许重新安排回调。通过SchedulerBinding.instance.scheduleFrameCallback方法来实现。

这个_tick函数是Ticker类的方法,不是AnimationController的方法,下面的_onTick这个方法才是调用AnimationController的_tick函数,这个_tick函数就是我们上面传入Ticker对象里的onTick。就是在这里调用的

scss 复制代码
  void _tick(Duration timeStamp) {
    _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);
    }
}

SchedulerBinding类的

ini 复制代码
int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
    scheduleFrame();
    _nextFrameCallbackId += 1;
    _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
    return _nextFrameCallbackId;
}

scheduleFrame()用来调度一个绘制帧

ini 复制代码
void scheduleFrame() {
        if (_hasScheduledFrame || !framesEnabled) {
              return;
            }
            ensureFrameCallbacksRegistered();
            platformDispatcher.scheduleFrame();
            _hasScheduledFrame = true;
        }

    void ensureFrameCallbacksRegistered() {
        platformDispatcher.onBeginFrame ??= _handleBeginFrame;
        platformDispatcher.onDrawFrame ??= _handleDrawFrame;
    }



void _handleBeginFrame(Duration rawTimeStamp) {
  if (_warmUpFrame) {
    _rescheduleAfterWarmUpFrame = true;
    return;
  }
  handleBeginFrame(rawTimeStamp);
}

_transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling), 这行代码是将我们的_tick函数封装成一个_FrameCallbackEntry对象,然后存到了_transientCallbacks 这个Map对象里。 从这里我们就可以猜到,应该就是等到下一个垂直同步信号的到来,然后再遍历调用_transientCallbacks的对象里的回调方法

我们找到了遍历_transientCallbacks的方法是handleBeginFrame

ini 复制代码
void handleBeginFrame(Duration? rawTimeStamp) {
    _frameTimelineTask?.start('Frame');
    _firstRawTimeStampInEpoch ??= rawTimeStamp;
    _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
    if (rawTimeStamp != null) {
    _lastRawTimeStamp = rawTimeStamp;
    }
_hasScheduledFrame = false;
try {
  // TRANSIENT FRAME CALLBACKS
  _frameTimelineTask?.start('Animate');
  _schedulerPhase = SchedulerPhase.transientCallbacks;
  final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
  _transientCallbacks = <int, _FrameCallbackEntry>{};
  callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
    if (!_removedIds.contains(id)) {
      _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
    }
  });
  _removedIds.clear();
} finally {
  _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}

}

从上面代码可以看出,_handleBeginFrame函数注册了一下,然后等垂直同步信号的到来. platformDispatcher.scheduleFrame()这个就是开始请求绘制帧的。

当垂直同步信号到来后,就开始回调handleBeginFrame方法,接着就开始遍历调用_transientCallbacks里的对象,也就会回调上面说的_tick函数,这样整个动画就开始了。

三、最后

其实还有很多源码细节,我就不去抠了,先从整体上把握整个流程我感觉就可以了。

相关推荐
jcLee9511 小时前
Flutter/Dart:使用日志模块Logger Easier
flutter·log4j·dart·logger
tmacfrank12 小时前
Flutter 异步编程简述
flutter
tmacfrank12 小时前
Flutter 基础知识总结
flutter
叫我菜菜就好13 小时前
【Flutter_Web】Flutter编译Web第三篇(网络请求篇):dio如何改造方法,变成web之后数据如何处理
前端·网络·flutter
AiFlutter17 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
m0_748247802 天前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
迷雾漫步者2 天前
Flutter组件————PageView
flutter·跨平台·dart
迷雾漫步者2 天前
Flutter组件————FloatingActionButton
前端·flutter·dart
coder_pig2 天前
📝小记:Ubuntu 部署 Jenkins 打包 Flutter APK
flutter·ubuntu·jenkins
捡芝麻丢西瓜2 天前
flutter自学笔记5- dart 编码规范
flutter·dart