NineOldAndroids 是一款支持在低版本(API 11 以下)使用 Android 屬性動畫以及 3D 旋轉動畫的框架,它提供了一系列如 ViewAnimator,ObjectAnimator,ViewPropertyAnimator 等 API 來完成這些動畫,解決了 Android 動畫框架在低版本的兼容性問題。在 API 11 (Honeycomb (Android 3.0))后 Android 推出了屬性動畫、X 軸翻轉等動畫效果,但是這些效果卻不能運行在 API 11 以下,NineOldAndroids 的出現使得這些動畫效果能夠兼容低版本系統,保證動畫在各個系統版本能夠完美運行。

NineOldAndroids 提供了和系統屬性一樣的動畫功能。看源碼你可以發現,其實 NOA 的架構實現和系統屬性動畫實現架構其實是一樣的。只是兼容的那一部分采用了 Matrix 實現了各種動畫效果,中間多了一些輔助類,比如 PreHoneycombCompat,AnimatorProxy,ViewHelper,另外某些類對于兼容有些改動,其它的類幾乎和系統屬性動畫部分是一樣的。
在屬性動畫基礎中已經提到:ValueAnimator 的缺點是需要通過實現 AnimatorUpdateListener 自己手動去更新屬性值,它的子類 ObjectAnimator 為用戶實現了自動更新動畫,但是對于自定義的屬性,需要提供標準 JavaBean 的 setter 和 getter 方法,以便獲取和更新屬性值。NOA 也是遵循了這樣的實現思路,對于 3.0 之前的系統來說,屬性動畫中所提供的屬性都是新的,在實現的時候也就都屬于自定義的。NOA 在 PreHoneycombCompat 中定義了這些屬性,并在 get 和 setValue 中提供了標準的 setter 和 getter 方法用于設置和獲取屬性值,這里的 setter 和 getter 其實是直接調用 AnimatorProxy 類的方法。

以上是 NineoldAnimations 的整體設計圖,其實就是系統屬性動畫的整體設計。Animator 通過 PropertyValuesHolder 來更新對象的目標屬性。如果用戶沒有設置目標屬性的 Property 對象,那么會通過反射的形式調用目標屬性的 setter 方法來更新屬性值;否則則通過 Property 的 set 方法來設置屬性值。這個屬性值則通過 KeyFrameSet 的計算得到,而 KeyFrameSet 又是通過時間插值器 TimeInterpolator 和類型估值器 TypeEvaluator 來計算。在動畫執行過程中不斷地計算當前時刻目標屬性的值,然后更新屬性值來達到動畫效果。
在進行下一步的分析之前,我們先來了解一下 NineOldAndroids 中一些核心的類以及它們的作用。
ValueAnimator : 該類是 Animator 的子類,實現了動畫的整個處理邏輯,也是 NineOldAndroids 為核心的類;
ObjectAnimator : 對象屬性動畫的操作類,繼承自 ValueAnimator,通過該類使用動畫的形式操作對象的屬性;
TimeInterpolator : 時間插值器,它的作用是根據時間流逝的百分比來計算出當前屬性值改變的百分比,系統預置的有 LinearInterpolator(線性插值器:勻速動畫)、AccelerateDecelerateInterpolator(加速減速插值器:動畫兩頭慢中間快)和 DecelerateInterpolator(減速插值器:動畫越來越慢)等;
TypeEvaluator : TypeEvaluator 的中文翻譯為類型估值算法,它的作用是根據當前屬性改變的百分比來計算改變后的屬性值,系統預置的有 IntEvaluator(針對整型屬性)、FloatEvaluator(針對浮點型屬性)和 ArgbEvaluator(針對 Color 屬性);
Property : 屬性對象,主要是定義了屬性的 set 和 get 方法;
PropertyValuesHolder : PropertyValuesHolder 是持有目標屬性 Property、setter 和 getter 方法、以及 KeyFrameSet 的類;
KeyFrame : 一個 keyframe 對象由一對 time / value 的鍵值對組成,可以為動畫定義某一特定時間的特定狀態,Animator 傳入的一個個參數映射為一個個 keyframe,存儲相應的動畫的觸發時間和屬性值;
KeyFrameSet : 存儲一個動畫的關鍵幀集合;
AnimationProxy : 兼容屬性動畫的終實現類,每一個應用屬性的方法,都會有以下兩個步驟:prepareForUpdate(); invalidateAfterUpdate(); 而動畫的平移和旋轉是通過 Matrix 實現的;
PreHoneycombCompat 創建了 3.0 屬性動畫中的所有屬性,并提供了 setter 和 getter 方法獲取和更新屬性值;
ViewHelper : 設置各種動畫值的幫助類,可以簡單的設置并應用動畫值。內部先做是否需要代理的判斷,然后調用不同的實現,NOA 的具體實現其實在 AnimatorProxy 中完成的;
核心類更詳細多介紹,請參考公共技術點動畫基礎部分
示例 1:
改變一個對象(myObject)的 translationY 屬性,讓其沿著 Y 軸向上平移一段距離:它的高度,該動畫在默認時間內完成,動畫的完成時間是可以定義的,想要更靈活的效果我們還可以定義插值器和估值算法,但是一般來說我們不需要自定義,系統已經預置了一些,能夠滿足常用的動畫。
ObjectAnimator.ofFloat(myObject, "translationY", -myObject.getHeight()).start();示例 2:
改變一個對象的背景色屬性,典型的情形是改變 View 的背景色,下面的動畫可以讓背景色在 3 秒內實現從 0xFFFF8080 到 0xFF8080FF 的漸變,并且動畫會無限循環而且會有反轉的效果。
ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", /*Red*/0xFFFF8080, /*Blue*/0xFF8080FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.start();示例 3:
動畫集合,5 秒內對 View 的旋轉、平移、縮放和透明度都進行了改變。
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(myView, "rotationX", 0, 360),
ObjectAnimator.ofFloat(myView, "rotationY", 0, 180),
ObjectAnimator.ofFloat(myView, "rotation", 0, -90),
ObjectAnimator.ofFloat(myView, "translationX", 0, 90),
ObjectAnimator.ofFloat(myView, "translationY", 0, 90),
ObjectAnimator.ofFloat(myView, "scaleX", 1, 1.5f),
ObjectAnimator.ofFloat(myView, "scaleY", 1, 0.5f),
ObjectAnimator.ofFloat(myView, "alpha", 1, 0.25f, 1)
);
set.setDuration(5 * 1000).start();示例 4:
下面是個簡單的調用方式,其 animate 方法是 nineoldandroids 特有的,動畫持續時間為 2 秒,在 Y 軸上旋轉 720 度,并且平移到(100, 100)的位置。
Button myButton = (Button)findViewById(R.id.myButton);
animate(myButton).setDuration(2000).rotationYBy(720).x(100).y(100);更多使用可參考 lightSky 的一篇文章PropertyAnim 實際應用,介紹了一些基本使用以及 GitHub 上使用了 NOA 的動畫開源庫



上圖左側其實和系統屬性動畫的結構是一樣的,右側的 AnimatorProxy 和 ViewHelper 是 NOA 中特有的輔助類。
ObjectAnimator 是 ValueAnimator 的子類,ObjectAnimator 負責的是屬性動畫,但是真正對于動畫進行操作的類實際上是 ValueAnimator,它定義了 nineoldandroids 的執行邏輯,是 nineoldandroids 的核心之一。 啟動動畫時,會將 Animator 自身添加到等待執行的動畫隊列中(sPendingAnimations),然后通過 AnimationHandler 發送一個 ANIMATION_START 的消息來通知 nineoldandroids 啟動動畫。
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
// ......
sPendingAnimations.get().add(this);
// ......
AnimationHandler animationHandler = sAnimationHandler.get();
if (animationHandler == null) {
animationHandler = new AnimationHandler();
sAnimationHandler.set(animationHandler);
}
animationHandler.sendEmptyMessage(ANIMATION_START);
}AnimationHandler 是 ValueAnimator 的內部類,它的職責是處理動畫的持續執行。
private static class AnimationHandler extends Handler {
@Override
public void handleMessage(Message msg) {
boolean callAgain = true;
ArrayList<ValueAnimator> animations = sAnimations.get();
ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();
switch (msg.what) {
case ANIMATION_START:
ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get();
// 啟動在等待隊列中的動畫,然后進入 ANIMATION_FRAME 進行循環執行
case ANIMATION_FRAME:
// 根據啟動時間來判斷是否啟動等待中的動畫,如果是已經啟動的動畫,則根據時間點來計算此刻該動畫應該得到的屬性值
//,并且跟新屬性,如果此時動畫沒有執行完成,則又會通過發布一個 ANIMATION_FRAME 消息,使得在此進入到這個代碼段,
// 這樣就相當于循環執行動畫,直到動畫完成
}
}
}在前文中已經說過,如果是屬性動畫,且系統低于 API 11 時會通過矩陣變換的形式來處理屬性動畫效果;否則會通過 set + 屬性名的形式調用目標對象的 setter 方法來達到更新屬性值。這個版本兼容問題會在初始化動畫時進行處理,處理這個問題的函數在 ObjectAnimator 的 initAnimation 中。
@Override
void initAnimation() {
if (!mInitialized) {
// 注意這里,很重要
if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY
&& (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
setProperty(PROXY_PROPERTIES.get(mPropertyName));
}
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupSetterAndGetter(mTarget);
}
super.initAnimation();
}
}這里進行包裝的屬性動畫主要是 View 的 alpha、縮放、平移、旋轉等幾個主要動畫。如果是其他類型對象,那么會在會通過該對象中的目標屬性的 setter 和 getter 方法來訪問,例如對象類型是一個自定義的 MyCustomButton,它有一個屬性為 myText,用戶通過屬性動畫修改它時就需要定義它的 setter 和 getter 方法,比如 setMyText 和 getMyText,這樣 nineoldanimations 就會在動畫執行時通過反射來調用 setter 方法進行更新對象屬性。
ObjectAnimator 是屬性動畫的入口類,用戶通過上述一系列的靜態工廠函數來構建 ObjectAnimator,設置完基本屬性之后,用戶需要設置動畫執行時間、重復模式等其他屬性(可選)。 上述幾個靜態函數中,參數一都是需要動畫的目標對象,參數二要操作的屬性名稱,參數三是目標屬性的取值范圍,如果傳遞一個值那么該值就是目標屬性的終值;如果傳遞的是兩個值,那么個值為起始值,第二個為終值。
(1)主要函數
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) ;
public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> property, int... values) ;
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) ;
public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> property,
float... values) ;
public static ObjectAnimator ofObject(Object target, String propertyName,
TypeEvaluator evaluator, Object... values) ;ValueAnimator和ObjectAnimator都可以完成屬性動畫,但它們之間的區別和優劣可以參考公共技術點動畫基礎的相關部分
關鍵幀集合類在動畫運行時會根據流逝的時間因子 (fraction)和類型估值器來計算當前時間目標屬性的新值,然后將這個值通過反射或者 Property 的 set 方法設置給目標對象。下面是獲取當前屬性值的計算函數。
public Object getValue(float fraction) {
// Special-case optimization for the common case of only two keyframes
if (mNumKeyframes == 2) {
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
mLastKeyframe.getValue());
}
if (fraction <= 0f) {
final Keyframe nextKeyframe = mKeyframes.get(1);
final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
final float prevFraction = mFirstKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) /
(nextKeyframe.getFraction() - prevFraction);
return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
nextKeyframe.getValue());
} else if (fraction >= 1f) {
final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
final /*Time*/Interpolator interpolator = mLastKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
final float prevFraction = prevKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) /
(mLastKeyframe.getFraction() - prevFraction);
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
mLastKeyframe.getValue());
}
Keyframe prevKeyframe = mFirstKeyframe;
for (int i = 1; i < mNumKeyframes; ++i) {
Keyframe nextKeyframe = mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
final float prevFraction = prevKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) /
(nextKeyframe.getFraction() - prevFraction);
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
nextKeyframe.getValue());
}
prevKeyframe = nextKeyframe;
}
// shouldn't reach here
return mLastKeyframe.getValue();
}PropertyValuesHolder 是持有目標屬性 Property、setter 和 getter 方法、以及關鍵幀集合的類。如果沒有屬性的 mProperty 不為空,比如用戶使用了內置的 Property 或者自定義實現了 Property,并且設置給了動畫類,那么在更新動畫時則會使用 Property 對象的 set 方法來更新屬性值。否則在初始化時 PropertyValuesHolder 會拼裝屬性的 setter 和 getter 函數 (注意這里的 setter 和上面說的 Property 對象的 set 方法是兩碼事) ,然后檢測目標對象中是否含有這些方法,如果含有則獲取 setter 和 getter。
void setupSetter(Class targetClass) {
mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType);
}
// 通過 KeyFrameSet 計算屬性值
void calculateValue(float fraction) {
mAnimatedValue = mKeyframeSet.getValue(fraction);
}
// ......
void setAnimatedValue(Object target) {
if (mProperty != null) {
// 獲取新值,并且通過 Property 的 set 方法更新值
mProperty.set(target, getAnimatedValue());
}
// 如果沒有設置 Property,那么通過屬性的 setter 來反射調用更新
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());
}
}
}在動畫執行時通過關鍵幀中的插值器和類型估值器來計算新的屬性值(見 calculatVealue 函數),然后通過反射調用 setter 方法或者 Property 對象的 set 方法設置給目標對象來更新目標屬性,循環執行這個過程就實現了動畫效果。
NineoldAnimations 總得來說還是比較不錯的,在開發過程中起到了很大的作用。但是從設計角度上看,它可能并不是特別的好,例如代碼中到處充斥著沒有進行類型檢查的警告,也可能是這個庫本身存在太多的可變性,導致難以周全。 該項目目前已經標識為 DEPRECATED,作者的原意應該是不再更新該庫,因為它已經比較穩定,希望朋友們不要誤以為是不再建議使用該庫的意思。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。