简介
做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!