Flutter 从源码扒一扒动画机制

发布于:2024-04-26 ⋅ 阅读:(21) ⋅ 点赞:(0)

一、 关键类

1、AnimationController

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

2、TickerProvider

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

3、SingleTickerProviderStateMixin

 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

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。

5、SchedulerBinding

SchedulerBinding 是 Flutter 框架中一个核心的绑定类,它负责管理 Flutter 应用程序中的任务调度与时间线事件处理。以下是对 SchedulerBinding 类的主要功能和用途的详细说明:

a. 任务调度:SchedulerBinding
负责协调 Flutter 应用中的异步操作、动画帧绘制、事件处理等任务的执行顺序。它实现了基于优先级的任务队列,确保不同类型的任务在适当的时机得到调度。
通过 scheduleFrame() 方法,SchedulerBinding 可以触发下一帧的绘制请求。每当设备屏幕刷新时,此方法会被调用,以驱动 Flutter 的渲染循环。

b.生命周期管理:
SchedulerBinding 监听并响应应用程序的生命周期事件,如启动、暂停、恢复和停止。开发者可以通过监听其提供的 WidgetsBindingObserver 接口中的相关回调方法(如 didChangeAppLifecycleState())来适时调整应用状态或资源。

c.定时器管理:
SchedulerBinding 提供了创建、管理和取消定时器的功能。使用 Timer.run()、Timer.periodic() 等方法可以安排一次性或周期性任务。这些定时器任务会在特定时间点被调度执行,且遵循优先级规则。
手势与事件处理:
SchedulerBinding 整合了对触摸、键盘输入等用户交互事件的处理。它将这些事件转换为 GestureEvent 或 PointerEvent,并通过调度机制传递给相应的 GestureDetector 或 Listener 组件进行处理。

e. 调度监听与回调:
开发者可以注册监听 SchedulerBinding 上的特定事件或阶段,例如:
addPersistentFrameCallback():添加一个持续的帧回调,该回调将在每一帧绘制前被调用。
addPostFrameCallback():添加一个在当前帧绘制完成后执行的回调,常用于需要在视图布局完成后进行的操作。
addTimingsCallback():添加一个时间线回调,用于收集和分析应用性能数据。

二、 深入源码

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

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函数的代码

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类

@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对象,往后看在哪调用的,后面有解释

  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方法

void _checkStatusChanged() {
  final AnimationStatus newStatus = status;
  if (_lastReportedStatus != newStatus) {
    _lastReportedStatus = newStatus;
    notifyStatusListeners(newStatus);
  }
}

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

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

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

_animateToInternal 方法又调用了_startSimulation方法

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方法

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

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。就是在这里调用的

  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类的

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

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

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

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函数,这样整个动画就开始了。

三、最后

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