??您可能經(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é)冠骄!