Android属性动画原理

824 阅读5分钟

概述

属性动画是在Android3.0提供的一套全新的动画Api,它和传统的补间动画相比有更大的灵活性。比如我们要使用动画更新一个button的宽度,如果使用补间动画ScaleAnimation那么最后放大后的button是变形的,而使用属性动画实现不存在这样的问题,还有最大的区别在于补间动画不会真正的改变view的属性,而属性动画会。

本篇将从源码角度对属性动画进行分析

ValueAnimation

关于属性动画,我们最常用的类便是ValueAnimation和ObjectAnimator,其中ValueAnimation是ObjectAnimator的父类。它是属性动画实现的核心,所以我们先来看看ValueAnimation的实现

####继承关系

public class ValueAnimator extends Animator {
    ...
}

ValueAnimator继承自Animator,Animator是属性动画最基本的类,它是一个抽象类,定义了一些属性动画的接口,如start,cancel,setduration,addListener等等,ValueAnimator一般是通过使用其静态的of方法来构造,这里我们看看ofInt的实现,其他of方法实现类似

public static ValueAnimator ofInt(int... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setIntValues(values);
    return anim;
}

ofInt构造ValueAnimator实例并通过setIntValues为其设置初始值。

public void setIntValues(int... values) {
    if (values == null || values.length == 0) {
        return;
    }
    if (mValues == null || mValues.length == 0) {
        setValues(PropertyValuesHolder.ofInt("", values));
    } else {
        PropertyValuesHolder valuesHolder = mValues[0];
        valuesHolder.setIntValues(values);
    }
    // New property/values/target should cause re-initialization prior to starting
    mInitialized = false;
}

mValues是一个PropertyValuesHolder数组,从名称来看PropertyValuesHolder是属性和值的持有者,它维护属性和值的相关信息。开始mValues是null,继续调用setValues,同时使用PropertyValuesHolder的ofInt方法构造PropertyValuesHolder实例作为参数,属性名为空。

public static PropertyValuesHolder ofInt(String propertyName, int... values) {
    return new IntPropertyValuesHolder(propertyName, values);
}

public void setValues(PropertyValuesHolder... values) {
    int numValues = values.length;
    mValues = values;
    mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
    for (int i = 0; i < numValues; ++i) {
        PropertyValuesHolder valuesHolder = values[i];
        mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
    }
    // New property/values/target should cause re-initialization prior to starting
    mInitialized = false;
}

mValuesMap是一个Map,key值时属性名,value是PropertyValuesHolder,对于每一个属性的PropertyValuesHolder都会保存在mValuesMap中。

@Override
public void start() {
    start(false);
}

private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mPlayingBackwards = playBackwards;
    mCurrentIteration = 0;
    mPlayingState = STOPPED;
    mStarted = true;
    mStartedDelay = false;
    mPaused = false;
    AnimationHandler animationHandler = getOrCreateAnimationHandler();
    animationHandler.mPendingAnimations.add(this);
    if (mStartDelay == 0) {
        // This sets the initial value of the animation, prior to actually starting it running
        setCurrentPlayTime(0);
        mPlayingState = STOPPED;
        mRunning = true;
        notifyStartListeners();
    }
    animationHandler.start();
}

ValueAnimation的启动默认调用了内部的private的start方法,参数playBackwards为false,表示动画是否反向执行。随后通过getOrCreateAnimationHandler创建一个AnimationHandler,然后将当前动画添加到其内部的等待队列mPendingAnimations中,这个animationHandler成员是个AnimationHandler,它是ValueAnimation内部类,如果未设置mStartDelay,则开始执行动画第一帧,随后通过notifyStartListeners通知动画Listener动画启动。

public void setCurrentPlayTime(long playTime) {
    initAnimation();//初始化动画
    long currentTime = AnimationUtils.currentAnimationTimeMillis();//当前时间
    if (mPlayingState != RUNNING) {
        mSeekTime = playTime;
        mPlayingState = SEEKED;
    }
    mStartTime = currentTime - playTime;//计算动画执行的开始时间
    doAnimationFrame(currentTime);//执行动画
}

setCurrentPlayTime是执行playTime指定的时间点的动画,这个playTime是在[0,duration]的区间内。在setCurrentPlayTime需要先初始化动画,然后设置mSeekTime为0,执行状态为SEEKED,随后通过doAnimationFrame执行动画。

void initAnimation() {
    if (!mInitialized) {
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].init();
        }
        mInitialized = true;
    }
}

动画只需要初始化一次,主要是对PropertyValuesHolder进行初始化,后面我们在看关于PropertyValuesHolder的初始化部分。初始化通过mInitialized标记控制。

final boolean doAnimationFrame(long frameTime) {
    if (mPlayingState == STOPPED) {
        mPlayingState = RUNNING;//设置播放状态为RUNNING
        if (mSeekTime < 0) {
            mStartTime = frameTime;
        } else {
            mStartTime = frameTime - mSeekTime;
            // Now that we're playing, reset the seek time
            mSeekTime = -1;
        }
    }
    if (mPaused) {
        if (mPauseTime < 0) {
            mPauseTime = frameTime;
        }
        return false;
    } else if (mResumed) {
        mResumed = false;
        if (mPauseTime > 0) {
            // Offset by the duration that the animation was paused
            mStartTime += (frameTime - mPauseTime);
        }
    }
    final long currentTime = Math.max(frameTime, mStartTime);
    return animationFrame(currentTime);//如果动画结束了就返回true
}

mSeekTime为0,mStartTime为frameTime值,即当前的时间值。如果mPaused表示暂停动画中,返回false,mResumed为true表示动画继续执行,此时根据mPauseTime重新计算mStartTime,即动画的开始执行时间,随后取frameTime和mStartTime的最大值传递给animationFrame进一步执行动画。

boolean animationFrame(long currentTime) {
    boolean done = false;
    switch (mPlayingState) {
    case RUNNING:
    case SEEKED:
        float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
        if (fraction >= 1f) {
            if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
                ……    
            }else{
                 done = true;
                fraction = Math.min(fraction, 1.0f);
            }
        }
        ……
        animateValue(fraction);
        break;
    }

    return done;
}

animationFrame中首先根据mDuration计算fraction,fraction即时间流逝的百分比,它的值是客观均匀的,随后通过animateValue计算动画值,这个方法返回动画是否执行完毕。

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);
        }
    }
}

通过fraction和插值器计算动画插值,我们知道插值器是能够控制动画的执行速率,它根据fraction值计算,ValueAnimation中默认的插值器是AccelerateDecelerateInterpolator,随后通过PropertyValuesHolder的calculateValue根据插值计算动画值,这个动画值就是我们在of方法指定的区间内的值,最后通过onAnimatinoUpdate来通知动画值的更新。到这里我们的动画第一帧就算执行了,那么它如何连续执行呢?这就要靠AnimationHandler了。

protected static ThreadLocal<AnimationHandler> sAnimationHandler =
            new ThreadLocal<AnimationHandler>();

其中AnimationHandler的说明如下

/**
* This custom, static handler handles the timing pulse that is shared by
* all active animations. This approach ensures that the setting of animation
* values will happen on the UI thread and that all animations will share
* the same times for calculating their values, which makes synchronizing
* animations possible.
*
* The handler uses the Choreographer for executing periodic callbacks.
*/

可以看出AnimationHandler在线程内是由所有活动的动画共享的。它可以保证animation动画值设置操作发生在UI线程,而所有的动画将共享相同的相同值来计算它们自己的值以使动画同步执行。这个handler使用Choreographer来执行周期性的回调。这个Choreographer可以看做是一个Vsync信号的观察者,它为上层应用提供监听VSync信号的接口,应用程序根据该信号刷新UI。

在start的方法最后调用了AnimationHandler的start,我们看看它的内部如何实现


protected static class AnimationHandler implements Runnable {

    ……
    public void start() {
        scheduleAnimation();//在下一帧中安排动画
    }

    @Override
    public void run() {
        mAnimationScheduled = false;
        doAnimationFrame(mChoreographer.getFrameTime());
    }

    //属性动画是的绘制是交给编舞者来处理绘制的
    private void scheduleAnimation() {
        if (!mAnimationScheduled) {
            //实际上是post一个Callback给编舞者 在垂直信号到来时这个Callback的run方法会执行
            mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
            mAnimationScheduled = true;
        }
    }
}

Animationhandler是通过Choreographer来执行动画的,在start方法中调用scheduleAnimation添加当前AnimationHandler到Choreographer的回调队列中,它的类型为Choreographer.CALLBACK_ANIMATION,Choreographer在接收到VSync信号时会通知该回调也就是调用回调的run方法,这里就是执行doAnimationFrame方法,同时重置mAnimationScheduled。所以真正执行动画的是通过doAnimationFrame来完成的。

private void doAnimationFrame(long frameTime) {
    //可能有多组等待的动画,将他们启动并添加到活动列表中
    while (mPendingAnimations.size() > 0) {
        ArrayList<ValueAnimator> pendingCopy =
                (ArrayList<ValueAnimator>) mPendingAnimations.clone();
        mPendingAnimations.clear();
        int count = pendingCopy.size();
        for (int i = 0; i < count; ++i) {
            ValueAnimator anim = pendingCopy.get(i);
            // If the animation has a startDelay, place it on the delayed list
            if (anim.mStartDelay == 0) {
                anim.startAnimation(this);
            } else {
                mDelayedAnims.add(anim);
            }
        }
    }
    ……
    
    //这里将活动动画添加到临时动画中 然后执行doAnimationFrame绘制一帧动画 
    int numAnims = mAnimations.size();
    for (int i = 0; i < numAnims; ++i) {
        mTmpAnimations.add(mAnimations.get(i));
    }
    
    for (int i = 0; i < numAnims; ++i) {
        ValueAnimator anim = mTmpAnimations.get(i);
        //根据doAnimationFrame的返回值可以知道动画是否结束了
        if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
            mEndingAnims.add(anim);
        }
    }
    
    mTmpAnimations.clear();//清理添加的临时动画 这里为什么需要一个mTmpAnimations?
    if (mEndingAnims.size() > 0) {
        for (int i = 0; i < mEndingAnims.size(); ++i) {
            mEndingAnims.get(i).endAnimation(this);
        }
        mEndingAnims.clear();
    }

    //如果有活动的动画或者延时的动画就等待下次垂直信号安排下一次绘制
    if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
        scheduleAnimation();//再绘制下一帧
    }
}

//将要执行的动画添加到活动列表中
private void startAnimation(AnimationHandler handler) {
    initAnimation();
    handler.mAnimations.add(this);
    if (mStartDelay > 0 && mListeners != null) {
        // Listeners were already notified in start() if startDelay is 0; this is
        // just for delayed animations
        notifyStartListeners();
    }
}

private void endAnimation(AnimationHandler handler) {
    handler.mAnimations.remove(this);
    handler.mPendingAnimations.remove(this);
    handler.mDelayedAnims.remove(this);
    mPlayingState = STOPPED;
    mPaused = false;
    if ((mStarted || mRunning) && mListeners != null) {
        if (!mRunning) {
            // If it's not yet running, then start listeners weren't called. Call them now.
            notifyStartListeners();
        }
        ArrayList<AnimatorListener> tmpListeners =
                (ArrayList<AnimatorListener>) mListeners.clone();
        int numListeners = tmpListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            tmpListeners.get(i).onAnimationEnd(this);
        }
    }
    mRunning = false;
    mStarted = false;
    mStartListenersCalled = false;
    mPlayingBackwards = false;
}

在doAnimationFrame中首先通过startAnimation将等待的动画启动,它其实将动画添加到活动的动画列表mAnimations并调用开始执行动画的回调。随后对于活动列表中的动画执行doAnimationFrame,它执行一帧动画。对于执行完成的动画调用endAnimation,它分别从活动列表,等待列表,延时列表中移除该动画实例。最后只要有活动的动画,就需要调用scheduleAnimation安排下一帧动画的执行。这样动画就能够连续执行了。

PropertyValuesHolder

在ValueAnimation动画的执行过程中依赖于PropertyValuesHolder,只不过这时候的PropertyName为空,在整个流程中涉及到PropertyValuesHolder的有下面几个部分:

  1. 在setValues中通过of方法实例化PropertyValuesHolder
  2. 在动画初始化initAnimation方法中调用其init方法
  3. 在animateValue中通过PropertyValuesHolder的calculateValue计算动画值

下面就分别看看这些具体是如何实现的

设置values

public static PropertyValuesHolder ofInt(String propertyName, int... values) {
    return new IntPropertyValuesHolder(propertyName, values);
}
static class IntPropertyValuesHolder extends PropertyValuesHolder {
    ……
    public IntPropertyValuesHolder(String propertyName, int... values) {
        super(propertyName);
        setIntValues(values);
    }

    @Override
    public void setIntValues(int... values) {
        super.setIntValues(values);
        mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
    }
    ……
}
//PropertyValuesHolder.java
public void setIntValues(int... values) {
    mValueType = int.class;
    mKeyframeSet = KeyframeSet.ofInt(values);
}

//KeyframeSet.java
public static KeyframeSet ofInt(int... values) {
    int numKeyframes = values.length;
    IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
    if (numKeyframes == 1) {
        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
        keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
    } else {
        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
        for (int i = 1; i < numKeyframes; ++i) {
            keyframes[i] =
                    (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
        }
    }
    return new IntKeyframeSet(keyframes);
}

我们设置的values最终是以KeyFrameSet的形式存在的,KeyframeSet实际上就是关键帧的值,包含了我们设置的值,以及 每个值对应的fraction。

初始化

void init() {
    if (mEvaluator == null) {
        // We already handle int and float automatically, but not their Object
        // equivalents
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                (mValueType == Float.class) ? sFloatEvaluator :
                null;
    }
    if (mEvaluator != null) {
        // KeyframeSet knows how to evaluate the common types - only give it a custom
        // evaluator if one has been set on this class
        mKeyframeSet.setEvaluator(mEvaluator);
    }
}

init实际上是为PropertyValuesHolder设置估值器,sIntEvaluator实际上就是整型的估值器,在计算动画值时使用,它的实现如下

public class IntEvaluator implements TypeEvaluator<Integer> {
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

计算动画值

void calculateValue(float fraction) {
    mAnimatedValue = mKeyframeSet.getValue(fraction);
}

public Object getValue(float fraction) {
    if (mNumKeyframes == 2) {//关键帧数目为2的情况
        if (mInterpolator != null) {//有插值器先计算插值
            fraction = mInterpolator.getInterpolation(fraction);
        }
        return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
                mLastKeyframe.getValue());
    }
    if (fraction <= 0f) {//关键帧第一帧
        final Keyframe nextKeyframe = mKeyframes.get(1);
        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);
        }
        final float prevFraction = mFirstKeyframe.getFraction();
        float intervalFraction = (fraction - prevFraction) /
            (nextKeyframe.getFraction() - prevFraction);
        return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
                nextKeyframe.getValue());
    } else if (fraction >= 1f) {//关键帧最后一帧
        final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
        final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);
        }
        final float prevFraction = prevKeyframe.getFraction();
        float intervalFraction = (fraction - prevFraction) /
            (mLastKeyframe.getFraction() - prevFraction);
        return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                mLastKeyframe.getValue());
    }
    //其他关键帧
    Keyframe prevKeyframe = mFirstKeyframe;
    for (int i = 1; i < mNumKeyframes; ++i) {
        Keyframe nextKeyframe = mKeyframes.get(i);
        if (fraction < nextKeyframe.getFraction()) {
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            final float prevFraction = prevKeyframe.getFraction();
            float intervalFraction = (fraction - prevFraction) /
                (nextKeyframe.getFraction() - prevFraction);
            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                    nextKeyframe.getValue());
        }
        prevKeyframe = nextKeyframe;
    }
    // shouldn't reach here
    return mLastKeyframe.getValue();
}

动画值最终是通过KeyFrameSet来计算的,我们通过of方法构造的KeyFrameSet,这里通过fraction来计算最终的动画值,fraction可能是经过插值器计算后的,对于关键帧数为2的最简单,直接通过估值器计算即可,startValue就是第一个关键帧的值,endValue就是最后一个关键帧。而当关键帧数目大于2的时候,需要这么计算,这里我举个例子比如ofInt(1,100,200),关键帧的数目为3,对应的KeyFrame分别为[0f,1],[0.5f,100],[1f,200]这里的0f,0.5f,1f分别是keyFrame对应的fraction。在getValue中根据fraction计算动画值时:

  1. 如果此时fraction小于0.5,则根据估值器进行如下计算evaluate(intervalFraction,0,100);
  2. 如果fraction大于0.5则进行如下计算evaluate(intervalFraction,100,200)

ObjectAnimation

ObjectAnimator是ValueAnimation的子类,它实际上同ValueAnimation的区别在于,ObjectAnimation包含一个Target和PropertyName,可以在该Target上的Property上应用动画,这个Target不仅仅可以是View。只要Target在该PropertyName上具有setter和getter方法即可。在ObjectAnimation中重载了initAnimation和animateValue方法

@Override
void initAnimation() {
    if (!mInitialized) {
        // mValueType may change due to setter/getter setup; do this before calling super.init(),
        // which uses mValueType to set up the default type evaluator.
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].setupSetterAndGetter(mTarget);
        }
        super.initAnimation();
    }
}

ObjectAnimation相比ValueAnimation会调用PropertyValuesHolder的setupSetterAndGetter方法,从名称上看应该是建立Target的对应属性的setter和getter方法。

void setupSetterAndGetter(Object target) {
    ……
    Class targetClass = target.getClass();
    if (mSetter == null) {
        setupSetter(targetClass);//获取mSetter方法
    }
    for (Keyframe kf : mKeyframeSet.mKeyframes) {
        if (!kf.hasValue()) {
            if (mGetter == null) {
                setupGetter(targetClass);
                if (mGetter == null) {
                    // Already logged the error - just return to avoid NPE
                    return;
                }
            }
            try {
                kf.setValue(mGetter.invoke(target));
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }
}
// 获取target对应属性的set方法
void setupSetter(Class targetClass) {
    mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType);
}

//获取target对应属性的get方法
private void setupGetter(Class targetClass) {
    mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
}

// PropertyValuesHolder.java
private Method setupSetterOrGetter(Class targetClass,
        HashMap<Class, HashMap<String, Method>> propertyMapMap,
        String prefix, Class valueType) {
    Method setterOrGetter = null;
    try {
        // Have to lock property map prior to reading it, to guard against
        // another thread putting something in there after we've checked it
        // but before we've added an entry to it
        mPropertyMapLock.writeLock().lock();
        HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
        if (propertyMap != null) {
            setterOrGetter = propertyMap.get(mPropertyName);//从缓存中取
        }
        if (setterOrGetter == null) {//没有的话,就从target中取
            setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
            if (propertyMap == null) {
                propertyMap = new HashMap<String, Method>();
                propertyMapMap.put(targetClass, propertyMap);
            }
            propertyMap.put(mPropertyName, setterOrGetter);
        }
    } finally {
        mPropertyMapLock.writeLock().unlock();
    }
    return setterOrGetter;
}

setupSetterOrGetter方法会通过getPropertyFunction获取属性对应的get或者set方法,获取到以后缓存到propertyMapMap中,propertyMapMap以target实例为key,以一个Map<String,Method>为value。这个Map是用于保存属性对应的Method,其中key就是属性的名称。

//ObjectAnimator.java
@Override
void animateValue(float fraction) {
    super.animateValue(fraction);
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].setAnimatedValue(mTarget);
    }
}
//PropertyValuesHolder.java
void setAnimatedValue(Object target) {
    if (mProperty != null) {
        mProperty.set(target, getAnimatedValue());
    }
    if (mSetter != null) {
        try {
            mTmpValueArray[0] = getAnimatedValue();
            mSetter.invoke(target, mTmpValueArray);
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }
}

ObjectAnimator的animateValue和ValueAnmiation的实现是不同的,ObjectAnimator会通过PropertyValuesHolder的setAnimatedValue方法为Target设置属性值。