Android源码分析—属性动画的工作原理

本文深入分析Android属性动画的工作原理,从源码层面解析属性动画如何利用get和set方法更新动画对象属性值,帮助开发者更好地掌握属性动画的应用。
转载请注明出处: http://blog.youkuaiyun.com/singwhatiwanna/article/details/17853275

前言

本文为Android动画系列的最后一篇文章,通过对源码的分析,能够让大家更深刻地理解属性动画的工作原理,这有助于我们更好地使用属性动画。但是,由于动画的底层实现已经深入到jni层,并且涉及到显示子系统,因此,深入地分析动画的底层实现不仅比较困难而且意义不大,因此,本文的分析到jni层为止。

Android动画系列:

android动画简介

Android动画进阶—使用开源动画库nineoldandroids

Android属性动画深入分析:让你成为动画牛人

Android源码分析—属性动画的工作原理

属性动画的原理

属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你传递的该熟悉的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get方法,因为系统要去拿属性的初始值。对于属性动画来说,其动画过程中所做的就是这么多,下面看源码分析。

源码分析

首先我们要找一个入口,就从ObjectAnimator.ofInt(mButton, “width”, 500).setDuration(5000).start()开始吧,其他动画都是类似的。

看ObjectAnimator的start方法

  1. @Override  
  2. public void start() {  
  3.     // See if any of the current active/pending animators need to be canceled  
  4.     AnimationHandler handler = sAnimationHandler.get();  
  5.     if (handler != null) {  
  6.         int numAnims = handler.mAnimations.size();  
  7.         for (int i = numAnims - 1; i >= 0; i–) {  
  8.             if (handler.mAnimations.get(i) instanceof ObjectAnimator) {  
  9.                 ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);  
  10.                 if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {  
  11.                     anim.cancel();  
  12.                 }  
  13.             }  
  14.         }  
  15.         numAnims = handler.mPendingAnimations.size();  
  16.         for (int i = numAnims - 1; i >= 0; i–) {  
  17.             if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {  
  18.                 ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);  
  19.                 if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {  
  20.                     anim.cancel();  
  21.                 }  
  22.             }  
  23.         }  
  24.         numAnims = handler.mDelayedAnims.size();  
  25.         for (int i = numAnims - 1; i >= 0; i–) {  
  26.             if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {  
  27.                 ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);  
  28.                 if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {  
  29.                     anim.cancel();  
  30.                 }  
  31.             }  
  32.         }  
  33.     }  
  34.     if (DBG) {  
  35.         Log.d(”ObjectAnimator”“Anim target, duration: ” + mTarget + “, ” + getDuration());  
  36.         for (int i = 0; i < mValues.length; ++i) {  
  37.             PropertyValuesHolder pvh = mValues[i];  
  38.             ArrayList<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes;  
  39.             Log.d(”ObjectAnimator”“   Values[“ + i + “]: ” +  
  40.                 pvh.getPropertyName() + ”, ” + keyframes.get(0).getValue() + “, ” +  
  41.                 keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue());  
  42.         }  
  43.     }  
  44.     super.start();  
  45. }  
    @Override
    public void start() {
        // See if any of the current active/pending animators need to be canceled
        AnimationHandler handler = sAnimationHandler.get();
        if (handler != null) {
            int numAnims = handler.mAnimations.size();
            for (int i = numAnims - 1; i >= 0; i--) {
                if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
                    ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                        anim.cancel();
                    }
                }
            }
            numAnims = handler.mPendingAnimations.size();
            for (int i = numAnims - 1; i >= 0; i--) {
                if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
                    ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                        anim.cancel();
                    }
                }
            }
            numAnims = handler.mDelayedAnims.size();
            for (int i = numAnims - 1; i >= 0; i--) {
                if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
                    ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                        anim.cancel();
                    }
                }
            }
        }
        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();
    }
说明:上面的代码别看那么长,其实做的事情很简单,首先会判断一下,如果当前动画、等待的动画(Pending)和延迟的动画(Delay)中有和当前动画相同的动画,那么就把相同的动画给取消掉,接下来那一段是log,再接着就调用了父类的super.start()方法, 因为ObjectAnimator继承了ValueAnimator,所以接下来我们看一下ValueAnimator的Start方法

  1. private void start(boolean playBackwards) {  
  2.     if (Looper.myLooper() == null) {  
  3.         throw new AndroidRuntimeException(“Animators may only be run on Looper threads”);  
  4.     }  
  5.     mPlayingBackwards = playBackwards;  
  6.     mCurrentIteration = 0;  
  7.     mPlayingState = STOPPED;  
  8.     mStarted = true;  
  9.     mStartedDelay = false;  
  10.     mPaused = false;  
  11.     AnimationHandler animationHandler = getOrCreateAnimationHandler();  
  12.     animationHandler.mPendingAnimations.add(this);  
  13.     if (mStartDelay == 0) {  
  14.         // This sets the initial value of the animation, prior to actually starting it running  
  15.         setCurrentPlayTime(0);  
  16.         mPlayingState = STOPPED;  
  17.         mRunning = true;  
  18.         notifyStartListeners();  
  19.     }  
  20.     animationHandler.start();  
  21. }  
    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();
    }
说明:上述代码最终会调用AnimationHandler的start方法,这个AnimationHandler并不是Handler,它是个Runnable。看下它的代码,通过代码我们发现,很快就调到了jni层,不过jni层最终还是要调回来的。它的run方法会被调用,这个 Runnable涉及到和底层的交互,我们就忽略这部分,直接看重点:ValueAnimator中的doAnimationFrame方法

  1. final boolean doAnimationFrame(long frameTime) {  
  2.     if (mPlayingState == STOPPED) {  
  3.         mPlayingState = RUNNING;  
  4.         if (mSeekTime < 0) {  
  5.             mStartTime = frameTime;  
  6.         } else {  
  7.             mStartTime = frameTime - mSeekTime;  
  8.             // Now that we’re playing, reset the seek time  
  9.             mSeekTime = -1;  
  10.         }  
  11.     }  
  12.     if (mPaused) {  
  13.         if (mPauseTime < 0) {  
  14.             mPauseTime = frameTime;  
  15.         }  
  16.         return false;  
  17.     } else if (mResumed) {  
  18.         mResumed = false;  
  19.         if (mPauseTime > 0) {  
  20.             // Offset by the duration that the animation was paused  
  21.             mStartTime += (frameTime - mPauseTime);  
  22.         }  
  23.     }  
  24.     // The frame time might be before the start time during the first frame of  
  25.     // an animation.  The “current time” must always be on or after the start  
  26.     // time to avoid animating frames at negative time intervals.  In practice, this  
  27.     // is very rare and only happens when seeking backwards.  
  28.     final long currentTime = Math.max(frameTime, mStartTime);  
  29.     return animationFrame(currentTime);  
  30. }  
    final boolean doAnimationFrame(long frameTime) {
        if (mPlayingState == STOPPED) {
            mPlayingState = 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);
            }
        }
        // The frame time might be before the start time during the first frame of
        // an animation.  The "current time" must always be on or after the start
        // time to avoid animating frames at negative time intervals.  In practice, this
        // is very rare and only happens when seeking backwards.
        final long currentTime = Math.max(frameTime, mStartTime);
        return animationFrame(currentTime);
    }
注意到上述代码末尾调用了 animationFrame方法,而 animationFrame内部调用了 animateValue,下面看animateValue的代码

  1. void animateValue(float fraction) {  
  2.     fraction = mInterpolator.getInterpolation(fraction);  
  3.     mCurrentFraction = fraction;  
  4.     int numValues = mValues.length;  
  5.     for (int i = 0; i < numValues; ++i) {  
  6.         mValues[i].calculateValue(fraction);  
  7.     }  
  8.     if (mUpdateListeners != null) {  
  9.         int numListeners = mUpdateListeners.size();  
  10.         for (int i = 0; i < numListeners; ++i) {  
  11.             mUpdateListeners.get(i).onAnimationUpdate(this);  
  12.         }  
  13.     }  
  14. }  
    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方法就是计算每帧动画所对应的属性的值,下面着重看一下到底是在哪里调用属性的get和set方法的,毕竟这个才是我们最关心的。

get方法:在初始化的时候,如果属性的初始值没有提供,则get方法将会被调用。

  1. private void setupValue(Object target, Keyframe kf) {  
  2.     if (mProperty != null) {  
  3.         kf.setValue(mProperty.get(target));  
  4.     }  
  5.     try {  
  6.         if (mGetter == null) {  
  7.             Class targetClass = target.getClass();  
  8.             setupGetter(targetClass);  
  9.             if (mGetter == null) {  
  10.                 // Already logged the error - just return to avoid NPE  
  11.                 return;  
  12.             }  
  13.         }  
  14.         kf.setValue(mGetter.invoke(target));  
  15.     } catch (InvocationTargetException e) {  
  16.         Log.e(”PropertyValuesHolder”, e.toString());  
  17.     } catch (IllegalAccessException e) {  
  18.         Log.e(”PropertyValuesHolder”, e.toString());  
  19.     }  
  20. }  
    private void setupValue(Object target, Keyframe kf) {
        if (mProperty != null) {
            kf.setValue(mProperty.get(target));
        }
        try {
            if (mGetter == null) {
                Class targetClass = target.getClass();
                setupGetter(targetClass);
                if (mGetter == null) {
                    // Already logged the error - just return to avoid NPE
                    return;
                }
            }
            kf.setValue(mGetter.invoke(target));
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }

set方法:当动画的下一帧到来的时候,PropertyValuesHolder中的setAnimatedValue方法会将新的属性值设置给对象,调用其set方法

  1. void setAnimatedValue(Object target) {  
  2.     if (mProperty != null) {  
  3.         mProperty.set(target, getAnimatedValue());  
  4.     }  
  5.     if (mSetter != null) {  
  6.         try {  
  7.             mTmpValueArray[0] = getAnimatedValue();  
  8.             mSetter.invoke(target, mTmpValueArray);  
  9.         } catch (InvocationTargetException e) {  
  10.             Log.e(”PropertyValuesHolder”, e.toString());  
  11.         } catch (IllegalAccessException e) {  
  12.             Log.e(”PropertyValuesHolder”, e.toString());  
  13.         }  
  14.     }  
  15. }  
    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());
            }
        }
    }

总结

我觉得这篇源码分析写的逻辑有点混乱,希望不要给大家带来误导。从源码上来说,属性动画的源码逻辑层次有点跳跃,不过没关系,大家只要了解属性动画的工作原理就好,源码的作用在于让我们发现其工作原理的确如此。到此为止,Android动画系列已经全部完成,十分感谢大家阅读,希望能给大家带来一点帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值