企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
#### 7.3.5 属性动画的工作原理 属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次去调用set方法。每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get方法,因为系统要去获取属性的初始值。对于属性动画来说,其动画过程中所做的就是这么多,下面看源码分析。 首先我们要找一个入口,就从ObjectAnimator.ofInt(mButton, "width", 500).setDuration (5000).start()开始,其他动画都是类似的。先看ObjectAnimator的start方法: 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.mPending- Animations.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.mDelayed- Anims.get(i); if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) { anim.cancel(); } } } } if (DBG) { Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration()); for (int i = 0; i < mValues.length; ++i) { PropertyValuesHolder pvh = mValues[i]; Log.d(LOG_TAG, " Values[" + i + "]: " + pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " + pvh.mKeyframes.getValue(1)); } } super.start(); } 上面的代码别看那么长,其实做的事情很简单,首先会判断如果当前动画、等待的动画(Pending)和延迟的动画(Delay)中有和当前动画相同的动画,那么就把相同的动画给取消掉,接下来那一段是log,再接着就调用了父类的super.start()方法。因为ObjectAnimator继承了ValueAnimator,所以接下来我们看一下ValueAnimator的Start方法: 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; updateScaledDuration(); // in case the scale factor has changed since creation time 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(); } 可以看出属性动画需要运行在有Looper的线程中。上述代码最终会调用Animation-Handler的start方法,这个AnimationHandler并不是Handler,它是一个Runnable。看一下它的代码,通过代码我们发现,很快就调到了JNI层,不过JNI层最终还是要调回来的。它的run方法会被调用,这个Runnable涉及和底层的交互,我们就忽略这部分,直接看重点:ValueAnimator中的doAnimationFrame方法,如下所示。 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的代码: 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方法将会被调用,请看Property-ValuesHolder的setupValue方法,可以发现get方法是通过反射来调用的,如下所示。 private void setupValue(Object target, Keyframe kf) { if (mProperty ! = null) { Object value = convertBack(mProperty.get(target)); kf.setValue(value); } try { if (mGetter == null) { Class targetClass = target.getClass(); setupGetter(targetClass); if (mGetter == null) { // Already logged the error - just return to avoid NPE return; } } Object value = convertBack(mGetter.invoke(target)); kf.setValue(value); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } 当动画的下一帧到来的时候,PropertyValuesHolder中的setAnimatedValue方法会将新的属性值设置给对象,调用其set方法。从下面的源码可以看出,set方法也是通过反射来调用的: ``` 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()); } } } ```