Animation是动画的关键类,本节主要对flutter动画原理的分析,先看一个例子
class _MyAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
Animation<double>? animation;
AnimationController? _controller;
initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = Tween(begin: 0.0, end: 300.0).animate(_controller!)
..addListener(() {
setState(() {
// Animation对象内的值改变了
});
});
_controller?.forward();
}
Widget build(BuildContext context) {
return Container(
color: Colors.red,
width: animation!.value,
height: animation!.value,
);
}
1.这个源码是将一个container从0x0放大到300x300 大小的动画
2.这里创建了animation和AnimationController的变量,这是动画能够运行的关键
3.animation变量添加了addListener监听,即在动画运行的时候不断的刷新widget进行重绘
4.animation通过监听器注册将animation的value值传递给container并刷新绘制
5.AnimationController是驱动者,Tween则提供了补间动画的插值模型。animation作为最终的调用出口
一、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,
}) : assert(upperBound >= lowerBound),
_direction = _AnimationDirection.forward {
if (kFlutterMemoryAllocationsEnabled) {
_maybeDispatchObjectCreation();
}
//_tick是函数,监听vsync的变化就会调用
_ticker = vsync.createTicker(_tick);
//初始化动画值,这里的lowerBound和upperBound是动画参数的最小值和最大值
_internalSetValue(value ?? lowerBound);
}
//更新当前动画的状态
void _internalSetValue(double newValue) {
//构造AnimationController的时候初始化_value值,同时初始化动画状态_status
_value = clampDouble(newValue, lowerBound, upperBound);
if (_value == lowerBound) {
//动画未开始
_status = AnimationStatus.dismissed;
} else if (_value == upperBound) {
//动画已经结束
_status = AnimationStatus.completed;
} else {
//动画进行中
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.forward :
AnimationStatus.reverse;
}
}
1.AnimationController在创建初期先初始化_ticker的vsync监听函数,同时传给widget的state持有一份,同时初始化AnimationController内部的_value和_status的初始值
2.初始动画值从lowerBound(0.0)到upperBound(1.0)之间。动画值有四种状态,开始状态dismissed,进行状态forward,结束状态completed和反向状态reverse
二、Tween插值器
接下来看看Tween的源码(动画插值器)
class Tween<T extends Object?> extends Animatable<T> {
/// Creates a tween.
Tween({
this.begin,
this.end,
});
//这里就是开始了线性插值的过程
@override
T lerp(double t) {
//这里开始做运算得到最终值
return (begin as dynamic) + ((end as dynamic) - (begin as dynamic)) * t as T;
}
@override
T transform(double t) {
if (t == 0.0) {
//这里的T实际上就是Tween<Double>内传的泛型,如Double。
return begin as T;
}
if (t == 1.0) {
return end as T;
}
return lerp(t);
}
}
abstract class Animatable<T> {
T transform(double t);
//这里就是_AnimatedEvaluation的value方法,相当于transform(animationController.value)
T evaluate(Animation<double> animation) => transform(animation.value);
Animation<T> animate(Animation<double> parent) {
return _AnimatedEvaluation<T>(parent, this);
}
Animatable<T> chain(Animatable<double> parent) {
return _ChainedEvaluation<T>(parent, this);
}
}
class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
//这里的parent就是AnimationController,_evaluatable就是Tween
_AnimatedEvaluation(this.parent, this._evaluatable);
@override
final Animation<double> parent;
final Animatable<T> _evaluatable;
//这里相当于Tween.evaluate(AnimationController),这样写方便看。
@override
T get value => _evaluatable.evaluate(parent);
}
知识点:所谓的插值器就是用于控制动画的速度的。我们知道,像补间动画,只需要定义动画的第一帧和最后一帧,系统就能将中间帧都生成出来,而生成这个中间帧的过程就叫插值,上图中lerp()就是进行了插值运算,
1.AnimationController 会在 lowerBound
和 upperBound
之间进行过渡取值,但如果我们需要 0.0-1.0
区间之外的值或者其他类型的值来设置动画,就需要用到插值器(Tween)了,它可以在 begin
和 end
之间进行插值补间帧。
2.上面的逻辑展示了Tween的插值过程,插值的参数是AnimationController.value的值。这个值在每一帧都会进行更新,Tween会根据这个值完成线性插值的过程。Tween的值最终会存入到_AnimatedEvaluation中,即Animation
Flutter 提供了一系列 Tween 的子类供我们使用(插值器):
- ReverseTween,反向插值,其参数是一个 Tween 对象,会反向评估它的值。
- ColorTween,颜色插值器,可传入如Color.blue等值
- SizeTween,Size 类型的插值器,可用于涉及到 SizedBox 之类的尺寸
- RectTween,Rect 类型的插值器
- IntTween,Int 类型的插值器
- StepTween,步数插值器
- ConstantTween,常量值插值器,返回的总是begin的值,永远不变。
3.每个插值器都有自己实现的lerp()插值逻辑。其中IntTween和StepTween都是传int,唯一的区别是int直接使用的是整数值,而IntTween最终计算的值会进行四舍五入,而StepTween会返回小于或等于传入值的最大整数
三、动画驱动机制(forward)
当我们了解了动画的值是如何使用的,现在我们看看动画是如何驱动的
AnimationController.forwad源码
//AnimationController
//驱动开始的地方
TickerFuture forward({ double? from }) {
//初始化动画运行方向,forward是正向,reverse反向
_direction = _AnimationDirection.forward;
if (from != null) {
//执行forward的同时,更新动画的值。
value = from;
}
return _animateToInternal(upperBound);
}
TickerFuture _animateToInternal(double target, { Duration? duration, Curve curve = Curves.linear }) {
double scale = 1.0;
if (SemanticsBinding.instance.disableAnimations) {
switch (animationBehavior) {
case AnimationBehavior.normal:
// Since the framework cannot handle zero duration animations, we run it at 5% of the normal
// duration to limit most animations to a single frame.
// Ideally, the framework would be able to handle zero duration animations, however, the common
// pattern of an eternally repeating animation might cause an endless loop if it weren't delayed
// for at least one frame.
scale = 0.05;
case AnimationBehavior.preserve:
break;
}
}
Duration? simulationDuration = duration;
if (simulationDuration == null) {
final double range = upperBound - lowerBound;
//target就是传入的upperBound,最大值,如1.0,_value就是动画进度值
//这里求得动画运行的百分比
final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
final Duration directionDuration =
(_direction == _AnimationDirection.reverse && reverseDuration != null)
? reverseDuration!
: this.duration!;
//从上面获得的百分比进行时长的计算
simulationDuration = directionDuration * remainingFraction;
} else if (target == value) {
// Already at target, don't animate.
simulationDuration = Duration.zero;
}
stop();
if (simulationDuration == Duration.zero) {
if (value != target) {
_value = clampDouble(target, lowerBound, upperBound);
notifyListeners();
}
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
_checkStatusChanged();
return TickerFuture.complete();
}
return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
}
//_InterpolationSimulation是Simulation的实现类,是个模拟插值器
class _InterpolationSimulation extends Simulation {
.....
}
1.第二步,进入到_animateToInternal进行动画时长(simulationDuration)的计算及开始动画逻辑
- simulationDuration时长的计算,根据剩余值与总值的比例进行计算
- simulationDuration为0的情况进行处理,动画已经结束,对_value字段进行赋值并通知更新
- 执行_startSimulation开始动画的逻辑
2.先进行百分比计算(target - _value).abs() / range , 再进行时长计算simulationDuration = directionDuration * remainingFraction;其中directionDuration就是构造AnimationController的时候传入的duration动画时长。
3.动画结束时,会进行AnimationStatus状态的更新,注意,这里会根据不同的方向给于状态结束的不同值,即forwad则给定AnimationStatus.completed,反向reverse则给定AnimationStatus.dismissed
动画开始执行_startSimulation
//AnimationController..
TickerFuture _startSimulation(Simulation simulation) {
_simulation = simulation;
//截止到上一帧动画已消耗的时间参数
_lastElapsedDuration = Duration.zero;
//确保值在lowerBound和upperBound范围内取值
_value = clampDouble(simulation.x(0.0), lowerBound, upperBound);
//开始动画请求,驱动动画执行_ticker!.start();
final TickerFuture result = _ticker!.start();
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.forward :
AnimationStatus.reverse;
//动画状态的更新
_checkStatusChanged();
return result;
}
//Ticker类
TickerFuture start() {
//开始一个future方法,可以看成是一个待完成的动画
_future = TickerFuture._();
//开始请求心跳。
if (shouldScheduleTick) {
scheduleTick();
}
if (SchedulerBinding.instance.schedulerPhase.index > SchedulerPhase.idle.index &&
SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index) {
_startTime = SchedulerBinding.instance.currentFrameTimeStamp;
}
return _future!;
}
@protected
void scheduleTick({ bool rescheduling = false }) {
//通过SchedulerBinding注册_tick函数给心跳包,即每当vsync函数到来时就更新_tick回调函数
_animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}
1.第一步,更新AnimationStatus的动画执行状态。
2.第二步,调用_checkStatusChanged更新由addStatusListener注册的监听。即执行addStatusListener的回调告知AnimationStatus的状态。
3.第三步,注册vsync监听,等待监听回调ticker的_tick方法。
4.第四步,修正动画开始时间
在注册vsync监听前会先判断shouldScheduleTick是否为true
//是否处于静默状态
bool get muted => _muted;
//判断是否存在动画
bool get isActive => _future != null;
//是否正在等待心跳
@protected
bool get scheduled => _animationId != null;
//此变量即是判断是否需要一次心跳。
@protected
bool get shouldScheduleTick => !muted && isActive && !scheduled;
该变量主要是判断是否需要心跳,即是否需要注册vsync监听
我们看看_tick方法做了什么
//Ticker
void _tick(Duration timeStamp) {
assert(isTicking);
assert(scheduled);
_animationId = null;
//记录动画首次开始的时间戳
_startTime ??= timeStamp;
//执行_onTick方法,即前面传进来的方法。
_onTick(timeStamp - _startTime!);
//判断是否继续进行vsync监听注册
if (shouldScheduleTick) {
scheduleTick(rescheduling: true);
}
}
//AnimationController
void _tick(Duration elapsed) {
//动画已经执行了的时间
_lastElapsedDuration = elapsed;
//单位转换,将毫秒转换为秒
final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
//范围控制,同时执行插值操作
_value = clampDouble(_simulation!.x(elapsedInSeconds), lowerBound, upperBound);
//根据秒数判断动画是否完成
if (_simulation!.isDone(elapsedInSeconds)) {
//如果完成则更新状态
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
//停止动画
stop(canceled: false);
}
//广播更新_value值
notifyListeners();
_checkStatusChanged();
}
总结:记录动画时间并更新value值,并广播通知更新value字段已变更(即更新widget)
1.动画第一次开始记录动画的开始时间,并记录动画已经执行的时长
2.根据时长判断动画是否已经完成,如果完成则更新动画完成的状态。
3.如果动画已经完成则调用stop方法停止动画。
4.更新value并广播使用该value的widget进行更新
5.判断是否继续注册vsync继续监听刷新动画。
//通知value值更新
void notifyListeners() {
final List<VoidCallback> localListeners = _listeners.toList(growable: false);
for (final VoidCallback listener in localListeners) {
InformationCollector? collector;
try {
if (_listeners.contains(listener)) {
//这里开始通知监听器
listener();
}
} catch (exception, stack) {
}
}
}
//通知状态改变
void _checkStatusChanged() {
final AnimationStatus newStatus = status;
if (_lastReportedStatus != newStatus) {
_lastReportedStatus = newStatus;
notifyStatusListeners(newStatus);
}
}
//停止动画,并重置相关变量,调用ticker的stop方法处理
void stop({ bool canceled = true }) {
_simulation = null;
_lastElapsedDuration = null;
_ticker!.stop(canceled: canceled);
}
void stop({ bool canceled = false }) {
if (!isActive) {
return;
}
final TickerFuture localFuture = _future!;
//重置相关状态
_future = null;
_startTime = null;
assert(!isActive);
//停止注册vsync的更新
unscheduleTick();
if (canceled) {
localFuture._cancel(this);
} else {
localFuture._complete();
}
}
这里就只有两个操作,一个通知value值的更新,一个通知动画状态的改变
notifyListeners每次在帧更新的时候就通知value值的更新,通知更新动画状态。