Android 屬性動畫詳解與原始碼分析
一、前言
關於什麼是動畫,動畫的相關概念等等這裡就不講了。這裡僅表述一下個人觀點,個人認知是:
1.動畫增加了 UI 展示的動態性,使得UI看起來更具生機。同時,一些酷炫的動畫一定程度上也會提高應用的 Bigger。但這裡要把握一個度,一個頁面中不宜有過多的動畫,1 到 2 個明顯的即可,尤其動畫不能掩蓋業務的主旨。
2.動畫更好的表達了使用者操作的反饋,同時也能更好的給使用者以指導操作。
下面的思維導圖展示了 Android 原生所支援的主要動畫分類,概念以及相關場景。

Android動畫分類.png
這裡概括了 Android 中 8 種主要的動畫,就目前而言,其中最常用的便是屬性動畫(檢視動畫現在應該用的少了),轉場動畫以及檢視狀態動畫,其餘動畫基本上我們是很少接觸的,主要是國內的應用開發中不實用。
當然,除了 Android 原生動畫之外,目前第 3 方的 ofollow,noindex">airbnb / lottie-android 動畫方案,更是給我們提供了跨平臺的動畫解決方案。
在這篇文章裡,我們先主要來看看屬性動畫。
二、屬性動畫概覽
1.基本概念
屬性動畫系統是一個強大的框架,允許您使用幾乎任何物件來作動畫,甚至不用管它是否是繪製到螢幕上的一個View。其主要的原理就是通過定義動畫以隨時間變化而更改物件的某一屬性。更簡單地說,就是修改物件的某個屬性值來實現動畫的。
2.主要特徵
持續時間:您可以指定動畫的持續時間。預設長度為300毫秒。
時間插值:您可以指定如何計算屬性值作為動畫當前已用時間的函式。
重複計數和行為:您可以指定是否在到達持續時間結束時重複動畫以及重複動畫的次數。您還可以指定是否要反向播放動畫。將其設定為反向向前播放動畫然後反覆播放動畫,直到達到重複次數。
動畫設定:您可以將動畫分組為一起或按順序或在指定延遲後播放的邏輯集。
幀重新整理延遲:您可以指定重新整理動畫幀的頻率。預設設定為每10毫秒重新整理一次,但應用程式重新整理幀的速度最終取決於系統整體的繁忙程度以及系統為基礎計時器提供服務的速度。
這是翻譯自官網對屬性動畫的特徵概括,但稍有經驗的你不難發現,其他型別的動畫都會有此概念。
3.框架類圖

Main.jpg
如上面類圖所見,屬性動畫主要被分成 3 個部分,用於組織動畫的 Animator ,用於計算時間插值的 插值器 以及用於計算結果的 估值器 。
4.插值器Interpolator
插值器的作用是使得 屬性值 根據時間的變化從初始值過渡到結束值的變化規律。其實質是一個數學函式 y = f(x) ,定義域 x 屬於 (0.0,1.0) 的 float 值,值域 y 也是 (0.0,1.0) 的 float 值,曲線的斜率是速度。如上圖框架類中,Android 為我們定義了 10 個內建的插值器,其基本上滿足了我們大部分的需求。當然,我們也可以自定義來實現自己的插值器。
下面通過 2 個簡單的 gif 動畫來粗略的感受一下插值器。

簡單右移動畫.gif

綜合右移,旋轉,縮放,alpha
5.估值器
估值器也是一個函式,插值器是得到時間的變化規律,而估值器則是根據時間的變化規律計算得到每一步的運算結果。在動畫的監聽中根據計算的結果來改變目標的屬性值。利用估值器我們可以實現一些曲線運動如拋物線,貝塞爾曲線等動畫。下圖是通過自定義估值器實現的一個簡單的曲線運動。

曲線.gif
三、原始碼分析
不管是 Android 原始碼還是其他第三方的原始碼框架,其程式碼構成往往都是比較繁多且複雜的。我分析程式碼的一個簡單的方法論就是先寫一個最簡單的 demo,然後根據 demo 沿著呼叫鏈來分析其主體流程,在分析的過程中再慢慢補齊相關的概念,模組甚至是重要的細節部分。
1.demo
以 ObjectAnimator 為例來寫一個簡單的右移動畫。
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageViewMove,"translationX",0,100) .setDuration(1 * 1000); objectAnimator.setInterpolator(new LinearInterpolator()); objectAnimator.start();
2.程式碼分析
先來看看時序圖,我們將按照時序的圖的順序一步一步來分析。

ObjectAnimator.jpg
建立動畫
建立動畫就是建立動畫執行的基礎或者說是條件,前面 1 - 9 步都可以說是在建立動畫的基礎。從 ObjectAnimator.ofFloat()開始。
/** * 構建一個返回值為 float 的 ObjectAnimator 的例項 * * @param target 作用於動畫的物件。 * @param propertyName 屬性名稱,要求物件須有setXXX() 方法,且是 public 的。 * @param values,屬性變化的值,可以設定1 個或者 多個。當只有 1 個時,起始值為屬性值本身。當有 2 個值時,第 1 個為起始值,第 2 個為終止值。當超過 2 個時,首尾值的定義與 2 個時一樣,中間值做需要經過的值。 */ public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) { ObjectAnimator anim = new ObjectAnimator(target, propertyName); anim.setFloatValues(values); return anim; }
該方法的引數以作用都已經放在註釋裡面了。而這個方法裡面所做的事情是,首先建立一個 ObjectAnimator 的例項,然後為該例項設定 values。那麼,繼續看 ObjectAnimator 的構建。
構造 ObjectaAnimator
private ObjectAnimator(Object target, String propertyName) { setTarget(target); setPropertyName(propertyName); }
分別呼叫了 setTarget() 方法和setPropertyName()
setTarget()
public void setTarget(@Nullable Object target) { final Object oldTarget = getTarget(); if (oldTarget != target) { if (isStarted()) { cancel(); } mTarget = target == null ? null : new WeakReference<Object>(target); // New target should cause re-initialization prior to starting mInitialized = false; } }
存在舊動畫物件(也可為 null) 與新設定的動畫物件不一致,如果舊動畫是開始了的狀態,則先取消動畫,然後將動畫物件以弱引用物件為記錄下來。
setPropertyName()
public void setPropertyName(@NonNull String propertyName) { // mValues could be null if this is being constructed piecemeal. Just record the // propertyName to be used later when setValues() is called if so. if (mValues != null) { PropertyValuesHolder valuesHolder = mValues[0]; String oldName = valuesHolder.getPropertyName(); valuesHolder.setPropertyName(propertyName); mValuesMap.remove(oldName); mValuesMap.put(propertyName, valuesHolder); } mPropertyName = propertyName; // New property/values/target should cause re-initialization prior to starting mInitialized = false; }
主要就是記錄下 propertyName 的名字。而如果已經有這個 propertyName,則會替換其相應的 PropertyValuesHolder,這裡用了一個 HashMap 來儲存 propertyName 和 PropertyValuesHolder。關於 PropertyValuesHolder 先來看看其類圖結構。對於屬性動畫來說,其屬性相關的變數都被封裝在了 PropertyValuesHolder 裡。

PropertyValuesHolder.jpg
這裡我們還要記住的是 propertyName 是 "translationX"。接下來看 setFloatValues() 方法。
setFloatValues()@Override public void setFloatValues(float... values) { if (mValues == null || mValues.length == 0) { // 當前還沒有任何值 if (mProperty != null) { setValues(PropertyValuesHolder.ofFloat(mProperty, values)); } else { setValues(PropertyValuesHolder.ofFloat(mPropertyName, values)); } } else { // 當前已經有值的情況,呼叫父類的 setFloatValues() super.setFloatValues(values); } }
父類,即 ValueAnimator ,其方法setFloatValues() 如下。
ValueAnimator#setFloatValues()
public void setFloatValues(float... values) { if (values == null || values.length == 0) { return; } if (mValues == null || mValues.length == 0) { setValues(PropertyValuesHolder.ofFloat("", values)); } else { PropertyValuesHolder valuesHolder = mValues[0]; valuesHolder.setFloatValues(values); } // New property/values/target should cause re-initialization prior to starting mInitialized = false; }
這裡可以看出,不管是否呼叫父類的 setFloatValues()。最後都是要將 values 逐個構造成 PropertyValuesHolder,最後存放在前面所說的 HashMap 裡面。當然,如果這裡的 hashMap 還沒有初始化,則先會將其初始化。所以這裡面最關鍵的是要構建出 PropertyValuesHolder 這個物件。那麼就繼續來看看 PropertyValuesHolder#ofFloat() 方法。
PropertyValuesHolder#ofFloat()
public static PropertyValuesHolder ofFloat(String propertyName, float... values) { return new FloatPropertyValuesHolder(propertyName, values); }
構造 FloatPropertyValuesHolder,當然,這裡相應的還有 IntPropertyValuesHolder、MultiIntValuesHolder以及MultiFloatValuesHolder,都是 PropertyValuesHolder 的子類。這裡就只關注 FloatPropertyValuesHolder 吧。
FloatPropertyValuesHolder
public FloatPropertyValuesHolder(String propertyName, float... values) { super(propertyName); setFloatValues(values); }
FloatPropertyValuesHolder 建構函式比較簡單,呼叫父類的構造方法並傳遞了 propertyName,關鍵是進一步 setFloatValues() 方法的呼叫,其又進一步呼叫了父類的 setFloatValues(),在父類的 setFloatValues() 方法裡初始化了動畫的關鍵幀。
PropertyValuesHolder#setFloatValues()
public void setFloatValues(float... values) { mValueType = float.class; mKeyframes = KeyframeSet.ofFloat(values); }
進一步呼叫了 KeyframeSet#ofFloat() 方法以完成關鍵幀的構造。KeyframeSet 是介面 Keyframe 的實現類。
KeyframeSet#ofFloat()
public static KeyframeSet ofFloat(float... values) { boolean badValue = false; int numKeyframes = values.length; // 至少要 2 幀 FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)]; // 然後構造出每一幀,每一幀中主要有 2 個重要的引數 fraction 以及 value if (numKeyframes == 1) { keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f); keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]); if (Float.isNaN(values[0])) { badValue = true; } } 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]); if (Float.isNaN(values[i])) { badValue = true; } } } if (badValue) { Log.w("Animator", "Bad value (NaN) in float animator"); } // 最後將所有的 關鍵幀 彙集到一個集合中 return new FloatKeyframeSet(keyframes); }
這段程式碼看起來多,但其實結構很簡單,其主要內容是:
(1) 構造動畫的關鍵幀,且動畫裡至少要有 2 個關鍵幀。
(2) 關鍵幀中有 2 個重要的引數,fraction這個可以看成是關鍵幀的序號,value 關鍵幀的值,可能是起始值,也可能是中間的某個值。
(3) 最後將關鍵幀彙集成一個關鍵幀集返回給 PropertyValuesHolder。
到這裡就完成建立動畫的 1 ~ 6 步,其主要是 2 件事情,屬性封裝與關鍵幀構建。接下來繼續看 setDuration() 與 setInterpolator() 方法。
setDuration()
@Override @NonNull public ObjectAnimator setDuration(long duration) { super.setDuration(duration); return this; }
呼叫了父類 ValueAnimator 的 setDuration()。
ValueAnimator#setDuration()
@Override public ValueAnimator setDuration(long duration) { if (duration < 0) { throw new IllegalArgumentException("Animators cannot have negative duration: " + duration); } mDuration = duration; return this; }
setDuration() 只是簡單的儲存下 duration 的值,僅此而已,那麼繼續分析 setInterpolator()。
setInterpolator()
@Override public void setInterpolator(TimeInterpolator value) { if (value != null) { mInterpolator = value; } else { mInterpolator = new LinearInterpolator(); } }
setInterpolator() 方法也很簡單,只是簡單的儲存,並且如果傳遞的是 null 的話,則預設使用的便是 LinearInterpolator,即線性插值器。我們這裡的假設的場景也是設定了 LinearInterpolator,這是最簡單的插值器,其作用就是完成勻速運動。這裡藉助 LinearInterpolator 來分析一下插值器。
關於插值器的概念已經在第二節第 4 小節裡面有介紹了。且在第 3 小節中的框架類圖中也完整的描述了插值器的繼承關係。其最關鍵的定義就在 TimeInterpolator 這個介面中,來看看這個介面。
/** * 插值器定義了動畫變化的頻率,其可以是線性的也可以是非線性的,如加速運動或者減速運動。 */ public interface TimeInterpolator { /** * 這裡傳進來的 input 代表當前時間與總時間的比,根據這個時間佔比返回當前的變化頻率。其輸出與輸值都在 [0,1] 之間。 */ float getInterpolation(float input); }
插值器的關鍵定義便是實現 getInterpolation() 方法,即根據當前動畫執行的時間佔比來計算當前動畫的變化頻率。那麼來看看 LinearInterpolator 的 getInterpolation() 實現。
LinearInterpolator#getInterpolation()
public float getInterpolation(float input) { return input; }
對,就是返回原值,因為時間的變化肯定始終都是勻速的。到這裡,建立動畫的 1 ~ 9 步都已經完成了。可時間究竟是怎麼樣變化的,getInterpolation() 又是怎樣被呼叫的?這就是接下來要分析的啟動動畫。
啟動動畫
啟動動畫從 start() 方法開始。
@Override public void start() { AnimationHandler.getInstance().autoCancelBasedOn(this); 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(); }
先確認動畫已經取消。這個方法裡的重要的那句程式碼就是呼叫父類 ValueAnimator 的 start()。父類對外的 start() 方法很簡單,其主要的實現在另一個過載的私有 start() 方法上,來繼續分析。
// 引數 playBackwards 代表動畫是否是逆向的 private void start(boolean playBackwards) { ..... mReversing = playBackwards; // 重置脈衝為 "true" mSelfPulse = !mSuppressSelfPulseRequested; ..... // 新增脈衝回撥用 addAnimationCallback(0); if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) { // If there's no start delay, init the animation and notify start listeners right away // to be consistent with the previous behavior. Otherwise, postpone this until the first // frame after the start delay. startAnimation(); if (mSeekFraction == -1) { // No seek, start at play time 0. Note that the reason we are not using fraction 0 // is because for animations with 0 duration, we want to be consistent with pre-N // behavior: skip to the final value immediately. setCurrentPlayTime(0); } else { setCurrentFraction(mSeekFraction); } } }
這個方法原本很多,但我們只需要關注關鍵的呼叫,其中之一是 addAnimationCallback(),其主要是向 AnimationHander 新增一個回撥介面AnimationHandler.AnimationFrameCallback。如下程式碼。
addAnimationFrameCallback
/** * Register to get a callback on the next frame after the delay. */ public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) { if (mAnimationCallbacks.size() == 0) { getProvider().postFrameCallback(mFrameCallback); } if (!mAnimationCallbacks.contains(callback)) { mAnimationCallbacks.add(callback); } if (delay > 0) { mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay)); } }
ValueAnimator 就實現了 AnimationFrameCallback,所以這裡新增的是 ValueAnimator 的例項。且最終被新增到 mAnimationCallbacks 這個佇列中。這個是很重要的,後面還會再重點關注的。而接下來是另一個呼叫 startAnimation()
startAnimation()
private void startAnimation() { ...... mAnimationEndRequested = false; initAnimation(); mRunning = true; if (mSeekFraction >= 0) { mOverallFraction = mSeekFraction; } else { mOverallFraction = 0f; } if (mListeners != null) { // 通過動畫監聽器動畫開始了 notifyStartListeners(); } }
關鍵呼叫 initAnimation()
void initAnimation() { if (!mInitialized) { int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].init(); } mInitialized = true; } }
mValues 是 PropertyValuesHolder 陣列,這裡的目的是初始化 PropertyValuesHolder。
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 mKeyframes.setEvaluator(mEvaluator); } }
init() 方法的主要目的是就是給關鍵幀設定估值器。因為我們前面呼叫的是 ObjectAnimator#ofFloat() 方法,所以這裡預設給的就是 FloatEvaluator。這裡也來分析一下估值器。估值器的相關概念已經在第二節第 5 小節中有所描述,並且根據框架類圖,其主要是定義了 TypeEvaluator 介面。
TypeEvaluator
public interface TypeEvaluator<T> { public T evaluate(float fraction, T startValue, T endValue); }
fraction 代表了startValue 到 endValue 之間的比例,startValue 與 endValue 是我們自己在 ofFloat() 呼叫時設定的那個。返回結果就是一個線性的結果,在 startValue 與 endValue 之間。那麼來看看 FloatEvaluator 的實現。
FloatEvaluator
public class FloatEvaluator implements TypeEvaluator<Number> { public Float evaluate(float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); } }
很簡單,就是起點值加上當前的段值。到這裡,我們應該對估值器有一個更深的認知了。那麼,再回到最初的 start() 方法裡,經 startAnimation() 設定了 KeyFrame 的估值器後,接下來就會進一步呼叫 setCurrentPlayTime() 來開始動畫。
setCurrentPlayTime()
public void setCurrentPlayTime(long playTime) { float fraction = mDuration > 0 ? (float) playTime / mDuration : 1; setCurrentFraction(fraction); }
初始時呼叫的是setCurrentPlayTime(0),也就是 playTime 為 0,而 mDuration 就是我們自己通過 setDuration() 來設定的。所以這裡得到的 fraction 也是 0。進一步看 setCurrentFraction() 方法。
public void setCurrentFraction(float fraction) { // 再次呼叫 initAnimation() ,前面初始化過了,所以這裡是無用的 initAnimation(); // 校準 fraction 為 [0, mRepeatCount + 1] fraction = clampFraction(fraction); mStartTimeCommitted = true; // do not allow start time to be compensated for jank if (isPulsingInternal()) { // 隨機時間? long seekTime = (long) (getScaledDuration() * fraction); // 獲取動畫的當前執行時間 long currentTime = AnimationUtils.currentAnimationTimeMillis(); // Only modify the start time when the animation is running. Seek fraction will ensure // non-running animations skip to the correct start time. // 得到開始時間 mStartTime = currentTime - seekTime; } else { // If the animation loop hasn't started, or during start delay, the startTime will be // adjusted once the delay has passed based on seek fraction. mSeekFraction = fraction; } mOverallFraction = fraction; final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing); // 執行動畫,注意這裡會先呼叫子類的 animateValue() 方法 animateValue(currentIterationFraction); }
前面都是一些時間的計算,得到當前真正的currentIterationFraction,最後會通過呼叫animateValue() 來執行動畫。而這裡需要同時關注父類與子類的 animateValue() 方法。
子類 ObjectAnimator#animateValue()
void animateValue(float fraction) { final Object target = getTarget(); if (mTarget != null && target == null) { // We lost the target reference, cancel and clean up. Note: we allow null target if the /// target has never been set. cancel(); return; } // 呼叫父類的 animateValue() ,這個很關鍵,時間插值與估值器的計算都在父類的 animateValue() 方法中進行的。 super.animateValue(fraction); int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { // 這裡的 mValues 的是PropertyValuesHolder[],也就是在 PropertyValuesHolder 裡面來改變了目標 target 的屬性值。 mValues[i].setAnimatedValue(target); } }
父類 ValueAnimator#animateValue()
void animateValue(float fraction) { // 獲取時間插值 fraction = mInterpolator.getInterpolation(fraction); mCurrentFraction = fraction; int numValues = mValues.length; // 將時間插值送給估值器,計算出 values 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); } } }
animateValue() 的主要功能都已經在註釋中說明了,其主要就是 2 步,一步計算時間插值和估值器,另一步是呼叫 PropertyValuesHolder 來改變屬性。
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()); } } }
這裡就是通過屬性的 Setter 方法來修改屬性的。
分析到這裡,就完成了動畫的一幀關鍵幀的執行。那麼你一定會感到奇怪了,剩下的幀是怎麼驅動的呢?還是得回到 start() 方法裡面,在這裡最初分析到 addAnimationFrameCallback() 方法。這個方法裡等於是向AnimationHandler註冊了AnimationHandler.AnimationFrameCallback。這個 callback 中其中之一的方法是 doAnimationFrame()。在 ValueAnimator 的實現中如下。
public final boolean doAnimationFrame(long frameTime) { ..... boolean finished = animateBasedOnTime(currentTime); if (finished) { endAnimation(); } return finished; }
這段程式碼原來也是很長的,我們只看關鍵呼叫 animateBasedOnTime()
boolean animateBasedOnTime(long currentTime) { boolean done = false; if (mRunning) { ..... float currentIterationFraction = getCurrentIterationFraction( mOverallFraction, mReversing); animateValue(currentIterationFraction); } return done; }
前面的計算過程,這裡就省略了,其主要的目的也還是計算出 currentIterationFraction。然後再通過 animateValue() 方法來執行動畫。可以看到只要 doAnimationFrame() 被不斷的呼叫,就會產生動畫的一個關鍵幀。如果關鍵幀是連續的,那麼最後也就產生了我們所看到的動畫。
再來分析doAnimationFrame() 是如何被不斷呼叫的。這個需要回到 AnimationHandler 中來,在 AnimationHandler 中有一個非常重要的 callback 實現——Choreographer.FrameCallback。
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { doAnimationFrame(getProvider().getFrameTime()); if (mAnimationCallbacks.size() > 0) { getProvider().postFrameCallback(this); } } };
瞭解 VSync 的同學都知道,Andorid 中的重繪就是由Choreographer在 1 秒內產生 60 個 vsync 來通知 view tree 進行 view 的重繪的。而 vsync 產生後會呼叫它的監聽者回調介面 Choreographer.FrameCallback,也就是說,只要向Choreographer註冊了這個介面,就會每 1 秒裡收到 60 次回撥。因此,在這裡就實現了不斷地呼叫 doAnimationFrame() 來驅動動畫了。想必看到這裡,你應該明白了同學們常說的動畫掉幀的原因了吧。如果 view 的繪製過於複雜,即在 15 ms 內無法完成,那麼就會使得中間某些幀跳過從而造成掉幀。
到這裡,屬性動畫的整個過程以及原理都分析完了。下面來總結一下這個過程:
(1) 動畫是由許多的關鍵幀組成的,這是一個動畫能夠動起來的最基本的原理。
(2) 屬性動畫的主要組成是 PropertyValuesHolder,而 PropertyValuesHolder 又封裝了關鍵幀。
(3) 動畫開始後,其監聽了 Choreographer 的 vsync,使得其可以不斷地呼叫 doAnimationFrame() 來驅動動畫執行每一個關鍵幀。
(4) 每一次的 doAnimationFrame() 呼叫都會去計算時間插值,而通過時間插值器計算得到 fraction 又會傳給估值器,使得估值器可以計算出屬性的當前值。
(5) 最後再通過 PropertyValuesHolder 所記錄下的 Setter 方法,以反射的方式來修改目標屬性的值。當屬性值一幀一幀的改變後,形成連續後,便是我們所見到的動畫。
四、後記
Android 原生動畫中,除了屬性動畫還有其他幾種動畫,在對屬性動畫有了一定的認知後,再來分析其他動畫的實現原理,相信不會太難。後面有時間或者有需要會再來分析。
最後,感謝你能讀到並讀完此文章,如果分析的過程中存在錯誤或者疑問都歡迎留言討論。如果我的分享能夠幫助到你,還請記得幫忙點個贊吧,謝謝。