上篇文章我们详细介绍了动画的使用,本文我们将从源码的角度解析动画的底层逻辑。
动画的实现机制
动画的控制是由AnimationController来实现的,这样我们就先从AnimationController来入手研究。
- AnimationController构造函数
<!-- AnimationController -->
AnimationController({
double? value,
this.duration,
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
required TickerProvider vsync,
}) : _direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
void _tick(Duration elapsed) {
// 省略内容...
}
Ticker? _ticker;
AnimationController的构造函数中用
vsync.createTicker(_tick)
方法初始化了属性_ticker
,并设置了动画的初始值。
_ticker
中一个重要的作用是持有了一个回调函数void _tick(Duration elapsed)
,这个_tick
回调函数的作用我们后面会详细解释。
- AnimationController的
forward
方法
<!-- AnimationController -->
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 }) {
stop();
// 省略内容...
return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
}
TickerFuture _startSimulation(Simulation simulation) {
// start
final TickerFuture result = _ticker!.start();
return result;
}
void stop({ bool canceled = true }) {
// stop
_ticker!.stop(canceled: canceled);
}
forward
方法中先调用了_ticker!.stop(canceled: true)
,然后调用了_ticker!.start()
方法。
上面提到的两个方法都是_ticker
的方法,那它们做了什么工作呢?
- Ticker
<!-- Ticker -->
void stop({ bool canceled = false }) {
// 取消调度Tick
unscheduleTick();
}
TickerFuture start() {
// 省略内容...
if (shouldScheduleTick) {
// 1 开始调度Tick
scheduleTick();
}
if (SchedulerBinding.instance!.schedulerPhase.index > SchedulerPhase.idle.index &&
SchedulerBinding.instance!.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index)
// 2 记录动画开始时间
_startTime = SchedulerBinding.instance!.currentFrameTimeStamp;
return _future!;
}
stop
方法调用了unscheduleTick
方法取消Tick调度;start
方法调用了scheduleTick
方法开始Tick调度,并且记录了开始时间。
Tick调度 是什么意思呢?
<!-- Ticker -->
void scheduleTick({ bool rescheduling = false }) {
_animationId = SchedulerBinding.instance!.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}
void unscheduleTick() {
if (scheduled) {
SchedulerBinding.instance!.cancelFrameCallbackWithId(_animationId!);
_animationId = null;
}
}
<!-- SchedulerBinding -->
int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
scheduleFrame();
_nextFrameCallbackId += 1;
_transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
return _nextFrameCallbackId;
}
void cancelFrameCallbackWithId(int id) {
_transientCallbacks.remove(id);
_removedIds.add(id);
}
scheduleTick
是将AnimationController的_tick
加入到了SchedulerBinding的_transientCallbacks
数组中,然后返回了一个对应的回调ID,然后请求刷新界面。unscheduleTick
是根据回调ID将AnimationController的_tick
从SchedulerBinding的_transientCallbacks
数组中移除。
- SchedulerBinding
进入了我们熟悉的SchedulerBinding了,如果你有阅读过前面的文章应该对它的功能和调用逻辑有印象。
void handleBeginFrame(Duration? rawTimeStamp) {
// 1. 时间
_currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
if (rawTimeStamp != null)
_lastRawTimeStamp = rawTimeStamp;
_hasScheduledFrame = false;
try {
final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
_transientCallbacks = <int, _FrameCallbackEntry>{};
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
// 2 回调
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
});
_removedIds.clear();
} finally {
}
}
每次进行界面刷新的时候,Flutter Engine会回调SchedulerBinding的
handleBeginFrame
方法,还传过来了一个时间戳。这时候会一一调用_transientCallbacks
数组中的回调函数,并且将时间戳传进去。
handleBeginFrame
是在刷新界面函数_handleDrawFrame
之前调用的,这里我们就可以知道handleBeginFrame
主要是为了在绘制之前处理和设置好动画的中间值,便于重新绘制。
我们回过来再看看AnimationController的_tick
的逻辑。
_tick
方法
void _tick(Duration elapsed) {
_lastElapsedDuration = elapsed;
// 1. 计算已经动画的时间
final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
// 2. 计算当前时间对应的动画的值
_value = _simulation!.x(elapsedInSeconds).clamp(lowerBound, upperBound);
// 3. 如果动画已经完成进行状态的设置和回调函数的取消
if (_simulation!.isDone(elapsedInSeconds)) {
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
stop(canceled: false);
}
// 4. 通知监听者value发生了变化
notifyListeners();
// 5. 通知监听者status发生了变化
_checkStatusChanged();
}
_tick
方法的逻辑是
- 先根据SchedulerBinding回调过来的时间戳算出当前动画的时间;
- 然后根据时间算出对应的value值;
- 如果动画已经完成进行状态的设置和回调函数的取消;
- 通知监听者value发生了变化
- 通知监听者status发生了变化
至此,动画的实现逻辑就清晰了。
动画的中间值的计算
我们从_tick
方法中看到了动画的中间值是根据时间elapsed
来计算的。AnimationController默认是从0-1,假设动画的时长是2s。如果曲线是速率变化线性的,那么elapsed
是1的时候,AnimationController的value
就变成了0.5。这个很好理解。
动画时间 | 动画值 |
---|---|
0 | 0 |
0.5 | 0.25 |
1.0 | 0.5 |
1.5 | 0.75 |
2 | 1 |
- 设置了CurvedAnimation之后呢?
class CurvedAnimation extends Animation<double> {
double get value {
final Curve? activeCurve = _useForwardCurve ? curve : reverseCurve;
final double t = parent.value;
if (activeCurve == null)
return t;
if (t == 0.0 || t == 1.0) {
return t;
}
return activeCurve.transform(t);
}
}
设置了CurvedAnimation后,动画的值会调用Curve的transform
方法,转换成新的值。
以Curve.decelerate为例:
double transformInternal(double t) {
t = 1.0 - t;
return 1.0 - t * t;
}
动画时间 | 动画值 |
---|---|
0 | 0 |
0.5 | 0.4375 |
1.0 | 0.75 |
1.5 | 0.9375 |
2 | 1 |
- 设置了Tween之后呢?
Tween其实也是调用了transform
方法进行了一次转换,例如:
Tween(begin: 100.0, end: 200.0).animate(_animation);
动画时间 | 动画值 |
---|---|
0 | 100 |
0.5 | 143.75 |
1.0 | 175 |
1.5 | 193.75 |
2 | 200 |
上面的逻辑就是AnimationController,CurvedAnimation和Tween一起共同决定动画中间值的一个逻辑。
AnimatedWidget为什么不需要手动刷新?
abstract class AnimatedWidget extends StatefulWidget {
@override
_AnimatedState createState() => _AnimatedState();
}
class _AnimatedState extends State<AnimatedWidget> {
@override
void initState() {
super.initState();
widget.listenable.addListener(_handleChange);
}
void _handleChange() {
setState(() {
});
}
}
代码中我们看到AnimatedWidget继承自StatefulWidget,_AnimatedState
在initState
加入了一个动画的监听_handleChange
函数。_handleChange
函数中调用了setState
进行刷新。
AnimatedBuilder如何避免了子Widget的重构?
class AnimatedBuilder extends AnimatedWidget {
const AnimatedBuilder({
Key? key,
required Listenable animation,
required this.builder,
this.child,
}) : assert(animation != null),
assert(builder != null),
super(key: key, listenable: animation);
final Widget? child;
@override
Widget build(BuildContext context) {
return builder(context, child);
}
}
代码中我们看到构造函数传入的child和builder
方法传出去的child是同一个,这样就达到了child的复用逻辑。
是否对这种复用方式有印象?Provider其实也有类似的设计。
ImplicitlyAnimatedWidget如何实现自动动画的?
abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
@override
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState();
}
abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> {
@protected
// 1
AnimationController get controller => _controller;
late final AnimationController _controller = AnimationController(
duration: widget.duration,
debugLabel: kDebugMode ? widget.toStringShort() : null,
vsync: this,
);
// 2
Animation<double> get animation => _animation;
late Animation<double> _animation = _createCurve();
@override
void initState() {
super.initState();
_controller.addStatusListener((AnimationStatus status) {
switch (status) {
case AnimationStatus.completed:
if (widget.onEnd != null)
widget.onEnd!();
break;
case AnimationStatus.dismissed:
case AnimationStatus.forward:
case AnimationStatus.reverse:
}
});
_constructTweens();
didUpdateTweens();
}
// 2.
CurvedAnimation _createCurve() {
return CurvedAnimation(parent: _controller, curve: widget.curve);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
bool _shouldAnimateTween(Tween<dynamic> tween, dynamic targetValue) {
return targetValue != (tween.end ?? tween.begin);
}
void _updateTween(Tween<dynamic>? tween, dynamic targetValue) {
if (tween == null)
return;
tween
..begin = tween.evaluate(_animation)
..end = targetValue;
}
void didUpdateWidget(T oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.curve != oldWidget.curve)
_animation = _createCurve();
_controller.duration = widget.duration;
if (_constructTweens()) {
forEachTween((Tween<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
_updateTween(tween, targetValue);
return tween;
});
_controller
..value = 0.0
..forward();
didUpdateTweens();
}
}
bool _constructTweens() {
bool shouldStartAnimation = false;
forEachTween((Tween<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
if (targetValue != null) {
tween ??= constructor(targetValue);
if (_shouldAnimateTween(tween, targetValue))
shouldStartAnimation = true;
} else {
tween = null;
}
return tween;
});
return shouldStartAnimation;
}
@protected
void forEachTween(TweenVisitor<dynamic> visitor);
@protected
void didUpdateTweens() { }
}
ImplicitlyAnimatedWidget的代码也很清晰,使用的也是AnimationController,CurvedAnimation和Tween的组合,只是内部实现了。
当属性发生变化的时候,会调用didUpdateWidget
,然后调用AnimationController的forward
方法开始动画。
总结
本文通过源码的分析,解读了动画的一些相关内容。后面我们将会进入状态管理的使用和分析,欢迎点赞和关注。