一、问题出现场景
某个业务场景下需对一个View进行Rotation动画,代码如下:
float shakeDegrees = 3f;
float totalFrame = 12f;
int usedFrame = 0;
PropertyValuesHolder rotateHolder = PropertyValuesHolder.ofKeyframe(
View.ROTATION,
Keyframe.ofFloat((usedFrame += 2) / totalFrame, shakeDegrees),
Keyframe.ofFloat((usedFrame += 2) / totalFrame, -shakeDegrees),
Keyframe.ofFloat((usedFrame += 2) / totalFrame, 0f),
Keyframe.ofFloat((usedFrame += 2) / totalFrame, shakeDegrees),
Keyframe.ofFloat((usedFrame += 2) / totalFrame, -shakeDegrees),
Keyframe.ofFloat((usedFrame += 2) / totalFrame, 0f),
Keyframe.ofFloat(1f, 0f));
final ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(
findViewById(R.id.animView),
rotateHolder);
objectAnimator.setRepeatCount(1);
objectAnimator.setDuration((long) (40 * totalFrame));
执行完之后View便看不见了,通过抓取布局可以看到View的各项属性均是正常的,除了ration,其值是Float.NaN。
二、问题原因
对上述动画增加UpdateListener:
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i(TAG, "onAnimationUpdate: " + animation.getAnimatedValue());
}
});
有如下日志输出:
很明显,由于动画中值是NaN,故设置给View的Roation属性便是NaN了。
那如何出现NaN的呢?,比较简单的办法便是,对值为NaN时进行Debug,在上述Log行设置断点:
可以看出其调用路径,一层一层的回溯,先看ValueAnimator#animateValue()方法(由于ROM厂商可能修改源码,故行数与SDK中行数可能不一致):
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
注意到calculateValue方法,根据名字可得知会计算出当前动画对应的Animate Value,通过Debug可以看到mValues是FloatPropertyValuesHolder:
查看其源码:
void calculateValue(float fraction) {
mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}
再次通过Debug功能,如上图所示,mFloatKeyframes是FloatKeyframeSet:
public float getFloatValue(float fraction) {
if (fraction <= 0f) {
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
} else if (fraction >= 1f) {
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
}
// 省略部分代码
}
可以看出其大概思想是根据我们代码设置的关键帧,在两个关键帧之间,会计算之间的diff fraction,从而获得对应的AnimateValue,故若两关键帧的fraction是同样的值,计算出来的diff fraction便是Float.NaN,同样计算出来的AnimateValue也是Float.NaN,从而间接导致View的ration也是Float.NaN。检查动画代码,最后两帧对应的Fraction参数值是一样的。
三、问题解决办法
去除fration相同的Keyframe即可。