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 frame
为 runApp()
启动过程中,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、刷新界面
在 AnimatedWidget
或 AnimatedBuilder
对应的 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
动画在 SchedulerBinding
和 Ticker
的驱动下,循环往复的不停运转,直到动画完成或被调用停止。
下一篇文章分析 element.rebuild() 的过程。
本文链接: http://w4lle.com/2020/11/13/flutter-ui-animate/
版权声明:本文为 w4lle 原创文章,可以随意转载,但必须在明确位置注明出处!
本文链接: http://w4lle.com/2020/11/13/flutter-ui-animate/