Flutter 从源码扒一扒动画机制

一、 关键类

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

三、最后

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

相关推荐
奋斗的小青年!!4 小时前
Flutter浮动按钮在OpenHarmony平台的实践经验
flutter·harmonyos·鸿蒙
程序员老刘7 小时前
一杯奶茶钱,PicGo + 阿里云 OSS 搭建永久稳定的个人图床
flutter·markdown
奋斗的小青年!!10 小时前
OpenHarmony Flutter 拖拽排序组件性能优化与跨平台适配指南
flutter·harmonyos·鸿蒙
小雨下雨的雨11 小时前
Flutter 框架跨平台鸿蒙开发 —— Stack 控件之三维层叠艺术
flutter·华为·harmonyos
行者9612 小时前
OpenHarmony平台Flutter手风琴菜单组件的跨平台适配实践
flutter·harmonyos·鸿蒙
小雨下雨的雨14 小时前
Flutter 框架跨平台鸿蒙开发 —— Flex 控件之响应式弹性布局
flutter·ui·华为·harmonyos·鸿蒙系统
cn_mengbei14 小时前
Flutter for OpenHarmony 实战:CheckboxListTile 复选框列表项详解
flutter
cn_mengbei14 小时前
Flutter for OpenHarmony 实战:Switch 开关按钮详解
flutter
奋斗的小青年!!14 小时前
OpenHarmony Flutter实战:打造高性能订单确认流程步骤条
flutter·harmonyos·鸿蒙
Coder_Boy_14 小时前
Flutter基础介绍-跨平台移动应用开发框架
spring boot·flutter