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
| @pragma('vm:entry-point') void _beginFrame(int microseconds) { _invoke1<Duration>(window.onBeginFrame, window._onBeginFrameZone, Duration(microseconds: microseconds)); } @pragma('vm:entry-point') void _drawFrame() { _invoke(window.onDrawFrame, window._onDrawFrameZone); } void _invoke(void callback()?, Zone zone) { ... if (identical(zone, Zone.current)) { callback(); } else { zone.runGuarded(callback); } }
|
上篇文章中提到,SchedulerBinding
注册了 window.onBeginFrame()
和 window.onDrawFrame()
。
1 2 3 4 5 6
| void ensureFrameCallbacksRegistered() { 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) { Timeline.startSync('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent); _firstRawTimeStampInEpoch ??= rawTimeStamp; _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp); if (rawTimeStamp != null) _lastRawTimeStamp = rawTimeStamp; assert(() { _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; }()); assert(schedulerPhase == SchedulerPhase.idle); _hasScheduledFrame = false; try { Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent); _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; } }
|
- TimeLine 开始记录
Frame
过程
- 如果 debugPrintBeginFrameBanner || debugPrintEndFrameBanner 打印绘制次数及时间
- 更新标志位 _hasScheduledFrame = false ,接收下次 scheduleFrame 请求
- 开始记录 Animate 过程
- 执行
transientCallbacks
瞬时帧回调,并清空回调列表,由 WidgetsBinding.scheduleFrameCallback()
注册
- 改变状态为
SchedulerPhase.midFrameMicrotasks
打印如下,打印的时间为距离首帧绘制的时间,warm-up frame
为 runApp()
启动过程中,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, this.upperBound = 1.0, this.animationBehavior = AnimationBehavior.normal, @required TickerProvider vsync, }) : assert(lowerBound != null), assert(upperBound != null), assert(upperBound >= lowerBound), assert(vsync != null), _direction = _AnimationDirection.forward { _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
| 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) { 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(); } 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
| TickerFuture _startSimulation(Simulation simulation) { ... _value = simulation.x(0.0).clamp(lowerBound, upperBound) as double; 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
| 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; } @protected void scheduleTick({ bool rescheduling = false }) { _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling); }
|
加入到 SchedulerBinding._transientCallbacks
队列中,等待回调 _tick
方法
1 2 3 4 5 6 7 8 9
| int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) { scheduleFrame(); _nextFrameCallbackId += 1; _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling); return _nextFrameCallbackId; }
|
- 请求绘制
- 加入 _transientCallbacks 队列
Ticker._tick()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| void _tick(Duration timeStamp) { _animationId = null; _startTime ??= timeStamp; _onTick(timeStamp - _startTime); if (shouldScheduleTick) scheduleTick(rescheduling: true); }
|
- 回调
AnimationController
构建 Ticker
对象时注册的回调方法 _tick()
- 再次注册
_transientCallbacks
回调,等待触发再次注册… 直到动画完成或停止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
注册动画监听
1 2 3 4 5 6 7 8 9 10 11 12
| void initState() { super.initState(); widget.listenable.addListener(_handleChange); } void _handleChange() { setState(() { }); }
|
- 通过
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
动画在 SchedulerBinding
和 Ticker
的驱动下,循环往复的不停运转,直到动画完成或被调用停止。
下一篇文章分析 element.rebuild() 的过程。
本文链接: http://w4lle.com/2020/11/13/flutter-ui-animate/
版权声明:本文为 w4lle 原创文章,可以随意转载,但必须在明确位置注明出处!
本文链接: http://w4lle.com/2020/11/13/flutter-ui-animate/