8

Flutter UI 渲染浅析(三)Animation 原理

 3 years ago
source link: http://w4lle.com/2020/11/13/flutter-ui-animate/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Flutter UI 渲染浅析(三)Animation 原理
w4lle

Flutter UI 渲染浅析(三)Animation 原理

系列文章的第三篇,本篇文章主要分析下收到 VSync 信号回调后 Dart Framework 触发动画的过程及动画实现原理。

基于 Android 平台,Flutter v1.20.4。

在上一篇文章最后,我们提到做三件事情:

  • 执行 Dart Framework dart:ui 包下的 _beginFrame()
  • 执行 microtasks 任务
  • 执行 Dart Framework dart:ui 包下的 _drawFrame()

dart:ui 包下的两个方法的实现在 hooks.dart

// lib/ui/hooks.dart
// 没有直接引用,注解标记防止被 tree shaking干掉
@pragma('vm:entry-point')
void _beginFrame(int microseconds) {
// 执行 window.onBeginFrame
_invoke1<Duration>(window.onBeginFrame, window._onBeginFrameZone, Duration(microseconds: microseconds));
@pragma('vm:entry-point')
void _drawFrame() {
// 执行 window.onDrawFrame
_invoke(window.onDrawFrame, window._onDrawFrameZone);
void _invoke(void callback()?, Zone zone) {
// 运行 zone 未变,直接调用方法
if (identical(zone, Zone.current)) {
callback();
} else {
zone.runGuarded(callback);

上篇文章中提到,SchedulerBinding 注册了 window.onBeginFrame()window.onDrawFrame()

// lib/src/scheduler/binding.dart
void ensureFrameCallbacksRegistered() {
// 注册 onBeginFrame 、onDrawFrame 回调方法
window.onBeginFrame ??= _handleBeginFrame;
window.onDrawFrame ??= _handleDrawFrame;

本篇文章先分析 SchedulerBinding._handleBeginFrame()

1、_handleBeginFrame()

void _handleBeginFrame(Duration rawTimeStamp) {
if (_warmUpFrame) {
// 如果是启动预热帧,忽略本次调用
_ignoreNextEngineDrawFrame = true;
return;
handleBeginFrame(rawTimeStamp);

如果是启动预热帧,忽略本次调用。

void handleBeginFrame(Duration rawTimeStamp) {
// 开始记录 Frame 过程
Timeline.startSync('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent);
_firstRawTimeStampInEpoch ??= rawTimeStamp;
_currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
if (rawTimeStamp != null)
_lastRawTimeStamp = rawTimeStamp;
assert(() {
// 绘制次数 + 1
_debugFrameNumber += 1;
if (debugPrintBeginFrameBanner || debugPrintEndFrameBanner) {
final StringBuffer frameTimeStampDescription = StringBuffer();
if (rawTimeStamp != null) {
_debugDescribeTimeStamp(_currentFrameTimeStamp, frameTimeStampDescription);
} else {
frameTimeStampDescription.write('(warm-up frame)');
_debugBanner = '▄▄▄▄▄▄▄▄ Frame ${_debugFrameNumber.toString().padRight(7)} ${frameTimeStampDescription.toString().padLeft(18)} ▄▄▄▄▄▄▄▄';
if (debugPrintBeginFrameBanner)
debugPrint(_debugBanner);
return true;
}());
//当前处于 SchedulerPhase.idle 状态
assert(schedulerPhase == SchedulerPhase.idle);
// 更新标志位,接收下次 scheduleFrame 请求
_hasScheduledFrame = false;
// TRANSIENT FRAME CALLBACKS
// 开始记录 Animate 过程
Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);
_schedulerPhase = SchedulerPhase.transientCallbacks;
// 执行 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.midFrameMicrotasks
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
  • TimeLine 开始记录 Frame 过程
  • 如果 debugPrintBeginFrameBanner || debugPrintEndFrameBanner 打印绘制次数及时间
  • 更新标志位 _hasScheduledFrame = false ,接收下次 scheduleFrame 请求
  • 开始记录 Animate 过程
  • 执行 transientCallbacks 瞬时帧回调,并清空回调列表,由 WidgetsBinding.scheduleFrameCallback() 注册
  • 改变状态为 SchedulerPhase.midFrameMicrotasks

打印如下,打印的时间为距离首帧绘制的时间,warm-up framerunApp() 启动过程中,RenderView 触发的预热帧,非 C ++ Engine 回调

I/flutter (14160): ▄▄▄▄▄▄▄▄ Frame 1 (warm-up frame) ▄▄▄▄▄▄▄▄
I/flutter (14160): ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
D/PhoneWindow(14160): setNavigationBarColor: ff000000
I/flutter (14160): ▄▄▄▄▄▄▄▄ Frame 2 0ms ▄▄▄▄▄▄▄▄
I/flutter (14160): ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
I/flutter (14160): [verbose]: Add bucket in set from 0
I/flutter (14160): ▄▄▄▄▄▄▄▄ Frame 3 301.668ms ▄▄▄▄▄▄▄▄
I/flutter (14160): ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
I/flutter (14160): ▄▄▄▄▄▄▄▄ Frame 4 402.225ms ▄▄▄▄▄▄▄▄

beginFrame()主要是执行 transientCallbacks 瞬时帧回调,是由 WidgetsBinding.scheduleFrameCallback 注册,用来处理帧间动画计算和更新的工作,如果没有动画,则 transientCallbacks 为空。

先看下动画注册瞬时帧回调的过程。

2、Flutter Animation 动画原理

2.1、主要类

Flutter 中的动画主要类结构如下图

  • Animation 动画实现类,通过 Ticker 瞬时帧回调改变当前值,提供值回调和状态回调
    • 值回调 addListener(VoidCallback)
    • 状态回调 addStatusListener(AnimationStatusListener)
  • AnimationController ,动画控制器,用于控制 Animation 运行状态,注册 Ticker 回调方法
  • Ticker 用于注册 SchedulerBinding.transientCallbacks 回调,执行 AnimationController 注册的回调
  • TickerProvider & SingleTickerProviderStateMixin,提供默认的 Ticker 对象
  • AnimateWidget & AnimateState or AnimateBuilder 动画 UI 展示,注册 Animation 值回调,自动触发 setState() ,混入SingleTickerProviderStateMixin 提供默认的 Ticker 对象,用于构建 AnimationController 动画控制器
  • Curve 动画曲线可以控制时间的变化情况,类似于 Android 动画的插值器,默认是线性流逝,有以下几种类型,效果参考 官网 Curves-class
    • linear
    • decelerate
    • easeIn
    • easeOut
    • easeInOut
    • fastOutSlowIn
    • bounceIn
    • bounceOut
    • bounceInOut
    • elasticIn
    • elasticOut
    • elasticInOut
  • Tween 动画取值计算出当前时间 t 对应的泛型值,有以下几个子类
    • ReverseTween
    • ColorTween
    • SizeTween
    • RectTween
    • IntTween
    • StepTween
    • ConstantTween
  • AnimationStatus 动画状态机,含有四个状态
    • dismissed:动画暂未开始
    • forward:动画正向运行
    • reverse:动画反向运行
    • complete:动画完成状态
  • Simulation 物理模拟器,位于physics 包下,用于模拟物理运动,提供以下信息
    • 位置信息 x
    • 速度d(x)
    • 是否完成 isDone

2.2、官方例子

在 Flutter 中可以使用 AnimateWidget or AnimateBuilder 构建一个动画,如下

class LogoApp extends StatefulWidget {
_LogoAppState createState() => _LogoAppState();
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller);
controller.forward();
@override
Widget build(BuildContext context) => AnimatedLogo(animation: animation);
@override
void dispose() {
controller.dispose();
super.dispose();
class AnimatedLogo extends AnimatedWidget {
AnimatedLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
margin: EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: FlutterLogo(),

跟下构建流程。

2.3、AnimationController

AnimationController({
double value,
this.duration,// 动画时长
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0, // 默认是 0.0 - 1.0
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
// TickerProvider 对象,一般为混入了 SingleTickerProviderStateMixin 的 State 类对象
@required TickerProvider vsync,
}) : assert(lowerBound != null),
assert(upperBound != null),
assert(upperBound >= lowerBound),
assert(vsync != null),
_direction = _AnimationDirection.forward {
//构建 Ticker 对象
_ticker = vsync.createTicker(_tick);
// 更新状态和动画值
_internalSetValue(value ?? lowerBound);
  • 提供动画时长
  • 提供 TickerProvider 对象,一般为混入了 SingleTickerProviderStateMixin 的 State 类对象
  • 构建 Ticker 对象,注册回调方法 _tick()

当调用 AnimationController.forward() 方式

// lib/src/animation/animation_controller.dart
TickerFuture forward({ double from }) {
// 正向运行
_direction = _AnimationDirection.forward;
if (from != null)
value = from;
return _animateToInternal(upperBound);
TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear }) {
// 默认线性曲线
double scale = 1.0;
Duration simulationDuration = duration;
if (simulationDuration == 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 = target.clamp(lowerBound, upperBound) as double;
notifyListeners();
// 改变状态
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
_checkStatusChanged();
return TickerFuture.complete();
// 构建一个 Simulation 物理模拟器对象,使用线性曲线
return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));

构建一个 Simulation 物理模拟器对象,这里使用线性模拟,内部使用的是 Curves.linear 实现。

// lib/src/animation/animation_controller.dart
TickerFuture _startSimulation(Simulation simulation) {
// 通过模拟器获取值
_value = simulation.x(0.0).clamp(lowerBound, upperBound) as double;
// ticker 开始工作
final TickerFuture result = _ticker.start();
// 更新状态
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.forward :
AnimationStatus.reverse;
_checkStatusChanged();
return result;
// 检查状态变化
void _checkStatusChanged() {
final AnimationStatus newStatus = status;
if (_lastReportedStatus != newStatus) {
_lastReportedStatus = newStatus;
notifyStatusListeners(newStatus);

2.4、Ticker

Ticker 开始工作

// lib/src/schedule/ticker.dart
TickerFuture start() {
_future = TickerFuture._();
if (shouldScheduleTick) {
// 触发 tick 事件
scheduleTick();
if (SchedulerBinding.instance.schedulerPhase.index > SchedulerPhase.idle.index &&
SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index)
// Scheduler状态在 idle 和 postFrameCallbacks 之间,即在绘制状态中
_startTime = SchedulerBinding.instance.currentFrameTimeStamp;
return _future;
@protected
void scheduleTick({ bool rescheduling = false }) {
// 加入到 _transientCallbacks 队列中,等待回调 _tick 方法
_animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);

加入到 SchedulerBinding._transientCallbacks 队列中,等待回调 _tick 方法

// lib/src/scheduler/binding.dart
int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
// 请求绘制
scheduleFrame();
_nextFrameCallbackId += 1;
// 加入 _transientCallbacks 队列
_transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
return _nextFrameCallbackId;
  • 加入 _transientCallbacks 队列

Ticker._tick() 方法

// lib/src/schedule/ticker.dart
void _tick(Duration timeStamp) {
_animationId = null;
_startTime ??= timeStamp;
// 回调 AnimationController 注册的回调方法
_onTick(timeStamp - _startTime);
if (shouldScheduleTick)
// 再次注册 _transientCallbacks 回调
scheduleTick(rescheduling: true);
  • 回调 AnimationController 构建 Ticker 对象时注册的回调方法 _tick()
  • 再次注册 _transientCallbacks 回调,等待触发再次注册… 直到动画完成或停止
// lib/src/animation/animation_controller.dart
void _tick(Duration elapsed) {
_lastElapsedDuration = elapsed;
final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
// 更新动画值
_value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound) as double;
// 更新动画状态
if (_simulation.isDone(elapsedInSeconds)) {
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
stop(canceled: false);
// 通知监听
notifyListeners();
_checkStatusChanged();

2.5、刷新界面

AnimatedWidgetAnimatedBuilder 对应的 State 中,会对 Animation 注册动画监听

// lib/src/widgets/trasitions.dart
void initState() {
super.initState();
widget.listenable.addListener(_handleChange);
void _handleChange() {
// element 标脏,触发绘制
setState(() {
// The listenable's state is our build state, and it changed already.
  • 通过 Animation 的回调方法触发界面刷新
  • Widget 通过 Animation 对象获取对应的值
  • setState() 标脏 element,下一次 VSync 信号到来触发 rebuild()

调用过程:

AnimationController.forward() -> Ticker.start() -> Ticker.scheduleTick() -> Ticker._tick() -> AnimationController._tick() -> Widget.build() -> Ticker.scheduleTick()

不断的触发刷新 -> 更新动画值 -> element.rebuild() -> 触发刷新… 直到动画完成或停止。

Dart Framework 触发 Engine 渲染管线开始绘制流程,回调给 Dart Framework,通过 SchedulerBinding 调度 UI 绘制。

其中的第一个操作是更新 _transientCallbacks 回调,该回调在动画过程中被使用。

Animation 动画在 SchedulerBindingTicker 的驱动下,循环往复的不停运转,直到动画完成或被调用停止。

下一篇文章分析 element.rebuild() 的过程。

本文链接: http://w4lle.com/2020/11/13/flutter-ui-animate/

版权声明:本文为 w4lle 原创文章,可以随意转载,但必须在明确位置注明出处!
本文链接: http://w4lle.com/2020/11/13/flutter-ui-animate/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK