天天看点

属性动画源码分析

好久没有写博客,因为本身工作的项目里面涉及到很多动画,而里面用到动画基本是属性动画,所以这几天就认真阅读了里面的源码进行分析,话不多说,开讲!

使用

ObjectAnimator animator = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, 2.0f);
animator.setDuration(2000);
animator.setInterpolator(new LinearInterpolator());
animator.start();
           

分析

  • 前期准备工作

首先我们来看看ofFloat方法

#ObjectAnimator.ofFloat()

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setFloatValues(values);
    return anim;
}
           

这里首先会传入三个参数,target就是要执行动画的View,propertyName就是改变的属性名字,随后values是一个可变数组,其实可以理解为属性变化的值,从后面分析可以知道这里是关键帧。接下来我们看ObjectAnimator的构造方法:

private ObjectAnimator(Object target, String propertyName) {
    setTarget(target);
    setPropertyName(propertyName);
}

public void setTarget(@Nullable Object target) {
    final Object oldTarget = getTarget();
    // 如果旧的动画与新的动画设置的对象是一样的,如果旧动画已经开始了
    // 则需要把旧动画取消
    if (oldTarget != target) {
        if (isStarted()) {
            cancel();
        }
        mTarget = target == null ? null : new WeakReference<Object>(target);
        mInitialized = false;
    }
}

public void setPropertyName(@NonNull String propertyName) {
    if (mValues != null) {
        PropertyValuesHolder valuesHolder = mValues[0];
        String oldName = valuesHolder.getPropertyName();
        valuesHolder.setPropertyName(propertyName);
        mValuesMap.remove(oldName);
        mValuesMap.put(propertyName, valuesHolder);
    }
    mPropertyName = propertyName;
    mInitialized = false;
}
           

这个构造方法里面,最主要的就是赋值,mTarget 为控件对象,mPropertyName为修改的属性。我们继续看回ObjectAnimator.ofFloat方法里面,还会调用anim.setFloatValues(values),我们继续看看:

#ObjectAnimator.setFloatValues()

public void setFloatValues(float... values) {
    if (mValues == null || mValues.length == 0) {
        if (mProperty != null) {
            setValues(PropertyValuesHolder.ofFloat(mProperty, values));
        } else {
            // 关键代码1
            setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
        }
    } else {
        super.setFloatValues(values);
    }
}
           

因为在此之前mValues并没有进行赋值,而且mProperty也是为空的,所以会走到关键代码1这里setValues(PropertyValuesHolder.ofFloat(mPropertyName, values)),那我们就先看PropertyValuesHolder.ofFloat()这个方法

#PropertyValuesHolder.ofFloat

public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
    return new FloatPropertyValuesHolder(propertyName, values);
}
private PropertyValuesHolder(String propertyName) {
    mPropertyName = propertyName;
}

// FloatPropertyValuesHolder类里面的
public FloatPropertyValuesHolder(String propertyName, float... values) {
    super(propertyName);
    setFloatValues(values);
}
           

我们可以看到,这里返回的FloatPropertyValuesHolder的实例,而FloatPropertyValuesHolder是继承自PropertyValuesHolder ,所以我们看到它的构造方法这里。

在FloatPropertyValuesHolder的构造方法里,会调用super(proertyName),就是将PropertyValuesHolder中的mPropertyName属性名赋值为传进来的proertyName(注:需要变化的属性)。

接着FloatPropertyValuesHolder的构造方法里面,还调用了setFloatValues(values),我们看看:

#FloatPropertyValuesHolder.setFloatValues()

public void setFloatValues(float... values) {
    super.setFloatValues(values);
    mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}

// PropertyValuesHolder.setFloatValues()
public void setFloatValues(float... values) {
    mValueType = float.class;
    mKeyframes = KeyframeSet.ofFloat(values);
}
           

这里我们可以看到它这里又会去调用父类PropertyValuesHolder的setFloatValues方法,然后将mKeyframes赋值给mFloatKeyframes。

那我们就看看父类的setFloatValues方法,这里首先将mValueType赋值为float.class,就是说修改的属性值是float类型的。然后就是调用KeyframeSet.ofFloat方法

#KeyframeSet.ofFloat

public static KeyframeSet ofFloat(float... values) {
    boolean badValue = false;
    int numKeyframes = values.length;
    // 动画至少两个关键帧,一头一尾
    FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
    if (numKeyframes == 1) {
        keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
        keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
        if (Float.isNaN(values[0])) {
            badValue = true;
        }
    } else {
        keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
        for (int i = 1; i < numKeyframes; ++i) {
            keyframes[i] =
                    (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
            if (Float.isNaN(values[i])) {
                badValue = true;
            }
        }
    }
    return new FloatKeyframeSet(keyframes);
}

// fraction:当前关键帧的时间百分比,例如有3帧,那么第2帧就是50%了
// value:来到当前帧时的数值
public static Keyframe ofFloat(float fraction, float value) {
    return new FloatKeyframe(fraction, value);
}
           

这里可以看到就是创建我们一个一个的关键帧Keyframe,因为修改的属性类型是float类型,所以会转化为FloatKeyframe。然后把这些关键帧封装到关键帧集FloatKeyframeSet中,然后返回到PropertyValuesHolder的时候是mKeyframes ,到FloatPropertyValuesHolder就会强转为mFloatKeyframes。

// 关键帧
public interface Keyframes extends Cloneable

//关键帧集,管理着关键帧
public class KeyframeSet implements Keyframes {
    int mNumKeyframes;
    Keyframe mFirstKeyframe;
    Keyframe mLastKeyframe;
    TimeInterpolator mInterpolator;
    List<Keyframe> mKeyframes; 
    TypeEvaluator mEvaluator;
}

// Float类型的关键帧集
class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes
           

至此,动画的准备工作已经处理好了,我们就正式启动动画。

  • 动画启动

#ObjectAnimator.start

public void start() {
    AnimationHandler.getInstance().autoCancelBasedOn(this);
    ......
    super.start();
}
           

这里主要的是调用了父类ValueAnimator的start方法:

#ValueAnimator.start

private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("");
    }
    ......
    // 关键代码1
    addAnimationCallback(0);
    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        // 关键代码2
        startAnimation();
        if (mSeekFraction == -1) {
            // 关键代码3
            setCurrentPlayTime(0);
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}
           

这里看到调用ValueAniator.start()方法之后,又调用了start的重载方法。我们看这里的关键代码1,addAnimationCallback(0),就是会把自身ValueAnimator添加到回调中,因为ValueAnimator是实现了AnimationHandler.AnimationFrameCallback这个接口,这里后续会继续讲到。

然后我们继续看到关键代码2这里,startAnimation()方法

#ValueAnimator.startAnimation()

private void startAnimation() {
    ......
    initAnimation();
    ......
    if (mListeners != null) {
        // 通知注册了动画监听器的回调
        notifyStartListeners();
    }
}
           

这里主要是两行代码,就是initAnimation和notifyStartListeners。

#ValueAnimator.initAnimation()

void initAnimation() {
    if (!mInitialized) {
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            // 这个mValues数组就是PropertyValuesHolder数组
            // 在前面也定义过,这里就是FloatPropertyValuesHolder
            mValues[i].init();
        }
        mInitialized = true;
    }
}
           

在前面我们已经得知这个mValues数组就是PropertyValuesHolder数组,mValues[i]就是FloatPropertyValuesHolder,那我们看看init方法

#PropertyValuesHolder.init()

void init() {
    if (mEvaluator == null) 
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                (mValueType == Float.class) ? sFloatEvaluator :
                null;
    }
    if (mEvaluator != null) {
        mKeyframes.setEvaluator(mEvaluator);
    }
}
           

这个方法的主要目的就是给关键帧设置估值器。因为类型是float类型,所以估值器就是sFloatEvaluator,也就是FloatEvaluator。

#KeyframeSet.setEvaluator()

public void setEvaluator(TypeEvaluator evaluator) {
    mEvaluator = evaluator;
}
           

现在我们继续回到ValueAnimator.start()方法里面,看到关键代码3这里setCurrentPlayTime(0)

#ValueAnimator.setCurrentPlayTime()&setCurrentFraction()

public void setCurrentPlayTime(long playTime) {
    // 因为动画的时长比0大,而且一开始playTime是为0的
    // 所以传进去的是0
    float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
    setCurrentFraction(fraction);
}

public void setCurrentFraction(float fraction) {
    initAnimation();
    fraction = clampFraction(fraction);
    mStartTimeCommitted = true; 
    if (isPulsingInternal()) {
        long seekTime = (long) (getScaledDuration() * fraction);
        long currentTime = AnimationUtils.currentAnimationTimeMillis();
        mStartTime = currentTime - seekTime;
    } else {
        mSeekFraction = fraction;
    }
    mOverallFraction = fraction;
    final float currentIterationFraction = 
        getCurrentIterationFraction(fraction, mReversing);
    // 执行动画,在子类和父类中都有定义这个方法,
    // 因此是会调用子类ObjectAnimator.animateValue方法
    animateValue(currentIterationFraction);
}
           

好,这里看注释应该基本都清楚,那我们就看看子类和父类的animateValue()方法

#ObjectAnimator.animateValue() & ValueAnimator.animateValue()

/**子类
 * ObjectAnimator.animateValue()
 */ 
void animateValue(float fraction) {
    final Object target = getTarget();
    // 这里就是要判断对象是否为空
    if (mTarget != null && target == null) {
        cancel();
        return;
    }
    // 关键代码,调用父类的方法
    super.animateValue(fraction);
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
    	// 通过holder来修改target的属性值
        mValues[i].setAnimatedValue(target);
    }
}

/** 父类
 * ValueAnimator.animateValue()
 */ 
void animateValue(float fraction) {
    // 通过插值器计算当前这一帧所占用的时间百分比
    fraction = mInterpolator.getInterpolation(fraction);
    mCurrentFraction = fraction;
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        //通过holder,根据插值,用估值器计算这一帧的属性值
        mValues[i].calculateValue(fraction);
    }
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}
           

这里首先调用的是子类的animateValue方法,然后在里面又调用了父类的animateValue方法。

在父类中,一开始就利用时间插值器计算这一帧的时间百分比,然后在利用估值器计算出当前帧的属性数值,mValues[i].calculateValue(fraction):

void calculateValue(float fraction) {
    mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}

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();
    }
    FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
    for (int i = 1; i < mNumKeyframes; ++i) {
        FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
        if (fraction < nextKeyframe.getFraction()) {
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                (nextKeyframe.getFraction() - prevKeyframe.getFraction());
            float prevValue = prevKeyframe.getFloatValue();
            float nextValue = nextKeyframe.getFloatValue();
            // Apply interpolator on the proportional duration.
            if (interpolator != null) {
                intervalFraction = interpolator.getInterpolation(intervalFraction);
            }
            return mEvaluator == null ?
                    prevValue + intervalFraction * (nextValue - prevValue) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                        floatValue();
        }
        prevKeyframe = nextKeyframe;
    }
    // shouldn't get here
    return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
}
           

算出这一帧的属性值之后,就要去修改属性值,所以我们就要看回ObjectAnimator.animateValue()方法里面,会调用holder的setAnimatedValue(target)方法来修改属性值

#FloatPropertyValuesHolder.setAnimatedValue(target)

void setAnimatedValue(Object target) {
    if (mFloatProperty != null) {
        mFloatProperty.setValue(target, mFloatAnimatedValue);
        return;
    }
    if (mProperty != null) {
        mProperty.set(target, mFloatAnimatedValue);
        return;
    }
    if (mSetter != null) {
        try {
            mTmpValueArray[0] = mFloatAnimatedValue;
            mSetter.invoke(target, mTmpValueArray);
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }
}
           

很清楚这里就是通过通过反射设置数值,修改数值。

总结这个animateValue方法:

1、通过插值器计算出到这帧的时候需要占时间的百分比

2、根据这个时间百分比,利用估值器算出此时控件的属性值

3、最后在FloatPropertyValuesHolder里面,利用反射设置控件的属性值

  • 如何反复调用改变控件的属性,进而形成动画?

通过以上的操作,一帧就处理了,但是后面的帧数怎么办呢?

其实在start方法的时候,有注册了一个AnimationFrameCallback,而这个callback就是ValueAnimator,接口定义了一个方法,就是doAnimationFrame,

AnimationHandler.addAnimationFrameCallback(callback, delay)

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (mAnimationCallbacks.size() == 0) {
        getProvider().postFrameCallback(mFrameCallback);
    }
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }
   ......
}
           

#ValueAnimator.doAnimationFrame()

public final boolean doAnimationFrame(long frameTime) {
    ......
    final long currentTime = Math.max(frameTime, mStartTime);
    // 关键代码
    boolean finished = animateBasedOnTime(currentTime);
    if (finished) {
        endAnimation();
    }
    return finished;
}
           

这里只需要看关键代码animateBasedOnTime()

#ValueAnimator.animateBasedOnTime()

boolean animateBasedOnTime(long currentTime) {
    boolean done = false;
    if (mRunning) {
        ......
        mOverallFraction = clampFraction(fraction);
        float currentIterationFraction = getCurrentIterationFraction(
                mOverallFraction, mReversing);
        animateValue(currentIterationFraction);
    }
    return done;
}
           

这里首先也是计算fraction时间百分比,然后往下看,看到没有,又调用了animateValue()方法了,这个方法在上面就已经讲过了。所以只要doAnimationFrame方法不断被调用,属性值就不断地发生变化,就会产生动画的效果。

那么如何连续调用doAnimationFrame方法?

在ValueAnimator的start方法里面,注册回调接口的时候,调用到AnimationHandler.addAnimationFrameCallback方法的时候,有这样一行getProvider().postFrameCallback(mFrameCallback);

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (mAnimationCallbacks.size() == 0) {
        getProvider().postFrameCallback(mFrameCallback);
    }
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }
    ......
}
           

这个mFrameCallback,我们来看看:

private final Choreographer.FrameCallback mFrameCallback = 
    new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());
        ......
    }
};
           

在Android系统中,每隔16ms就会发出VSYNC信号,触发对UI的渲染,然后就会调用Choreographe的回调接口,就是说会调用这个doFrame的回调函数。在这个函数里面,又会调用doAnimationFrame(long frameTime)这个方法:

#AnimationHandler.doAnimationFrame(long frameTime)

private void doAnimationFrame(long frameTime) {
    long currentTime = SystemClock.uptimeMillis();
    final int size = mAnimationCallbacks.size();
    for (int i = 0; i < size; i++) {
        final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
        if (callback == null) {
            continue;
        }
        if (isCallbackDue(callback, currentTime)) {
            callback.doAnimationFrame(frameTime);
            ......
        }
    }
    cleanUpList();
}
           

这里我们看到会遍历这个mAnimationCallbacks的集合,它其实是在Animator.start()方法里面addAnimationFrameCallback()的时候添加callback,而这个callback其实就是ValueAnimator,所以就会去调用ValueAnimator的doAnimationFrame()方法。这样就可以造成多次调用doAnimationFrame方法,进而形成动画。

补充:

插值器与估值器的区别?

属性动画源码分析

这里有个透明度从0f到1f的动画,需要5帧就能完成。这里每一帧出现的时间都不会发生改变,都是每隔16ms会画一帧出来,但是每一帧占这个动画总的百分比不一样,这里就是通过插值器来控制的。拿到这个百分比之后,就可以通过估值器算出这个百分比占的数值是多少

插值器:相当于函数y=f(x),x为时间,y为时间的百分比,0~1之间

估值器:根据时间变化规律得到每一步的运算结果

继续阅读