一、概述
動畫效果對於系統的用戶體驗非常重要,好的動畫能讓用戶感覺界面更加順暢,提升用戶體驗。
1.1 動畫類型
Flutter動畫大的分類來說主要分為兩大類:
補間動畫:給定初值與終值,系統自動補齊中間幀的動畫物理動畫:遵循物理學定律的動畫,實現了彈簧、阻尼、重力三種物理效果在應用使用過程中常見動畫模式:
動畫列表或者網格:例如元素的添加或者刪除操作;轉場動畫Shared element transition:例如從當前頁面打開另一頁面的過渡動畫;交錯動畫Staggered animations:比如部分或者完全交錯的動畫。1.2 類圖
核心類:
Animation對象是整個動畫中非常核心的一個類;AnimationController用於管理Animation;CurvedAnimation過程是非線性曲線;Tween補間動畫Listeners和StatusListeners用於監聽動畫狀態改變。AnimationStatus是枚舉類型,有4個值;
取值解釋dismissed動畫在開始時停止forward動畫從頭到尾繪製reverse動畫反向繪製,從尾到頭completed動畫在結束時停止
1.3 動畫實例
<code> //[見小節2.1]
AnimationController animationController = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
Animation animation = Tween(begin: 0.0,end: 10.0).animate(animationController);
animationController.addListener(() {
setState(() {});
});
//[見小節2.2]
animationController.forward();
/<code>
該過程說明:
AnimationController作為Animation子類,在屏幕刷新時生成一系列值,默認情況下從0到1區間的取值。Tween的animate()方法來自於父類Animatable,該方法返回的對象類型為_AnimatedEvaluation,而該對象最核心的工作就是通過value來調用Tween的transform();調用鏈:
<code>AnimationController.forward
AnimationController.\\_animateToInternal
AnimationController.\\_startSimulation
Ticker.start()
Ticker.scheduleTick()
SchedulerBinding.scheduleFrameCallback()
SchedulerBinding.scheduleFrame()
...
Ticker.\\_tick
AnimationController.\\_tick
Ticker.scheduleTick
/<code>
二、原理分析
2.1 AnimationController初始化
[-> lib/src/animation/animation_controller.dart]
<code>AnimationController({
double value,
this.duration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
@required TickerProvider vsync,
}) : _direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick); //[見小節2.1.1]
_internalSetValue(value ?? lowerBound); //[見小節2.1.3]
}
/<code>
該方法說明:
AnimationController初始化過程,一般都設置duration和vsync初值;upperBound(上邊界值)和lowerBound(下邊界值)都不能為空,且upperBound必須大於等於lowerBound;創建默認的動畫方向為向前(_AnimationDirection.forward);調用類型為TickerProvider的vsync對象的createTicker()方法來創建Ticker對象;TickerProvider作為抽象類,主要的子類有SingleTickerProviderStateMixin和TickerProviderStateMixin,這兩個類的區別就是是否支持創建多個TickerProvider,這裡SingleTickerProviderStateMixin為例展開。
2.1.1 createTicker
[-> lib/src/widgets/ticker_provider.dart]
<code>mixin SingleTickerProviderStateMixin on State implements TickerProvider {
Ticker _ticker;
Ticker createTicker(TickerCallback onTick) {
//[見小節2.1.2]
_ticker = Ticker(onTick, debugLabel: 'created by $this');
return _ticker;
}
/<code>
2.1.2 Ticker初始化
[-> lib/src/scheduler/ticker.dart]
<code>class Ticker {
Ticker(this._onTick, { this.debugLabel }) {
}
final TickerCallback _onTick;
}
/<code>
將AnimationControllerd對象中的_tick()方法,賦值給Ticker對象的_onTick成員變量,再來看看該_tick方法。
2.1.3 _internalSetValue
[-> lib/src/animation/animation_controller.dart ::AnimationController]
<code>void _internalSetValue(double newValue) {
_value = newValue.clamp(lowerBound, upperBound);
if (_value == lowerBound) {
_status = AnimationStatus.dismissed;
} else if (_value == upperBound) {
_status = AnimationStatus.completed;
} else {
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.forward :
AnimationStatus.reverse;
}
}
/<code>
根據當前的value值來初始化動畫狀態_status
2.2 forward
[-> lib/src/animation/animation_controller.dart ::AnimationController]
<code>TickerFuture forward({ double from }) {
//默認採用向前的動畫方向
_direction = _AnimationDirection.forward;
if (from != null)
value = from;
return _animateToInternal(upperBound); //[見小節2.3]
}
/<code>
_AnimationDirection是枚舉類型,有forward(向前)和reverse(向後)兩個值,也就是說該方法的功能是指從from開始向前滑動,
2.3 _animateToInternal
[-> lib/src/animation/animation_controller.dart ::AnimationController]
<code>TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear }) {
double scale = 1.0;
if (SemanticsBinding.instance.disableAnimations) {
switch (animationBehavior) {
case AnimationBehavior.normal:
scale = 0.05;
break;
case AnimationBehavior.preserve:
break;
}
}
Duration simulationDuration = duration;
if (simulationDuration == null) {
final double range = upperBound - lowerBound;
final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
//根據剩餘動畫的百分比來評估仿真動畫剩餘時長
simulationDuration = this.duration * remainingFraction;
} else if (target == value) {
//已到達動畫終點,不再執行動畫
simulationDuration = Duration.zero;
}
//停止老的動畫[見小節2.3.1]
stop();
if (simulationDuration == Duration.zero) {
if (value != target) {
_value = target.clamp(lowerBound, upperBound);
notifyListeners();
}
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
_checkStatusChanged();
//當動畫執行時間已到,則直接結束
return TickerFuture.complete();
}
//[見小節2.4]
return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
}
/<code>
默認採用的是線性動畫曲線Curves.linear。
2.3.1 AnimationController.stop
<code>void stop({ bool canceled = true }) {
_simulation = null;
_lastElapsedDuration = null;
//[見小節2.3.2]
_ticker.stop(canceled: canceled);
}
/<code>
2.3.2 Ticker.stop
[-> lib/src/scheduler/ticker.dart]
<code>void stop({ bool canceled = false }) {
if (!isActive) //已經不活躍,則直接返回
return;
final TickerFuture localFuture = _future;
_future = null;
_startTime = null;
//[見小節2.3.3]
unscheduleTick();
if (canceled) {
localFuture._cancel(this);
} else {
localFuture._complete();
}
}
/<code>
2.3.3 Ticker.unscheduleTick
[-> lib/src/scheduler/ticker.dart]
<code>void unscheduleTick() {
if (scheduled) {
SchedulerBinding.instance.cancelFrameCallbackWithId(_animationId);
_animationId = null;
}
}
/<code>
2.3.4 _InterpolationSimulation初始化
[-> lib/src/animation/animation_controller.dart ::_InterpolationSimulation]
<code>class _InterpolationSimulation extends Simulation {
_InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale)
: _durationInSeconds = (duration.inMicroseconds * scale) / Duration.microsecondsPerSecond;
final double _durationInSeconds;
final double _begin;
final double _end;
final Curve _curve;
}
/<code>
該方法創建插值模擬器對象,並初始化起點、終點、動畫曲線以及時長。這裡用的Curve是線性模型,也就是說採用的是勻速運動。
2.4 _startSimulation
[-> lib/src/animation/animation_controller.dart]
<code>TickerFuture _startSimulation(Simulation simulation) {
_simulation = simulation;
_lastElapsedDuration = Duration.zero;
_value = simulation.x(0.0).clamp(lowerBound, upperBound);
//[見小節2.5]
final TickerFuture result = _ticker.start();
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.forward :
AnimationStatus.reverse;
//[見小節2.4.1]
_checkStatusChanged();
return result;
}
/<code>
2.4.1 _checkStatusChanged
[-> lib/src/animation/animation_controller.dart]
<code>void _checkStatusChanged() {
final AnimationStatus newStatus = status;
if (_lastReportedStatus != newStatus) {
_lastReportedStatus = newStatus;
notifyStatusListeners(newStatus); //通知狀態改變
}
}
/<code>
這裡會回調_statusListeners中的所有狀態監聽器,這裡的狀態就是指AnimationStatus的dismissed、forward、reverse以及completed。
2.5 Ticker.start
[-> lib/src/scheduler/ticker.dart]
<code>TickerFuture start() {
_future = TickerFuture._();
if (shouldScheduleTick) {
scheduleTick(); //[見小節2.6]
}
if (SchedulerBinding.instance.schedulerPhase.index > SchedulerPhase.idle.index &&
SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index)
_startTime = SchedulerBinding.instance.currentFrameTimeStamp;
return _future;
}
/<code>
此處的shouldScheduleTick等於!muted && isActive && !scheduled,也就是沒有調度過的活躍狀態才會調用Tick。
2.6 Ticker.scheduleTick
[-> lib/src/scheduler/ticker.dart]
<code>void scheduleTick({ bool rescheduling = false }) {
//[見小節2.7]
_animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}
/<code>
此處的_tick會在下一次vysnc觸發時回調執行,見小節2.10。
2.7 scheduleFrameCallback
[-> lib/src/scheduler/binding.dart]
<code>int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
//[見小節2.8]
scheduleFrame();
_nextFrameCallbackId += 1;
_transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
return _nextFrameCallbackId;
}
/<code>
將前面傳遞過來的Ticker._tick()方法保存在_FrameCallbackEntry的callback中,然後將_FrameCallbackEntry記錄在Map類型的_transientCallbacks,
2.8 scheduleFrame
[-> lib/src/scheduler/binding.dart]
<code>void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled)
return;
ui.window.scheduleFrame();
_hasScheduledFrame = true;
}
/<code>
從文章Flutter之setState更新機制,可知此處調用的ui.window.scheduleFrame(),會註冊vsync監聽。噹噹下一次vsync信號的到來時會執行handleBeginFrame()。
2.9 handleBeginFrame
[-> lib/src/scheduler/binding.dart:: SchedulerBinding]
<code>void handleBeginFrame(Duration rawTimeStamp) {
Timeline.startSync('Frame', arguments: timelineWhitelistArguments);
_firstRawTimeStampInEpoch ??= rawTimeStamp;
_currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
if (rawTimeStamp != null)
_lastRawTimeStamp = rawTimeStamp;
...
//此時階段等於SchedulerPhase.idle;
_hasScheduledFrame = false;
try {
Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
_schedulerPhase = SchedulerPhase.transientCallbacks;
//執行動畫的回調方法
final Map callbacks = _transientCallbacks;
_transientCallbacks = {};
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
});
_removedIds.clear();
} finally {
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}
}
/<code>
該方法主要功能是遍歷_transientCallbacks,從前面小節[2.7],可知該過程會執行Ticker._tick()方法。
2.10 Ticker._tick
[-> lib/src/scheduler/ticker.dart]
<code>void _tick(Duration timeStamp) {
_animationId = null;
_startTime ??= timeStamp;
//[見小節2.11]
_onTick(timeStamp - _startTime);
//根據活躍狀態來決定是否再次調度
if (shouldScheduleTick)
scheduleTick(rescheduling: true);
}
/<code>
該方法主要功能:
小節[2.1.2]的Ticker初始化中,可知此處_onTick便是AnimationController的_tick()方法;小節[2.5]已介紹當仍處於活躍狀態,則會再次調度,回到小節[2.6]的scheduleTick(),從而形成動畫的連續繪製過程。2.11 AnimationController._tick
[-> lib/src/animation/animation_controller.dart]
<code>void _tick(Duration elapsed) {
_lastElapsedDuration = elapsed;
//獲取已過去的時長
final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
_value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
if (_simulation.isDone(elapsedInSeconds)) {
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
stop(canceled: false); //當動畫已完成,則停止
}
notifyListeners(); //通知監聽器[見小節2.11.1]
_checkStatusChanged(); //通知狀態監聽器[見小節2.11.2]
}
/<code>
2.11.1 notifyListeners
[-> lib/src/animation/listener_helpers.dart ::AnimationLocalListenersMixin]
<code>void notifyListeners() {
final List<voidcallback> localListeners = List<voidcallback>.from(_listeners);
for (VoidCallback listener in localListeners) {
try {
if (_listeners.contains(listener))
listener();
} catch (exception, stack) {
...
}
}
}
/<voidcallback>/<voidcallback>/<code>
AnimationLocalListenersMixin的addListener()會向_listeners中添加監聽器
2.11.2 _checkStatusChanged
[-> lib/src/animation/listener_helpers.dart ::AnimationLocalStatusListenersMixin]
<code>void notifyStatusListeners(AnimationStatus status) {
final List<animationstatuslistener> localListeners = List<animationstatuslistener>.from(_statusListeners);
for (AnimationStatusListener listener in localListeners) {
try {
if (_statusListeners.contains(listener))
listener(status);
} catch (exception, stack) {
...
}
}
}
/<animationstatuslistener>/<animationstatuslistener>/<code>
從前面的小節[2.4.1]可知,當狀態改變時會調用notifyStatusListeners方法。AnimationLocalStatusListenersMixin的addStatusListener()會向_statusListeners添加狀態監聽器。