目录
- 研究背景
- AnimationController
- Ticker
- SingleTickerProviderStateMixin
- Overlay
- 解决问题
- 总结
研究背景:
在项目中我们的 banner第三方控件实现的。
当页面切换到后台时 banner 仍自动播放,但我们用 AnimationController 实现的动画却停止了,于是我开始寻找原因。
在 flutter 中使用动画,就会用到 AnimationController 对象,通常我们是这样构造它:
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
通常使用用 AnimationController.value 的值配合 setState() 来做动画。
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 。
Ticker
以下源码有删减,不想看可直接往下拉
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 就会发生变化。
接下来看一下 Ticker 其他成员与方法:
muted: 设置为ture时钟仍然可以运行,但不会调用该回调。isTicking: 是否可以在下一帧调用其回调,如设备的屏幕已关闭,则返回false。_tick(): 时间相关的计算交给 _onTick(),受到muted影响。scheduleTick(): 将_tick()回调交给SchedulerBinding管理,flutter每绘制一帧都会调用它。unscheduleTick(): 取消回调的监听。
SingleTickerProviderStateMixin
@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
最终找到了 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处于前台页面,反之则说明在后台。























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)