NineoldAndroids动画库源码分析

简介

做Android开发的同学很多都知道或者使用过一个动画库,那就是NineOldAndroids,它的作者及其牛X,好几个著名的开源库都是他的作品,具体大家可以看他的JakeWharton。简单来说,NineOldAndroids是一个向下兼容的动画库,主要是使低于API 11的系统也能够使用View的属性动画。以下是个其官网的简述 :

Android library for using the Honeycomb (Android 3.0) animation API on all versions of the platform back to 1.0!Animation prior to Honeycomb was very limited in what it could accomplish so in Android 3.x a new API was written. With only a change in imports, we are able to use a large subset of the new-style animation with exactly the same API.

View的属性动画在Android API 11及其以后才支持,该库的作用就是让API 11以下的系统也能够正常的使用属性动画。它的类名、用法与官方的都一样,只是包名不一样。使用该库,你就可以在API 版本很低的情况下也能够使用各种属性动画,让你的应用更加有动感、平滑。 官方地址 : nineoldandroids

基本原理简介

一般来说,我们使用NineOldAndroids的属性动画时的代码大致是如下这样的:

ValueAnimator colorAnim = ObjectAnimator.ofFloat(myView, "scaleX", 0.3f);
colorAnim.setDuration(1000);
colorAnim.start();

这个动画会将myView (View的子类型)的宽度在1秒钟之内缩放到原始宽度的30%。下面我们先来简单说明一下NineOldAndroids的基本原理。

不管是官方的支持,还是nideoldandroids的低版本支持,它们使用的技术原理都是一样的。NineOldAndroids的基本原理就是在构建属性动画时根据用户的系统版本来选择不同的实现方式,并且对于低版本的API使用自己的实现来做属性动画。如果用户的系统API大于等于11,即Android 3.0及其以上,那么就会在动画的duration期间内连续地通过反射来调用该属性的set方法来修改它的值。例如上面的scaleX属性,该动画库会在内部构造scaleX的set方法,格式如下为set + 首字母大写属性名 + 参数,例如setScaleX(float scale),这样在一段时间内连续的修改myView的缩放值就达到了动画的效果。我们看setScaleX的文档:

如果用户的系统低于API 11,那么就不会调用View的set属性方法来修改它的属性,而是通过矩阵(Matrix)来修改View的缩放、平移、旋转等动画。关于矩阵的一些支持请参考相关的资料。Android中图像变换Matrix的原理、代码验证和应用(一)Android Matrix理论与应用详解Android--Matrix图片变换处理

基本原理了解以后我们就来看看它的实现吧。

源码分析

首先我们从它的入口,即ObjectAnimator入手,以上文中的scaleX为例来分析吧。

 /**
     * Constructs and returns an ObjectAnimator that animates between float values. A single
     * value implies that that value is the one being animated to. Two values imply a starting
     * and ending values. More than two values imply a starting value, values to animate through
     * along the way, and an ending value (these values will be distributed evenly across
     * the duration of the animation).
     *
     * @param target The object whose property is to be animated. This object should
     * have a public method on it called <code>setName()</code>, where <code>name</code> is
     * the value of the <code>propertyName</code> parameter.
     * @param propertyName The name of the property being animated.
     * @param values A set of values that the animation will animate between over time.
     * @return An ObjectAnimator object that is set up to animate between the given values.
     */
    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
    	// 1、构建属性动画对象, target为你要执行的动画的view,必须为View的子类, propertyName你要修改的属性名称.
    	// values是一个可变参数,如果是一个参数,那么该参数为目标值;如果是两个参数那么一个是起始值,一个是目标值;如果多余两个
    	// 参数,那么是起始值、动画要经过的几个值、目标值。
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        // 2、设置属性值
        anim.setFloatValues(values);
        Log.d(propertyName, "### 属性名称 : " + propertyName) ;
        return anim;
    }

	// 3、设置属性的目标值值
        @Override
    public void setFloatValues(float... values) {
    	// mValues是动画的各个数值的集合,The property/value sets being animated,声明如下 : PropertyValuesHolder[] mValues;
        if (mValues == null || mValues.length == 0) {
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }

首先通过ofFloat这个工厂方法来创建属性动画,设置它的属性名,然后调用setFloatValues来设置它的目标值。我们看到setFloatValues出现了一个类PropertyValuesHolder,这个类是该动画库的一个核心类之一。它的作用就是保存属性的名称和它的setter, getter方法,以及它的目标值。我们看看它的一些相关函数吧。

/**
 * This class holds information about a property and the values that that property
 * should take on during an animation. PropertyValuesHolder objects can be used to create
 * animations with ValueAnimator or ObjectAnimator that operate on several different properties
 * in parallel.
 */
public class PropertyValuesHolder implements Cloneable {

    /**
     * The name of the property associated with the values. This need not be a real property,
     * unless this object is being used with ObjectAnimator. But this is the name by which
     * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator.
     * 属性名称
     */
    String mPropertyName;

    /**
     * 属性
     */
    protected Property mProperty;

    /**
     * The setter function, if needed. ObjectAnimator hands off this functionality to
     * PropertyValuesHolder, since it holds all of the per-property information. This
     * property is automatically
     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
     * 属性的setter方法
     */
    Method mSetter = null;

    /**
     * The getter function, if needed. ObjectAnimator hands off this functionality to
     * PropertyValuesHolder, since it holds all of the per-property information. This
     * property is automatically
     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
     * The getter is only derived and used if one of the values is null. 属性的getter方法
     */
    private Method mGetter = null;

    /**
     * The type of values supplied. This information is used both in deriving the setter/getter
     * functions and in deriving the type of TypeEvaluator.   属性的类ing,比如float, int等。
     */
    Class mValueType;

    /**
     * The set of keyframes (time/value pairs) that define this animation.
     * 这里是动画关键帧的即可,即在duration时间内的动画帧集合,它保存的是在每个时刻该该属性对应的值。
     */
    KeyframeSet mKeyframeSet = null;

    /**
     * Constructs and returns a PropertyValuesHolder with a given property and
     * set of float values.
     * @param property The property being animated. Should not be null.
     * @param values The values that the property will animate between.
     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
     */
    public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
        // 1、构建的是FloatPropertyValuesHolder
        return new FloatPropertyValuesHolder(property, values);
    }

    // 内部类, Float类型的PropertyValuesHolder
    static class FloatPropertyValuesHolder extends PropertyValuesHolder {

        // Cache JNI functions to avoid looking them up twice
        //private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap =
        //        new HashMap<Class, HashMap<String, Integer>>();
        //int mJniSetter;
        private FloatProperty mFloatProperty;// float型属性

        FloatKeyframeSet mFloatKeyframeSet;// 动画的关键帧,这个是重点
        float mFloatAnimatedValue;

        public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) {
            super(propertyName);
            mValueType = float.class;
            mKeyframeSet = keyframeSet;
            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
        }

        public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) {
            super(property);
            mValueType = float.class;
            mKeyframeSet = keyframeSet;
            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
            if (property instanceof FloatProperty) {
                mFloatProperty = (FloatProperty) mProperty;
            }
        }

        public FloatPropertyValuesHolder(String propertyName, float... values) {
            super(propertyName);
            setFloatValues(values);
        }

        // 2、构造函数
        public FloatPropertyValuesHolder(Property property, float... values) {
            super(property);
            // 设置目标属性值
            setFloatValues(values);
            if (property instanceof  FloatProperty) {
                mFloatProperty = (FloatProperty) mProperty;
            }
        }

        // 3、设置动画的目标值
        @Override
        public void setFloatValues(float... values) {
            // 4、调用了父类的方法
            super.setFloatValues(values);
            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;// 5、获取动画关键帧
        }

        // 计算当前的动画值
        @Override
        void calculateValue(float fraction) {
            mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction);
        }

        // 代码省略
        }

}

该类是属性和属性值的辅助类,它保存了属性的名称、setter、getter、以及该属性在duration时间段内各个时刻对应属性数值( mKeyframeSet )。这样,当执行动画时,动画库只需要根据动画的执行时间,然后到mKeyframeSet中查询这个时刻对应的属性值,然后修改View的这个属性,连续的这样操作即可达到动画的效果。在这个例子中,我们的属性是scaleX,目标属性值是0.3f。因此,对应的属性类为FloatPropertyValuesHolder,还有一种是IntPropertyValuesHolder,这都很好理解,就不多说。
这个例子中,我们会调用注释2中的构造函数,然后该函数会调用setFloatValues来设置动画的目标值,然后会到达注释3处的函数。在这个函数中调用了父类中对应的方法,然后就获取到了动画的关键帧。那么很可能计算各个时刻的属性值的操作放在了父类 (即 PropertyValuesHolder ) 的setFloatValues函数中。我们一起来看看吧。

    /**
     * Set the animated values for this object to this set of floats.
     * If there is only one value, it is assumed to be the end value of an animation,
     * and an initial value will be derived, if possible, by calling a getter function
     * on the object. Also, if any value is null, the value will be filled in when the animation
     * starts in the same way. This mechanism of automatically getting null values only works
     * if the PropertyValuesHolder object is used in conjunction
     * {@link ObjectAnimator}, and with a getter function
     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
     * no way of determining what the value should be.
     *
     * @param values One or more values that the animation will animate between.
     */
    public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframeSet = KeyframeSet.ofFloat(values);
    }

可以看到该函数又调用了KeyFrameSet的ofFloat方法,继续看 :

class KeyframeSet {

    int mNumKeyframes;

    Keyframe mFirstKeyframe;
    Keyframe mLastKeyframe;
    /*Time*/Interpolator mInterpolator; // only used in the 2-keyframe case
    ArrayList<Keyframe> mKeyframes; // only used when there are not 2 keyframes
    TypeEvaluator mEvaluator;

    public KeyframeSet(Keyframe... keyframes) {
        mNumKeyframes = keyframes.length;
        mKeyframes = new ArrayList<Keyframe>();
        mKeyframes.addAll(Arrays.asList(keyframes));
        mFirstKeyframe = mKeyframes.get(0);
        mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
        mInterpolator = mLastKeyframe.getInterpolator();
    }

    // 1、关键帧的计算就在这里。如果用户设置了一个目标值,那么这个值就是最终的值,它的起始值会被默认设置为0。如果用户设置了大于等于两个目标值,那只有两个值有效。并且第一个值会是起始值,第二个值就是目标值。

    public static KeyframeSet ofFloat(float... values) {
        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]);
        } 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]);
            }
        }
        return new FloatKeyframeSet(keyframes);
    }

}

此时,我们就计算好了各个时刻的属性值了。设置完属性以后,我们就会调用start()方法启动动画了。我们看看ObjectAnimator的start()方法吧。

    @Override
    public void start() {
        if (DBG) {
            Log.d("ObjectAnimator", "Anim target, duration: " + mTarget + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                ArrayList<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes;
                Log.d("ObjectAnimator", "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " +
                    keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue());
            }
        }
        super.start();
    }

恩,这支持调用了父类的start()方法,它的父类是ValueAnimator,我们看看该类的start()方法吧。

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

        /**
     * Start the animation playing. This version of start() takes a boolean flag
     * that indicates whether the animation should play in reverse. The flag is
     * usually false, but may be set to true if called from the reverse()
     * method.
     * <p>
     * The animation started by calling this method will be run on the thread
     * that called this method. This thread should have a Looper on it (a
     * runtime exception will be thrown if this is not the case). Also, if the
     * animation will animate properties of objects in the view hierarchy, then
     * the calling thread should be the UI thread for that view hierarchy.
     * </p>
     *
     * @param playBackwards Whether the ValueAnimator should start playing in
     *            reverse.(逆向执行动画)
     */
    private void start(boolean playBackwards) {
    	// 判断Looper是否为空,这里的Looper是UI线程的Looper
        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;
        // 将该动画加入到等待执行的动画队列中
        sPendingAnimations.get().add(this);
        // 是否延时
        if (mStartDelay == 0) {
            // This sets the initial value of the animation, prior to actually
            // starting it running
            setCurrentPlayTime(getCurrentPlayTime());// 设置动画的开始执行时间,因为动画会有一个duration
            // 这个开始执行时间 + duration就是结束时间.
            mPlayingState = STOPPED;
            mRunning = true;
            // 田勇一些监听器
            if (mListeners != null) {
                ArrayList<AnimatorListener> tmpListeners =
                        (ArrayList<AnimatorListener>) mListeners.clone();
                int numListeners = tmpListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    tmpListeners.get(i).onAnimationStart(this);
                }
            }
        }
        AnimationHandler animationHandler = sAnimationHandler.get();
        if (animationHandler == null) {
            animationHandler = new AnimationHandler();
            sAnimationHandler.set(animationHandler);
        }
        // 发送一个动画启动的消息给AnimationHandler
        animationHandler.sendEmptyMessage(ANIMATION_START);
    }

我们看到,启动动画的操作设置了一些基本参数,然后把自己添加到待执行的动画列表( sPendingAnimations )中。然后通过AnimationHandler发送了一个ANIMATION_START消息,真是哪里都有Handler的事啊!我们继续看吧。

 /**
     * 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.
     */
    private static class AnimationHandler extends Handler {
        /**
         * There are only two messages that we care about: ANIMATION_START and
         * ANIMATION_FRAME. The START message is sent when an animation‘s
         * start() method is called. It cannot start synchronously when start()
         * is called because the call may be on the wrong thread, and it would
         * also not be synchronized with other animations because it would not
         * start on a common timing pulse. So each animation sends a START
         * message to the handler, which causes the handler to place the
         * animation on the active animations queue and start processing frames
         * for that animation. The FRAME message is the one that is sent over
         * and over while there are any active animations to process.
         */
        @Override
        public void handleMessage(Message msg) {
            boolean callAgain = true;
            // 正要执行的动画列表
            ArrayList<ValueAnimator> animations = sAnimations.get();
            // 需要delay执行的动画列表
            ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();
            switch (msg.what) {
            // TODO: should we avoid sending frame message when starting if we
            // were already running?
                case ANIMATION_START:
                	// 还未执行的动画列表
                    ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get();
                    if (animations.size() > 0 || delayedAnims.size() > 0) {
                        callAgain = false;
                    }
                    // pendingAnims holds any animations that have requested to
                    // be started
                    // We‘re going to clear sPendingAnimations, but starting
                    // animation may
                    // cause more to be added to the pending list (for example,
                    // if one animation
                    // starting triggers another starting). So we loop until
                    // sPendingAnimations
                    // is empty.
                    // 执行完正在等待执行的动画列表,我们当前的动画在start()方法中就是添加到这个列表中的。
                    // 即这一行, sPendingAnimations.get().add(this);
                    while (pendingAnimations.size() > 0) {
                        ArrayList<ValueAnimator> pendingCopy =
                                (ArrayList<ValueAnimator>) pendingAnimations.clone();
                        pendingAnimations.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();
                            } else {
                            	// 加入到延迟执行的动画列表中
                                delayedAnims.add(anim);
                            }
                        }
                    }
                    // fall through to process first frame of new animations
                case ANIMATION_FRAME:
                    // currentTime holds the common time for all animations
                    // processed
                    // during this frame
                    long currentTime = AnimationUtils.currentAnimationTimeMillis();
                    ArrayList<ValueAnimator> readyAnims = sReadyAnims.get();
                    ArrayList<ValueAnimator> endingAnims = sEndingAnims.get();

                    // First, process animations currently sitting on the
                    // delayed queue, adding
                    // them to the active animations if they are ready
                    int numDelayedAnims = delayedAnims.size();
                    for (int i = 0; i < numDelayedAnims; ++i) {
                        ValueAnimator anim = delayedAnims.get(i);
                        if (anim.delayedAnimationFrame(currentTime)) {
                            readyAnims.add(anim);
                        }
                    }
                    int numReadyAnims = readyAnims.size();
                    if (numReadyAnims > 0) {
                        for (int i = 0; i < numReadyAnims; ++i) {
                            ValueAnimator anim = readyAnims.get(i);
                            anim.startAnimation();
                            anim.mRunning = true;
                            delayedAnims.remove(anim);
                        }
                        readyAnims.clear();
                    }

                    // Now process all active animations. The return value from
                    // animationFrame()
                    // tells the handler whether it should now be ended
                    int numAnims = animations.size();
                    int i = 0;
                    while (i < numAnims) {
                        ValueAnimator anim = animations.get(i);
                        if (anim.animationFrame(currentTime)) {
                            endingAnims.add(anim);
                        }
                        if (animations.size() == numAnims) {
                            ++i;
                        } else {
                                        --numAnims;
                            endingAnims.remove(anim);
                        }
                    }
                    if (endingAnims.size() > 0) {
                        for (i = 0; i < endingAnims.size(); ++i) {
                            endingAnims.get(i).endAnimation();
                        }
                        endingAnims.clear();
                    }

                    // If there are still active or delayed animations, call the
                    // handler again
                    // after the frameDelay
                    if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) {
                        sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -
                                (AnimationUtils.currentAnimationTimeMillis() - currentTime)));
                    }
                    break;
            }
        }

该AnimationHandler简单来说就是处理动画的启动和各个关键帧动画的执行,在ANIMATION_START阶段,将sPendingAnimations中的动画取出,并且根据当前时间和动画的执行时间对比,将一些需要延迟的动画放到delay列表中,将不需要延迟的动画立即执行。动画开始执行后,并没有break出来,而是直接到ANIMATION_FRAME阶段,在这个阶段,会遍历已经ready的动画列表和delay的动画列表,并且根据当前时间与动画的执行时间进行比对,如果到了动画的执行时间,那么执行动画。然后才是遍历sAnimations中的动画,其中的动画就是立马可以执行的动画,最后如果动画列表中的动画没有被执行完成,则会发送一个ACTION_FRAME消息,使得又进入到ANIMATION_FRAME这个处理流程,然后又是上述的动画执行过程,直到动画全部执行完毕。sAnimations等列表的它的声明如下。

    // The per-thread list of all active animations,所有可以马上执行的动画
    private static final ThreadLocal<ArrayList<ValueAnimator>> sAnimations =
            new ThreadLocal<ArrayList<ValueAnimator>>() {
                @Override
                protected ArrayList<ValueAnimator> initialValue() {
                    return new ArrayList<ValueAnimator>();
                }
            };

    // The per-thread set of animations to be started on the next animation
    // frame    所有未被处理的动画
    private static final ThreadLocal<ArrayList<ValueAnimator>> sPendingAnimations =
            new ThreadLocal<ArrayList<ValueAnimator>>() {
                @Override
                protected ArrayList<ValueAnimator> initialValue() {
                    return new ArrayList<ValueAnimator>();
                }
            };

那么我们的动画是什么时候添加到这个sAnimations列表中的呢 ?
我们看到,在ANIMATION_START中,对于没有延时的动画,调用了一个anim.startAnimation()方法,我们看看它做了些什么操作吧。

    /**
     * Called internally to start an animation by adding it to the active
     * animations list. Must be called on the UI thread.
     */
    private void startAnimation() {
        initAnimation();// 1、初始化动画
        sAnimations.get().add(this);// 2、将动画添加到sAnimation中
        if (mStartDelay > 0 && mListeners != null) {
            // Listeners were already notified in start() if startDelay is 0;
            // this is
            // just for delayed animations
            ArrayList<AnimatorListener> tmpListeners =
                    (ArrayList<AnimatorListener>) mListeners.clone();
            int numListeners = tmpListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                tmpListeners.get(i).onAnimationStart(this);// 3、回调动画开始的hook
            }
        }
    }

可以看到,进行动画初始化之后,在startAnimation中确实将该动画添加到sAnimations中了,并且执行了相应的回调方法。

我们看看ObjectAnimator的initAnimation函数到底做了些什么吧。

/**
     * This function is called immediately before processing the first animation
     * frame of an animation. If there is a nonzero <code>startDelay</code>, the
     * function is called after that delay ends.
     * It takes care of the final initialization steps for the
     * animation. This includes setting mEvaluator, if the user has not yet
     * set it up, and the setter/getter methods, if the user did not supply
     * them.
     *
     *  <p>Overriders of this method should call the superclass method to cause
     *  internal mechanisms to be set up correctly.</p>
     */
    @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.   注意这里,很重要
            if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
                setProperty(PROXY_PROPERTIES.get(mPropertyName));
                Log.d("", "### 需要包装") ;
            }
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].setupSetterAndGetter(mTarget);
            }
            super.initAnimation();
        }
    }

我们看到initAnimation中有个很长的if判断,这个判断就是对系统版本进行判断的地方。如果系统版本小于API 11那么,AnimatorProxy.NEEDS_PROXY为真。我们看看它的声明。

    /** Whether or not the current running platform needs to be proxied. */
    public static final boolean NEEDS_PROXY = Integer.valueOf(Build.VERSION.SDK).intValue() < Build.VERSION_CODES.HONEYCOMB;

可以看到当Android SDK的版本号小于HONEYCOMB,NEEDS_PROXY就为真,HONEYCOMB就是系统3.0的代号,即API 11的代号。

如果NEEDS_PROXY为真,且属性是合法的属性,那么就会进入到if中,即会调用,

setProperty(PROXY_PROPERTIES.get(mPropertyName)); 在这个例子中,我们的属性是scaleX,可以看到,PROXY_PROPERTIES会get一个key为mPropertyName的值,这个mPropertyName就是我们的传递进来的scaleX属性。我们看看PROXY_PROPERTIES是什么吧。

private static final Map<String, Property> PROXY_PROPERTIES = new HashMap<String, Property>();

    static {
        PROXY_PROPERTIES.put("alpha", PreHoneycombCompat.ALPHA);
        PROXY_PROPERTIES.put("pivotX", PreHoneycombCompat.PIVOT_X);
        PROXY_PROPERTIES.put("pivotY", PreHoneycombCompat.PIVOT_Y);
        PROXY_PROPERTIES.put("translationX", PreHoneycombCompat.TRANSLATION_X);
        PROXY_PROPERTIES.put("translationY", PreHoneycombCompat.TRANSLATION_Y);
        PROXY_PROPERTIES.put("rotation", PreHoneycombCompat.ROTATION);
        PROXY_PROPERTIES.put("rotationX", PreHoneycombCompat.ROTATION_X);
        PROXY_PROPERTIES.put("rotationY", PreHoneycombCompat.ROTATION_Y);
        PROXY_PROPERTIES.put("scaleX", PreHoneycombCompat.SCALE_X);
        PROXY_PROPERTIES.put("scaleY", PreHoneycombCompat.SCALE_Y);
        PROXY_PROPERTIES.put("scrollX", PreHoneycombCompat.SCROLL_X);
        PROXY_PROPERTIES.put("scrollY", PreHoneycombCompat.SCROLL_Y);
        PROXY_PROPERTIES.put("x", PreHoneycombCompat.X);
        PROXY_PROPERTIES.put("y", PreHoneycombCompat.Y);
    }

嗯哼,这是一个map,我们传进来的是scaleX,那么我们get得到的就应该是PreHoneycombCompat.SCALE_X,继续看这货是什么吧。

 static Property<View, Float> SCALE_X = new FloatProperty<View>("scaleX") {
        @Override
        public void setValue(View object, float value) {
            AnimatorProxy.wrap(object).setScaleX(value);
        }

        @Override
        public Float get(View object) {
            return AnimatorProxy.wrap(object).getScaleX();
        }
    };

啊,原来就是包装了一下view,并且在setValue时通过它的包装对它调用setScaleX函数。那么AnimatorProxy又是什么呢?其实它就是低于API 11时对View进行包装从而通过矩阵来达到View的缩放、平移、翻转等效果的动画代理类。我们贴上部分代码吧。

/**
 * A proxy class to allow for modifying post-3.0 view properties on all pre-3.0
 * platforms. <strong>DO NOT</strong> wrap your views with this class if you are
 * using {@code ObjectAnimator} as it will handle that itself.
 */
public final class AnimatorProxy extends Animation {

    private AnimatorProxy(View view) {
        setDuration(0); // perform transformation immediately
        setFillAfter(true); // persist transformation beyond duration
        view.setAnimation(this);
        mView = new WeakReference<View>(view);
    }

    public void setScaleX(float scaleX) {
        Log.d("", "### setScaleX  --> " + this.getClass().getSimpleName() + ", scaleX = " + scaleX);
        if (mScaleX != scaleX) {
            prepareForUpdate();
            mScaleX = scaleX;
            invalidateAfterUpdate();
        }
    }

     private void prepareForUpdate() {
        View view = mView.get();
        if (view != null) {
            computeRect(mBefore, view);
        }
    }

    private void invalidateAfterUpdate() {
        View view = mView.get();
        if (view == null || view.getParent() == null) {
            return;
        }

        final RectF after = mAfter;
        computeRect(after, view);
        after.union(mBefore);

        ((View) view.getParent()).invalidate(
                (int) Math.floor(after.left),
                (int) Math.floor(after.top),
                (int) Math.ceil(after.right),
                (int) Math.ceil(after.bottom));
    }

    private void computeRect(final RectF r, View view) {
        // compute current rectangle according to matrix transformation
        final float w = view.getWidth();
        final float h = view.getHeight();

        // use a rectangle at 0,0 to make sure we don‘t run into issues with
        // scaling
        r.set(0, 0, w, h);

        final Matrix m = mTempMatrix;
        m.reset();
        transformMatrix(m, view);
        mTempMatrix.mapRect(r);

        r.offset(view.getLeft(), view.getTop());

        // Straighten coords if rotations flipped them
        if (r.right < r.left) {
            final float f = r.right;
            r.right = r.left;
            r.left = f;
        }
        if (r.bottom < r.top) {
            final float f = r.top;
            r.top = r.bottom;
            r.bottom = f;
        }
    }

    private void transformMatrix(Matrix m, View view) {
        final float w = view.getWidth();
        final float h = view.getHeight();
        final boolean hasPivot = mHasPivot;
        final float pX = hasPivot ? mPivotX : w / 2f;
        final float pY = hasPivot ? mPivotY : h / 2f;

        final float rX = mRotationX;
        final float rY = mRotationY;
        final float rZ = mRotationZ;
        if ((rX != 0) || (rY != 0) || (rZ != 0)) {
            final Camera camera = mCamera;
            camera.save();
            camera.rotateX(rX);
            camera.rotateY(rY);
            camera.rotateZ(-rZ);
            camera.getMatrix(m);
            camera.restore();
            m.preTranslate(-pX, -pY);
            m.postTranslate(pX, pY);
        }

        final float sX = mScaleX;
        final float sY = mScaleY;
        if ((sX != 1.0f) || (sY != 1.0f)) {
            m.postScale(sX, sY);
            final float sPX = -(pX / w) * ((sX * w) - w);
            final float sPY = -(pY / h) * ((sY * h) - h);
            m.postTranslate(sPX, sPY);
        }

        m.postTranslate(mTranslationX, mTranslationY);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        View view = mView.get();
        if (view != null) {
            t.setAlpha(mAlpha);
            transformMatrix(t.getMatrix(), view);
        }
    }
}

很明显了,就是通过矩阵变换达到动画的效果,关于矩阵的使用和原理请参考文中给出的资料,这里不做赘述。
这个动画的duration为0,也就是瞬时动画,也就是说每次修改属性的就是立即完成。每次需要更新属性时,都会调用

FloatProperty的setValue方法,而setValue又调用AnimationProxy的setScaleX()方法,setScaleX在低于API 11时通过矩阵变换来完成操作。那么大于等于API 11时它又是怎样的呢?

我们看到,在initAnimation中,还有一个地方需要注意。

    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.
            if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
                setProperty(PROXY_PROPERTIES.get(mPropertyName));
                Log.d("", "### 需要包装") ;
            }
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].setupSetterAndGetter(mTarget);// 2、设置setter和getter方法
            }
            super.initAnimation();
        }
    }

看到注释2处,这里会初始化属性的setter和getter方法。通过反射调用这些函数就可以修改其属性值了。

那么在什么时候会调用相应的方法来更新属性值呢?更新属性值,我们想到的应该是ANIMATION_FRAME的那里,那里就是循环调用动画,直到动画结束。因此那应该是更新属性的地方。我们注意到一个地方,就是

 // Now process all active animations. The return value from
                    // animationFrame()
                    // tells the handler whether it should now be ended
                    int numAnims = animations.size();
                    int i = 0;
                    while (i < numAnims) {
                        ValueAnimator anim = animations.get(i);
                        if (anim.animationFrame(currentTime)) {// 这里有一个使用时间的判断,就是获取当前时间的属性值的地方
                            endingAnims.add(anim);
                        }
// 代码省略
}

我们看到animationFrame是个关键函数,继续分析吧。

 boolean animationFrame(long currentTime) {
        boolean done = false;

        if (mPlayingState == STOPPED) {
            mPlayingState = RUNNING;
            if (mSeekTime < 0) {
                mStartTime = currentTime;
            } else {
                mStartTime = currentTime - mSeekTime;
                // Now that we‘re playing, reset the seek time
                mSeekTime = -1;
            }
        }
        switch (mPlayingState) {
            case RUNNING:
            case SEEKED:
                float fraction = mDuration > 0 ? (float) (currentTime - mStartTime) / mDuration
                        : 1f;
                if (fraction >= 1f) {
                    if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
                        // Time to repeat
                        if (mListeners != null) {
                            int numListeners = mListeners.size();
                            for (int i = 0; i < numListeners; ++i) {
                                mListeners.get(i).onAnimationRepeat(this);
                            }
                        }
                        if (mRepeatMode == REVERSE) {
                            mPlayingBackwards = mPlayingBackwards ? false : true;
                        }
                        mCurrentIteration += (int) fraction;
                        fraction = fraction % 1f;
                        mStartTime += mDuration;
                    } else {
                        done = true;
                        fraction = Math.min(fraction, 1.0f);
                    }
                }
                if (mPlayingBackwards) {
                    fraction = 1f - fraction;
                }
                animateValue(fraction);//计算当前动画的属性值
                break;
        }

        return done;
    }

我们看看ObjectAnimator重的animateValue函数。

    /**
     * This method is called with the elapsed fraction of the animation during every
     * animation frame. This function turns the elapsed fraction into an interpolated fraction
     * and then into an animated value (from the evaluator. The function is called mostly during
     * animation updates, but it is also called when the <code>end()</code>
     * function is called, to set the final value on the property.
     *
     * <p>Overrides of this method must call the superclass to perform the calculation
     * of the animated value.</p>
     *
     * @param fraction The elapsed fraction of the animation.
     */
    @Override
    void animateValue(float fraction) {
        super.animateValue(fraction);// 计算属性值
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].setAnimatedValue(mTarget);// 更新属性值
        }
    }

我们在本例中,我们的PropertyValuesHolder为FloatPropertyValuesHolder类型,我们看看FloatPropertyValuesHolder中的setAnimatedValue方法。

 /**
         * Internal function to set the value on the target object, using the setter set up
         * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
         * to handle turning the value calculated by ValueAnimator into a value set on the object
         * according to the name of the property.
         * @param target The target object on which the value is set
         */
        @Override
        void setAnimatedValue(Object target) {
            if (mFloatProperty != null) {// 1、如果有float property则通过setValue来更新属性值
                mFloatProperty.setValue(target, mFloatAnimatedValue);
                return;
            }
            if (mProperty != null) {// 2、如果有属性,则通过setValue来更新
                mProperty.set(target, mFloatAnimatedValue);
                return;
            }
            //if (mJniSetter != 0) {
            //    nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
            //    return;
            //}
            if (mSetter != null) {// 3、否则通过反射来更新动画
                try {
                    mTmpValueArray[0] = mFloatAnimatedValue;
                    Log.d("", "### set 新值 : 方法名 : " + mSetter.getName() + ", value = " + mFloatAnimatedValue) ;
                    mSetter.invoke(target, mTmpValueArray);
                } catch (InvocationTargetException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                } catch (IllegalAccessException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                }
            }
        }

可以看到,更新方式主要有setValue和反射。在我们初始化动画时,我们就通过版本号来判断,并且设置Property,我们回顾一下。

 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.
            if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
                setProperty(PROXY_PROPERTIES.get(mPropertyName));// 1、小于API 11,设置属性
                Log.d("", "### 需要包装") ;
            }
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].setupSetterAndGetter(mTarget);
            }
            super.initAnimation();
        }
    }

可以看到,小于 API 11时会设置Property, 对于本例中的scaleX,这个Property为FloatProperty,因此会在setAnimationValue中执行注释1的代码,最后使用矩阵变换的形式变换View;否则会通过反射的形式更新动画。

这样,整个动画框架就完成了。

万万没想到,写博客写到这么晚~~~~~~ Good Night!

时间: 2024-10-06 01:25:19

NineoldAndroids动画库源码分析的相关文章

Facebook Rebound 弹性动画库 源码分析

Rebound源码分析 让动画不再僵硬:Facebook Rebound Android动画库介绍一文中介绍了rebound这个库. 对于想体验一下rebound的效果,又懒得clone和编译代码的,这里提供一个demo apk. 今天看到了tumblr发布了基于rebound的Backboard,本想直接分析一下Backboard对rebound做了些什么,不过考虑到rebound还没有仔细分析过,所以这里做一下源码分析. 对外部来说,首先接触的就是SpringSystem了,但在说它之前,先

打造简易NineoldAndroids动画库,深入理解Android动画原理

简介 NineoldAndroids是Github上一个著名的动画库,简单来说,NineOldAndroids是一个向下兼容的动画库,主要是使低于API 11的系统也能够使用View的属性动画. 网上已经有一些文章,介绍了这个库的设计,包括类结构和思想,例如 NineOldAnimations 源码解析 NineoldAndroids动画库源码分析 上面两篇文章都比较详细的介绍了NineoldAndroids的源码,可以说为大家看源码带来很大的方便. 那为什么我还要写这篇文章呢? 我们来看Nin

S5PV210-uboot源码分析-第一阶段

uboot源码分析1-启动第一阶段 1.starts.S是我们uboot源码的第一阶段: 从u-boot.lds链接脚本中也可以看出start.S是我们整个程序的入口处,怎么看出的呢,因为在链接脚本中有个ENTRY(_start)声明了_start是程序的入口.所以_start符号所在的文件,就是我们整个程序的起始文件,_start所在处的代码就是我们整个程序的起始代码. 2.我们知道了程序的入口是_start这个符号,但是却不知道是在哪一个文件中,所以要SI进行查找搜索,点击SI的大R进行搜索

UiAutomator源码分析之UiAutomatorBridge框架

上一篇文章<UIAutomator源码分析之启动和运行>我们描述了uitautomator从命令行运行到加载测试用例运行测试的整个流程,过程中我们也描述了UiAutomatorBridge这个类的重要性 ,说它相当于UiAutomation的代理(我们都知道UiAutomator是通过UiAutomation和AccessibilityService进行连接然后获取界面空间信息和注入事件的).那么今天开始我们就围绕这个类以及跟它有关系的类进行进一步的分析. 1. UiAutomatorBrid

Android源码分析之SparseArray

本来接下来应该分析MessageQueue了,可是我这几天正好在实际开发中又再次用到了SparseArray(之前有用到过一次,那次只是 大概浏览了下源码,没做深入研究),于是在兴趣的推动下,花了些时间深入研究了下,趁着记忆还是新鲜的,就先在这里分析了. MessageQueue的分析应该会在本周末给出. 和以往一样,首先我们来看看关键字段和ctor: private static final Object DELETED = new Object(); private boolean mGar

令人惊叹的HTML5动画及源码分析下载

HTML5非常酷,利用HTML5制作动画简直让我们忘记了这世界上还有flash的存在.今天我们要分享的一些HTML5动画都还不错,有些动画设计还是挺别出心裁的.另外,每一款HTML5动画都提供源代码下载,并且我们对源码作一些简单的分析. HTML5可爱的404页面动画 很逗的机器人 利用HTML5绘制的机器人,还会动哦.你可以将它作为一个富有创意的404页面. 核心CSS3代码: @-webkit-keyframes move { 0%, 100% { -webkit-transform: ro

jQuery-1.9.1源码分析系列完毕目录整理

jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二)jQuery选择器 jQuery-1.9.1源码分析系列(二)jQuery选择器续1 jQuery-1.9.1源码分析系列(二)jQuery选择器续2——筛选 jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——词法解析 jQuery-1.9.1源码分析系列(三) Sizzle选择

JDK源码分析之String篇

------------------------------String在内存中的存储情况(一下内容摘自参考资料1)----------------------------------- 前提:先了解下什么是声明,什么时候才算是产生了对象实例 其中x并未看到内存分配,变量在使用前必须先声明,再赋值,然后才可以使用.java基础数据类型会用对应的默认值进行初始化 一.首先看看Java虚拟机JVM的内存块及其变量.对象内存空间是怎么存储分配的 1.栈:存放基本数据类型及对象变量的引用,对象本身不存放

Retrofit源码分析以及MVP框架封装使用

阅读此文前请先阅读Retrofit+okhttp网络框架介绍 从上文中我们已经了解通过如下代码即可得到返回给我们call 以及 response对象,今天我们通过源码来分析这个过程是如何实现的. /** * 获取天气数据 * @param cityname * @param key * @return */ @GET("/weather/index") Call<WeatherData> getWeatherData(@Query("format") S