在项目中咱们的 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
处于前台页面,反之则说明在后台。