记因Rotation为Float.NaN导致View不可见的问题

889 阅读1分钟

一、问题出现场景

某个业务场景下需对一个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即可。