Android屬性動畫基礎(chǔ):ObjectAnimator是如何修改對象屬性的

??您可能經(jīng)常會聽別人說或在相關(guān)資料中看到ObjectAnimator能夠通過反射直接修改對象的屬性负敏,但是您可能并不清楚相關(guān)機制刷后,本文簡單介紹一下撕彤。
ObjectAnimator重寫了initAnimation()和animateValue(float)方法须肆,探究ObjectAnimator如何修改對象的屬性也要從這兩個方法入手虫溜,看一下相關(guān)源碼:

 public final class ObjectAnimator extends ValueAnimator {
1    void initAnimation() {
2        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.
3            final Object target = getTarget();
4            if (target != null) {
5                final int numValues = mValues.length;
6                for (int i = 0; i < numValues; ++i) {
7                    mValues[i].setupSetterAndGetter(target);
8                }
9            }
10            super.initAnimation();
11        }
12    }

13    void animateValue(float fraction) {
14        final Object target = getTarget();
15        if (mTarget != null && target == null) {
            // We lost the target reference, cancel and clean up.
16            cancel();
17            return;
18        }
19        super.animateValue(fraction);
20        int numValues = mValues.length;
21        for (int i = 0; i < numValues; ++i) {
22            mValues[i].setAnimatedValue(target);
23        }
24    }
  }


// PropertyValuesHolder 部分源碼
public class PropertyValuesHolder implements Cloneable {
25    void setupSetterAndGetter(Object target) {
        // 省略n行代碼
        // We can't just say 'else' here because the catch statement sets mProperty to null.
26        if (mProperty == null) {
27            Class targetClass = target.getClass();
28            if (mSetter == null) {
29                setupSetter(targetClass);
30            }
       // 省略n行代碼
31        }
32    }

33    void setupSetter(Class targetClass) {
34        Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
35        mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
36    }

37    private Method setupSetterOrGetter(Class targetClass,
38            HashMap<Class, HashMap<String, Method>> propertyMapMap, String prefix, Class valueType) {
39        Method setterOrGetter = null;
40        synchronized(propertyMapMap) {
41            // Have to lock property map prior to reading it, to guard against
42            // another thread putting something in there after we've checked it
43            // but before we've added an entry to it
44            HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
45            boolean wasInMap = false;
46            if (propertyMap != null) {
47                wasInMap = propertyMap.containsKey(mPropertyName);
48                if (wasInMap) {
49                    setterOrGetter = propertyMap.get(mPropertyName);
50                }
51            }
52            if (!wasInMap) {
53                setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
54                if (propertyMap == null) {
55                    propertyMap = new HashMap<String, Method>();
56                    propertyMapMap.put(targetClass, propertyMap);
57                }
58                propertyMap.put(mPropertyName, setterOrGetter);
59            }
60        }
61        return setterOrGetter;
62    }

63    private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
64        // TODO: faster implementation...
65        Method returnVal = null;
66        String methodName = getMethodName(prefix, mPropertyName);
67        Class args[] = null;
68        if (valueType == null) {
69            try {
70                returnVal = targetClass.getMethod(methodName, args);
71            } catch (NoSuchMethodException e) {
                // Swallow the error, log it later
72            }
73        } else {
73            args = new Class[1];
74            Class typeVariants[];
75            if (valueType.equals(Float.class)) {
76                typeVariants = FLOAT_VARIANTS;
77            } else if (valueType.equals(Integer.class)) {
78                typeVariants = INTEGER_VARIANTS;
79            } else if (valueType.equals(Double.class)) {
80                typeVariants = DOUBLE_VARIANTS;
81            } else {
82                typeVariants = new Class[1];
83                typeVariants[0] = valueType;
84            }
85            for (Class typeVariant : typeVariants) {
86                args[0] = typeVariant;
87                try {
88                    returnVal = targetClass.getMethod(methodName, args);
89                    if (mConverter == null) {
90                        // change the value type to suit
91                        mValueType = typeVariant;
92                    }
93                    return returnVal;
94                } catch (NoSuchMethodException e) {
95                    // Swallow the error and keep trying other variants
96                }
97            }
            // If we got here, then no appropriate function was found
98        }

99        if (returnVal == null) {
100            Log.w("PropertyValuesHolder", "Method " +
101                    getMethodName(prefix, mPropertyName) + "() with type " + valueType +
102                    " not found on target class " + targetClass);
103        }
105        return returnVal;
106    }

107    static String getMethodName(String prefix, String propertyName) {
108        if (propertyName == null || propertyName.length() == 0) {
            // shouldn't get here
109            return prefix;
110        }
111        char firstLetter = Character.toUpperCase(propertyName.charAt(0));
112        String theRest = propertyName.substring(1);
113        return prefix + firstLetter + theRest;
114    }

115    void setAnimatedValue(Object target) {
116        if (mProperty != null) {
117            mProperty.set(target, getAnimatedValue());
118        }
119        if (mSetter != null) {
120            try {
121                mTmpValueArray[0] = getAnimatedValue();
122                mSetter.invoke(target, mTmpValueArray);
123            } catch (InvocationTargetException e) {
124                Log.e("PropertyValuesHolder", e.toString());
125            } catch (IllegalAccessException e) {
126                Log.e("PropertyValuesHolder", e.toString());
127            }
128        }
129    }
130 }

??前置基礎(chǔ):您需要知道,屬性動畫是通過PropertyValuesHolder來操縱對象屬性或數(shù)值變化的牲迫,它持有您要操縱的對象的屬性名稱(如果您操縱的是對象)及對象類的屬性對應(yīng)的getter和setter Method等關(guān)鍵信息耐朴,換言之,您要操縱的屬性或數(shù)值都被封裝到了PropertyValuesHolder實例中盹憎。PropertyValuesHolder在Android屬性動畫基礎(chǔ)之流程解析中曾做過簡單說明筛峭,但并不詳細,您可自行查看各種動畫實例創(chuàng)建方法來確認陪每。第7行的 mValues就是PropertyValuesHolder類型數(shù)組
??先看一下影晓,ObjectAnimator重寫initAnimatinon()方法主要做了什么定位到第3至第9行,首先檢測我們是否設(shè)置了動畫操縱對象target檩禾,若不為null則調(diào)用PropertyValuesHolder的setupSetterAndGetter(Object)方法來獲取target所屬類對應(yīng)屬性的getter和setter Method(第7行)俯艰,定位到setupSetterAndGetter(Object)方法,省略了部分代碼锌订,我們只看關(guān)鍵部分,第27行根據(jù)對象獲取其運行時Class targetClass画株,第29行調(diào)用setupSetter(targetClass)完成先關(guān)處理辆飘,我們需要深入setupSetter(Class)方法以便了解更多細節(jié),繼續(xù)查看setupSetter(Class)方法谓传,定位到34至35行代碼蜈项。第34行的propertyType是我們所操縱對象的屬性的類型,感興趣的話您也可以了解一下mConverter(android.animation.TypeConverter续挟,通常我們并不使用紧卒,但對于某些復(fù)雜的高級動畫,可能會很有用)诗祸,第35行就是最關(guān)鍵的地方了跑芳,獲取所操縱屬性的setter Method,看一下是如何獲取的直颅,注意一下第35行中的"set"參數(shù)博个,setupSetterOrGetter既可以返回屬性的setter也可以返回getter Method,至于返回誰功偿,是由setupSetterOrGetter方法的第三個參數(shù)決定的盆佣,這里傳入的是"set",所以返回的是setter,看一下setupSetterOrGetter方法相關(guān)細節(jié)吧共耍。
??定位到setupSetterOrGetter方法虑灰,看關(guān)鍵的第53行,獲取屬性setter Method時prefix參數(shù)就是第35行傳入的"set"痹兜,貌似要繼續(xù)追蹤getPropertyFunction(Class targetClass, String prefix, Class valueType)方法~穆咐,看一下吧,只看關(guān)鍵部分佃蚜。我們知道庸娱,獲取屬性相關(guān)Method是離不開屬性對應(yīng)方法名稱methodName的,我們看看屬性動畫系統(tǒng)是如何確定methodName的谐算,您若不注意熟尉,可能會踩坑的。定位到第66行洲脂,通過getMethodName(String prefix, String propertyName)獲取屬性對應(yīng)的方法名稱斤儿,進一步查看getMethodName方法,看一下第111至113行代碼恐锦,發(fā)現(xiàn)坑點了嗎往果?這個方法把屬性名稱第一個字母轉(zhuǎn)為大寫,然后前面拼接上前綴prefix就是相關(guān)的方法名稱一铅,這就是坑點所在陕贮,稍后再說為什么可能會有坑。到此為止潘飘,已經(jīng)獲取到方法名稱了肮之,然后定位到第88行,獲取setter Method卜录,這樣mSetter就初始化完畢了戈擒。
??目前為止,我們從ObjectAnimator的initAnimation()方法出發(fā)艰毒,跟進查看源碼筐高,已經(jīng)知道是如何獲取mSetter了,下面看一下什么時候通過反射修改屬性值的丑瞧,想都不用想柑土,肯定是更新計算完畢后做的“硇冢看一下ObjectAnimator重寫的 animateValue(float)方法冰单,定位到關(guān)鍵的第22行,嗯灸促,就是這里诫欠,深入PropertyValuesHolder的setAnimatedValue(Object)方法看一下涵卵。定位到第115行至末尾,依然看關(guān)鍵的地方荒叼,第122行轿偎,這就不用多說了吧,反射被廓、反射坏晦、反射~。
??綜上嫁乘,我們已經(jīng)知道屬性動畫是通過屬性的相關(guān)setter方法反射來修改對象屬性的昆婿,并不是通過屬性名稱直接獲取屬性來修改的,這是有道理的蜓斧,自己想去吧仓蛆。

??前述我們說過,獲取屬性的setter或getter Method時可能會采坑挎春,下面我們看一下為什么可能會踩坑看疙。以實例來說明為什么是坑點。假設(shè)我們以Point類來描述小球運動軌跡直奋,相關(guān)代碼如下:

public class Point {
    private float point_x;
    private float point_y;

       public float getPointX() {
            return point_x;
        }

        public void setPointX(float pointX) {
            this.point_x = pointX;
        }

        public float getPointY() {
            return point_y;
        }

        public void setPointY(float pointY) {
            this.point_y = pointY;
        }
}

我們的屬性動畫操縱的就是Point對象并希望屬性動畫系統(tǒng)根據(jù)反射不斷更新point_x和point_y能庆,那么很抱歉,會失敗的脚线,為什么搁胆?因為屬性名稱是point_x和point_y啊,根據(jù)上述getMethodName(String prefix, String propertyName)方法邮绿,返回的setter方法分別為setPoint_x和setPoint_y渠旁,我曹,然而并沒有這倆方法斯碌,有的只是setPointX和setPointY。所以啊肛度,使用屬性動畫時傻唾,若想通過反射修改對象的屬性,千萬記得保證set和get方法的格式正確性("set" + propetyName和"get" + propetyName)承耿。
此文終結(jié)冠骄!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市加袋,隨后出現(xiàn)的幾起案子凛辣,更是在濱河造成了極大的恐慌,老刑警劉巖职烧,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扁誓,死亡現(xiàn)場離奇詭異防泵,居然都是意外死亡,警方通過查閱死者的電腦和手機蝗敢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門捷泞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人寿谴,你說我怎么就攤上這事锁右。” “怎么了讶泰?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵咏瑟,是天一觀的道長。 經(jīng)常有香客問我痪署,道長码泞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任惠桃,我火速辦了婚禮浦夷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辜王。我一直安慰自己劈狐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布呐馆。 她就那樣靜靜地躺著肥缔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汹来。 梳的紋絲不亂的頭發(fā)上续膳,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音收班,去河邊找鬼坟岔。 笑死,一個胖子當(dāng)著我的面吹牛摔桦,可吹牛的內(nèi)容都是我干的社付。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼邻耕,長吁一口氣:“原來是場噩夢啊……” “哼鸥咖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起兄世,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤啼辣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后御滩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸥拧,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡党远,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了住涉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片麸锉。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖舆声,靈堂內(nèi)的尸體忽然破棺而出花沉,到底是詐尸還是另有隱情,我是刑警寧澤媳握,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布碱屁,位于F島的核電站,受9級特大地震影響蛾找,放射性物質(zhì)發(fā)生泄漏娩脾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一打毛、第九天 我趴在偏房一處隱蔽的房頂上張望柿赊。 院中可真熱鬧,春花似錦幻枉、人聲如沸碰声。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胰挑。三九已至,卻和暖如春椿肩,著一層夾襖步出監(jiān)牢的瞬間瞻颂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工郑象, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贡这,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓厂榛,卻偏偏與公主長得像盖矫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子噪沙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容