w4lle's Notes

人生如逆旅,我亦是行人。

w4lle's avatar 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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()

1
2
3
4
5
6
// lib/src/scheduler/binding.dart
void ensureFrameCallbacksRegistered() {
// 注册 onBeginFrame 、onDrawFrame 回调方法
window.onBeginFrame ??= _handleBeginFrame;
window.onDrawFrame ??= _handleDrawFrame;
}

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

1、_handleBeginFrame()

1
2
3
4
5
6
7
8
void _handleBeginFrame(Duration rawTimeStamp) {
if (_warmUpFrame) {
// 如果是启动预热帧,忽略本次调用
_ignoreNextEngineDrawFrame = true;
return;
}
handleBeginFrame(rawTimeStamp);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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;
try {
// 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 回调

1
2
3
4
5
6
7
8
9
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
    • ease
    • 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 构建一个动画,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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() 方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 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 实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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 开始工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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 方法

1
2
3
4
5
6
7
8
9
// 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() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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 回调,等待触发再次注册… 直到动画完成或停止
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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 注册动画监听

1
2
3
4
5
6
7
8
9
10
11
12
// 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() -> 触发刷新… 直到动画完成或停止。

3、总结

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/