走进源码之SpringAnimation
简介
SpringAnimation 是通过 SpringForce 进行驱动的,SpringForce 定义了弹性的阻尼、刚度以及平衡位置。当 SpringAnimation 启动后,弹性驱动力都会在每一帧更新动画的值以及加速度,动画会一直执行到弹性达到一个平衡点。如果弹性没有设置阻尼的话,那么动画将会一直执行下去,不会停止。
SpringAnimation 是继承于 DynamicAnimation 的,DynamicAnimation 的另一个直接子类是 FlingAnimation。
构造方法
public SpringAnimation(FloatValueHolder floatValueHolder) {
super(floatValueHolder);
}
public <K> SpringAnimation(K object, FloatPropertyCompat<K> property) {
super(object, property);
}
/**
* object 动画作用的属性的对象
* property object 的属性,用于动画作用
* finalPosition 创建时动画的平衡点
* <K> 属性的类型
*/
public <K> SpringAnimation(K object, FloatPropertyCompat<K> property,
float finalPosition) {
super(object, property);
mSpring = new SpringForce(finalPosition);
}
SpringAnimation 有三个构造方法,第一个构造方法只有一个参数,参数类型为 FloatValueHolder,其实就是一个提供value 值存储与访问的地方,在下一帧到来时设置新的值,调用者访问存储值。第二个构造函数与第三个构造函数大径相同,唯一不同的是第三个构造函数为我们初始化了 spring 属性,建议大家创建 SpringAniamtion 实例的时候用第三个构造函数,可以避免因忘记设置 spring 或者是 finalPosition 而导致异常发生。点进带两个参数的父类构造方法看看。
<K> DynamicAnimation(K object, FloatPropertyCompat<K> property) {
mTarget = object;
mProperty = property;
if (mProperty == ROTATION || mProperty == ROTATION_X
|| mProperty == ROTATION_Y) {
mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES;
} else if (mProperty == ALPHA) {
mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA;
} else if (mProperty == SCALE_X || mProperty == SCALE_Y) {
mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA;
} else {
mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
}
}
该方法就是确定动画的对象和动画作用的属性,以及为mMinVisibleChange
赋值,mMinVisibleChange
为用户最小可见变化的值,这个值会根据不同的属性有不同的设定,以 view 来说,当动画作用的是 view 的 rotation、rotationX 或者是 rotationY 属性时,这个值为MIN_VISIBLE_CHANGE_ROTATION_DEGREES
,如果是 alpha 或者是 scale 也会有不同的值,默认初始化后设置为像素类型值MIN_VISIBLE_CHANGE_PIXELS
。
start()
@Override
public void start() {
sanityCheck();
mSpring.setValueThreshold(getValueThreshold());
super.start();
}
SpringAnimation 的启动入口,在真正启动之前,做了一个安全检查,并给 spring 设置了相应的阈值,最后才是调用父类的启动方法,真正让动画动起来。
private void sanityCheck() {
if (mSpring == null) {
throw new UnsupportedOperationException("Incomplete SpringAnimation: Either final"
+ " position or a spring force needs to be set.");
}
double finalPosition = mSpring.getFinalPosition();
if (finalPosition > mMaxValue) {
throw new UnsupportedOperationException("Final position of the spring cannot be greater"
+ " than the max value.");
} else if (finalPosition < mMinValue) {
throw new UnsupportedOperationException("Final position of the spring cannot be less"
+ " than the min value.");
}
}
spring 在 SpringAnimation 里面不可或缺,弹性动画的各种计算,主要都是依靠 spring 来实现的。如果给 spring 设置了最大最小值的注意了,设置 finalPosition 的时候一定要在这个区间范围内,不然会报错。默认的最大值为 Float 的最大值,而最小值为最大值的负值。
查看父类的 start() 方法,终于来到真正执行动画开始的地方。
public void start() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new AndroidRuntimeException("Animations may only be started on the main thread");
}
if (!mRunning) {
startAnimationInternal();
}
}
由上可知,我们的启动动画必须在主线程操作,多次调用 start() 方法,并不会多次执行 startAnimationInternal() 。接着看 startAnimationInternal() 做了什么。
private void startAnimationInternal() {
if (!mRunning) {
// 设置已启动标志位
mRunning = true;
// 是否设置过开始值
if (!mStartValueIsSet) {
// 如果没有设置过的话,系统会取对象当前的属性值
mValue = getPropertyValue();
}
// Sanity check:
if (mValue > mMaxValue || mValue < mMinValue) {
throw new IllegalArgumentException("Starting value need to be in between min"
+ " value and max value");
}
AnimationHandler.getInstance().addAnimationFrameCallback(this, 0);
}
}
这里依旧是动画启动前的一些准备工作,我们关注到AnimationHandler.getInstance().addAnimationFrameCallback(this, 0);
继续跟进去看。
// 注册以获取延迟后下一帧的回调
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// 如果在这之前没任何回调注册,推动第一帧。
getProvider().postFrameCallback();
}
// 过滤掉重复注册的回调
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
// 带有延时的回调,先存储进待处理集合。
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
SystemClock.uptimeMillis() 会返回从开机到此刻的毫秒数,这其中不包括深度休眠的时间。看看第一帧是如何推动的getProvider().postFrameCallback()
,这个 getProvider() 是什么?
AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mProvider = new FrameCallbackProvider16(mCallbackDispatcher);
} else {
mProvider = new FrameCallbackProvider14(mCallbackDispatcher);
}
}
return mProvider;
}
可以看到返回类型是 AnimationFrameCallbackProvider 。
abstract static class AnimationFrameCallbackProvider {
final AnimationCallbackDispatcher mDispatcher;
AnimationFrameCallbackProvider(AnimationCallbackDispatcher dispatcher) {
mDispatcher = dispatcher;
}
abstract void postFrameCallback();
}
AnimationFrameCallbackProvider 是一个抽象类,我们主要看它的实现类,上面可以看到,这里根据 Android API 的不同分别设置两个实现类,API16 及以上用的是 FrameCallbackProvider16 而以下的则用 FrameCallbackProvider14 这两个在实现上有什么不同?接着往下看。
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
private static class FrameCallbackProvider16 extends AnimationFrameCallbackProvider {
// 获取编排器实例
private final Choreographer mChoreographer = Choreographer.getInstance();
private final Choreographer.FrameCallback mChoreographerCallback;
FrameCallbackProvider16(AnimationCallbackDispatcher dispatcher) {
super(dispatcher);
// 初始化编排器回调
mChoreographerCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mDispatcher.dispatchAnimationFrame();
}
};
}
@Override
void postFrameCallback() {
// post 第一帧,相当于启动编排器了。
mChoreographer.postFrameCallback(mChoreographerCallback);
}
}
Choreographer 跟创建实例的线程有关系,在哪个线程创建的,它就运行在哪个线程,特别注意的一点是,如果要获取 Choreographer 实例,那这个线程一定要有自己的 looper。其内部的实现,是通过 handler 实现消息循环的,这里我们是在主线程创建的,所以,我们的 Choreographer 是运行在主线程上的。兜兜转转,Choreographer 会走到一下代码:
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
这里,我们是FRAME_CALLBACK_TOKEN
类型,所以这里就调用了doFrame(),也就是回调了我们上一步设置的回调最终会执行mDispatcher.dispatchAnimationFrame();
,这里的 dispatcher 我们后面一起看,我们接着看 API16 以下的实现:
private static class FrameCallbackProvider14 extends AnimationFrameCallbackProvider {
private final Runnable mRunnable;
private final Handler mHandler;
long mLastFrameTime = -1;
FrameCallbackProvider14(AnimationCallbackDispatcher dispatcher) {
super(dispatcher);
mRunnable = new Runnable() {
@Override
public void run() {
mLastFrameTime = SystemClock.uptimeMillis();
mDispatcher.dispatchAnimationFrame();
}
};
mHandler = new Handler(Looper.myLooper());
}
@Override
void postFrameCallback() {
long delay = FRAME_DELAY_MS - (SystemClock.uptimeMillis() - mLastFrameTime);
delay = Math.max(delay, 0);
mHandler.postDelayed(mRunnable, delay);
}
}
可以看到,这里是通过 handler 和 runnable 来实现的不断轮询的。runnable 调用后调用 dispatcher 的 dispatchAnimationFrame,而这里面又调用postFrameCallback 实现循环。
class AnimationCallbackDispatcher {
void dispatchAnimationFrame() {
// 记录当前帧时间
mCurrentFrameTime = SystemClock.uptimeMillis();
AnimationHandler.this.doAnimationFrame(mCurrentFrameTime);
if (mAnimationCallbacks.size() > 0) {
// 如果有监听者的话继续调用以实现循环
getProvider().postFrameCallback();
}
}
}
这里看到了,dispatchAnimationFrame 回调了 AnimationHandler 的 doAnimationFrame 方法:
void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
for (int i = 0; i < mAnimationCallbacks.size(); i++) {
// 遍历所有已经注册的监听者
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
// 因为在调用 removeCallback 的时候,并没有真正的从集合里面删除,而是赋值为 null。
if (callback == null) {
continue;
}
// 判断是否当前帧该回调的监听者
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);
}
}
// 真正执行 mAnimationCallbacks 的元素删除。
cleanUpList();
}
可以看到,正常无 delay 的情况下,callback.doAnimationFrame() 会被调用。DynamicAnimation 也就是 SpringAnimation 的父类,在 start() 里面注册了这个监听器,该方法每一帧都会回调,所以会在里面进行 value 和 velocity 的计算,并且判断动画是否该结束。
public boolean doAnimationFrame(long frameTime) {
if (mLastFrameTime == 0) {
// First frame.
mLastFrameTime = frameTime;
setPropertyValue(mValue);
return false;
}
// 时间差,用于计算 value 和 velocity。
long deltaT = frameTime - mLastFrameTime;
mLastFrameTime = frameTime;
// 判断动画
boolean finished = updateValueAndVelocity(deltaT);
// Clamp value & velocity.
mValue = Math.min(mValue, mMaxValue);
mValue = Math.max(mValue, mMinValue);
// 更新属性值,并且回调所有监听的 update 的 listeners
setPropertyValue(mValue);
if (finished) {
// 平滑结束动画。
endAnimationInternal(false);
}
return finished;
}
计算 value 和 velocity 都在 updateValueAndVelocity 方法里面,这个方法是一个抽象方法,直接跟到 SpringAnimation 的实现里面。
@Override
boolean updateValueAndVelocity(long deltaT) {
// If user had requested end, then update the value and velocity to end state and consider
// animation done.
if (mEndRequested) {
// 设置结束点结束动画
if (mPendingPosition != UNSET) {
mSpring.setFinalPosition(mPendingPosition);
mPendingPosition = UNSET;
}
mValue = mSpring.getFinalPosition();
mVelocity = 0;
mEndRequested = false;
return true;
}
if (mPendingPosition != UNSET) {
double lastPosition = mSpring.getFinalPosition();
// Approximate by considering half of the time spring position stayed at the old
// position, half of the time it's at the new position.
// massState 用来存储 value 和 velocity。
MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT / 2);
mSpring.setFinalPosition(mPendingPosition);
mPendingPosition = UNSET;
massState = mSpring.updateValues(massState.mValue, massState.mVelocity, deltaT / 2);
mValue = massState.mValue;
mVelocity = massState.mVelocity;
} else {
MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT);
mValue = massState.mValue;
mVelocity = massState.mVelocity;
}
// 确保 value 在正常范围内
mValue = Math.max(mValue, mMinValue);
mValue = Math.min(mValue, mMaxValue);
// 是否已经达到平衡状态,可以理解为动画该结束的状态。
if (isAtEquilibrium(mValue, mVelocity)) {
mValue = mSpring.getFinalPosition();
mVelocity = 0f;
return true;
}
return false;
}
上面给 value 和 velocity 赋值的时候,有个判断因素,那就是 mPendingPosition,mPendingPosition != UNSET 只有在调用 animateToFinalPosition() 且动画处于 running 状态的时候,才会成立。也就是说,当调用 animateToFinalPosition() 驱动动画的时候,就会将时间一分为二,然后最终计算出 value 和 velocity,如果用 start() 驱动动画,则会直接调用 SpringForce 的 updateValues() 函数计算 value 和 velocity。
DynamicAnimation.MassState updateValues(double lastDisplacement, double lastVelocity,
long timeElapsed) {
init();
double deltaT = timeElapsed / 1000d; // unit: seconds
lastDisplacement -= mFinalPosition;
double displacement;
double currentVelocity;
if (mDampingRatio > 1) {
// Overdamped
double coeffA = lastDisplacement - (mGammaMinus * lastDisplacement - lastVelocity)
/ (mGammaMinus - mGammaPlus);
double coeffB = (mGammaMinus * lastDisplacement - lastVelocity)
/ (mGammaMinus - mGammaPlus);
displacement = coeffA * Math.pow(Math.E, mGammaMinus * deltaT)
+ coeffB * Math.pow(Math.E, mGammaPlus * deltaT);
currentVelocity = coeffA * mGammaMinus * Math.pow(Math.E, mGammaMinus * deltaT)
+ coeffB * mGammaPlus * Math.pow(Math.E, mGammaPlus * deltaT);
} else if (mDampingRatio == 1) {
// Critically damped
double coeffA = lastDisplacement;
double coeffB = lastVelocity + mNaturalFreq * lastDisplacement;
displacement = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT);
currentVelocity = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT)
* (-mNaturalFreq) + coeffB * Math.pow(Math.E, -mNaturalFreq * deltaT);
} else {
// Underdamped
double cosCoeff = lastDisplacement;
double sinCoeff = (1 / mDampedFreq) * (mDampingRatio * mNaturalFreq
* lastDisplacement + lastVelocity);
displacement = Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
* (cosCoeff * Math.cos(mDampedFreq * deltaT)
+ sinCoeff * Math.sin(mDampedFreq * deltaT));
currentVelocity = displacement * (-mNaturalFreq) * mDampingRatio
+ Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
* (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
+ mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
}
mMassState.mValue = (float) (displacement + mFinalPosition);
mMassState.mVelocity = (float) currentVelocity;
return mMassState;
}
这里和之前阻尼的划分是一样的,都有几个节点,>1的情况、=1的情况以及<1的情况。这段代码看着还蛮头晕的,不过我们知道,它利用了各种公式结合阻尼率和刚性计算出 value 和 velocity 就差不多可以了,吧。🤣🤣🤣
isAtEquiLibrium() 最终调用的是 SpringForce 里面的 isAtEquilibrium()
public boolean isAtEquilibrium(float value, float velocity) {
if (Math.abs(velocity) < mVelocityThreshold
&& Math.abs(value - getFinalPosition()) < mValueThreshold) {
return true;
}
return false;
}
如果 velocity 或者是位移差小于阈值的时候系统就认为其达到平衡状态了。这时就该停止动画了。
private void endAnimationInternal(boolean canceled) {
mRunning = false;
// 反注册,不在接收帧回调。
AnimationHandler.getInstance().removeCallback(this);
mLastFrameTime = 0;
mStartValueIsSet = false;
for (int i = 0; i < mEndListeners.size(); i++) {
if (mEndListeners.get(i) != null) {
// 回调 onAnimationEnd()
mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity);
}
}
removeNullEntries(mEndListeners);
}
调用 cancel() 最终也会执行 endAnimationInternal()。至此,从动画开始到结束的流程就过了一遍了,当然有很多的细节没有提到,可以自己阅读下源码,除了计算那一段,其他的应该都挺好理解的。