在項目中咱們的 banner第三方控件實現的。markdown
當頁面切換到後臺時 banner
仍自動播放,但咱們用 AnimationController
實現的動畫卻中止了,因而我開始尋找緣由。app
在 flutter
中使用動畫,就會用到 AnimationController
對象,一般咱們是這樣構造它:ide
class _FooState extends State<Foo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, // the SingleTickerProviderStateMixin
duration: widget.duration,
);
}
複製代碼
那麼傳入 vsync:this
以後作了什麼,還有爲何要傳入它?函數
一般使用用 AnimationController.value
的值配合 setState()
來作動畫。oop
AnimationController
構造函數以下:性能
AnimationController({
this.duration,
...
required TickerProvider vsync,
}) : _direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
void _tick(Duration elapsed) {
...
_value = _simulation!.x(elapsedInSeconds).clamp(lowerBound, upperBound);
...
notifyListeners();
_checkStatusChanged();
}
複製代碼
經過 vsync
對象建立了一個 _ticker
,而傳入的 _tick
是一個回調函數。查看源碼它是用於更新 value
,也就是說 AnimationController.value
是在此回調中發生改變。動畫
咱們將視角回調 _ticker = vsync.createTicker(_tick);
來看看 Ticker
。ui
如下源碼有刪減,不想看可直接往下拉this
class Ticker {
TickerFuture? _future;
bool get muted => _muted;
bool _muted = false;
set muted(bool value) {
if (value == muted)
return;
_muted = value;
if (value) {
unscheduleTick();
} else if (shouldScheduleTick) {
scheduleTick();
}
}
bool get isTicking {
if (_future == null)
return false;
if (muted)
return false;
if (SchedulerBinding.instance!.framesEnabled)
return true;
if (SchedulerBinding.instance!.schedulerPhase != SchedulerPhase.idle)
return true; // for example, we might be in a warm-up frame or forced frame
return false;
}
@protected
bool get shouldScheduleTick => !muted && isActive && !scheduled;
void _tick(Duration timeStamp) {
assert(isTicking);
assert(scheduled);
_animationId = null;
_startTime ??= timeStamp;
_onTick(timeStamp - _startTime!);
if (shouldScheduleTick)
scheduleTick(rescheduling: true);
}
@protected
void scheduleTick({ bool rescheduling = false }) {
assert(!scheduled);
assert(shouldScheduleTick);
_animationId = SchedulerBinding.instance!.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}
@protected
void unscheduleTick() {
if (scheduled) {
SchedulerBinding.instance!.cancelFrameCallbackWithId(_animationId!);
_animationId = null;
}
assert(!shouldScheduleTick);
}
@mustCallSuper
void dispose() {
if (_future != null) {
final TickerFuture localFuture = _future!;
_future = null;
assert(!isActive);
unscheduleTick();
localFuture._cancel(this);
}
}
}
複製代碼
Ticker
由 SchedulerBinding
驅動。flutter
每繪製一幀就會回調 Ticker._onTick()
,因此每繪製一幀 AnimationController.value
就會發生變化。lua
接下來看一下 Ticker
其餘成員與方法:
muted
: 設置爲 ture
時鐘仍然能夠運行,但不會調用該回調。isTicking
: 是否能夠在下一幀調用其回調,如設備的屏幕已關閉,則返回false。_tick()
: 時間相關的計算交給 _onTick(),受到 muted
影響。scheduleTick()
: 將 _tick()
回調交給 SchedulerBinding
管理,flutter
每繪製一幀都會調用它。unscheduleTick()
: 取消回調的監聽。@optionalTypeArgs
mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
Ticker? _ticker;
@override
Ticker createTicker(TickerCallback onTick) {
_ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null)
return _ticker!;
}
@override
void dispose() {
super.dispose();
}
@override
void didChangeDependencies() {
if (_ticker != null)
_ticker!.muted = !TickerMode.of(context);
super.didChangeDependencies();
}
}
複製代碼
SingleTickerProviderStateMixin
就是咱們在 State
中 vsync:this
,它作了一個橋樑鏈接了 State
與 Ticker
。
以上源碼重要一點:是在 didChangeDependencies()
中將 muted = !TickerMode.of(context)
初始化一遍。 xxx.of(context)
一看就是 InheritedWidget
中 widget
中的屬性。
最終找到了 Overlay
@override
Widget build(BuildContext context) {
final List<Widget> children = <Widget>[];
bool onstage = true;
int onstageCount = 0;
for (int i = _entries.length - 1; i >= 0; i -= 1) {
final OverlayEntry entry = _entries[i];
if (onstage) {
onstageCount += 1;
children.add(_OverlayEntryWidget(
key: entry._key,
entry: entry,
));
if (entry.opaque)
onstage = false;
} else if (entry.maintainState) {
children.add(_OverlayEntryWidget(
key: entry._key,
entry: entry,
tickerEnabled: false,
));
}
}
return _Theatre(
skipCount: children.length - onstageCount,
children: children.reversed.toList(growable: false),
clipBehavior: widget.clipBehavior,
);
}
複製代碼
根據 OverlayEntry
的 opaque
屬性,判斷哪些 OverlayEntry
在前臺(onstage)的tickerEnabled
爲 true
後臺爲 false
。
Navigator
負責將頁面棧中全部頁面包含的 OverlayEntry
組織成一個 List
,傳遞給 Overlay
,也就是說每個頁面都有一個OverlayEntry
因此解釋了前臺頁面 AnimationController
會調用其回調並播放動畫,後臺頁面AnimationController
即便時間在流逝並不會播放動畫。
解決問題很簡單在 Swiper
的 autoplay
參數中加入 TickerMode.of(context)
這樣切換到下一個頁面Swiper
就不會自動播放了。
Swiper(
...
autoplay: autoplay && TickerMode.of(context),
...
);
複製代碼
至於 Swiper
爲何切換到下一個頁面仍自動播放,有興趣能夠看 banner 源碼實現,這裏不過多講述。
根據以上,能得出以下結論:
- 當傳入
vsync:this
至關於告訴AnimationController
當前Widget
是否處於前臺。Widget
處於後臺的時,動畫時間會流失但不會調用其回調。flutter
這樣作的目的是減小沒必要要的性能消耗。TickerMode.of(context) == true
代表當前Widget
處於前臺頁面,反之則說明在後臺。