转载自https://www.jianshu.com/p/0404734f407a
乱世白衣 已关注
您可能经常会听别人说或在相关资料中看到ObjectAnimator能够通过反射直接修改对象的属性,但是您可能并不清楚相关机制,本文简单介绍一下。
ObjectAnimator重写了initAnimation()和animateValue(float)方法,探究ObjectAnimator如何修改对象的属性也要从这两个方法入手,看一下相关源码:
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 }
前置基础:您需要知道,属性动画是通过PropertyValuesHolder来操纵对象属性或数值变化的,它持有您要操纵的对象的属性名称(如果您操纵的是对象)及对象类的属性对应的getter和setter Method等关键信息,换言之,您要操纵的属性或数值都被封装到了PropertyValuesHolder实例中。PropertyValuesHolder在Android属性动画基础之流程解析中曾做过简单说明,但并不详细,您可自行查看各种动画实例创建方法来确认。第7行的 mValues就是PropertyValuesHolder类型数组
先看一下,ObjectAnimator重写initAnimatinon()方法主要做了什么定位到第3至第9行,首先检测我们是否设置了动画操纵对象target,若不为null则调用PropertyValuesHolder的setupSetterAndGetter(Object)方法来获取target所属类对应属性的getter和setter Method(第7行),定位到setupSetterAndGetter(Object)方法,省略了部分代码,我们只看关键部分,第27行根据对象获取其运行时Class targetClass,第29行调用setupSetter(targetClass)完成先关处理,我们需要深入setupSetter(Class)方法以便了解更多细节,继续查看setupSetter(Class)方法,定位到34至35行代码。第34行的propertyType是我们所操纵对象的属性的类型,感兴趣的话您也可以了解一下mConverter(android.animation.TypeConverter,通常我们并不使用,但对于某些复杂的高级动画,可能会很有用),第35行就是最关键的地方了,获取所操纵属性的setter Method,看一下是如何获取的,注意一下第35行中的"set"参数,setupSetterOrGetter既可以返回属性的setter也可以返回getter Method,至于返回谁,是由setupSetterOrGetter方法的第三个参数决定的,这里传入的是"set",所以返回的是setter,看一下setupSetterOrGetter方法相关细节吧。
定位到setupSetterOrGetter方法,看关键的第53行,获取属性setter Method时prefix参数就是第35行传入的"set",貌似要继续追踪getPropertyFunction(Class targetClass, String prefix, Class valueType)方法~,看一下吧,只看关键部分。我们知道,获取属性相关Method是离不开属性对应方法名称methodName的,我们看看属性动画系统是如何确定methodName的,您若不注意,可能会踩坑的。定位到第66行,通过getMethodName(String prefix, String propertyName)获取属性对应的方法名称,进一步查看getMethodName方法,看一下第111至113行代码,发现坑点了吗?这个方法把属性名称第一个字母转为大写,然后前面拼接上前缀prefix就是相关的方法名称,这就是坑点所在,稍后再说为什么可能会有坑。到此为止,已经获取到方法名称了,然后定位到第88行,获取setter Method,这样mSetter就初始化完毕了。
目前为止,我们从ObjectAnimator的initAnimation()方法出发,跟进查看源码,已经知道是如何获取mSetter了,下面看一下什么时候通过反射修改属性值的,想都不用想,肯定是更新计算完毕后做的。看一下ObjectAnimator重写的 animateValue(float)方法,定位到关键的第22行,嗯,就是这里,深入PropertyValuesHolder的setAnimatedValue(Object)方法看一下。定位到第115行至末尾,依然看关键的地方,第122行,这就不用多说了吧,反射、反射、反射~。
综上,我们已经知道属性动画是通过属性的相关setter方法反射来修改对象属性的,并不是通过属性名称直接获取属性来修改的,这是有道理的,自己想去吧。
前述我们说过,获取属性的setter或getter Method时可能会采坑,下面我们看一下为什么可能会踩坑。以实例来说明为什么是坑点。假设我们以Point类来描述小球运动轨迹,相关代码如下:
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对象并希望属性动画系统根据反射不断更新point_x和point_y,那么很抱歉,会失败的,为什么?因为属性名称是point_x和point_y啊,根据上述getMethodName(String prefix, String propertyName)方法,返回的setter方法分别为setPoint_x和setPoint_y,我曹,然而并没有这俩方法,有的只是setPointX和setPointY。所以啊,使用属性动画时,若想通过反射修改对象的属性,千万记得保证set和get方法的格式正确性("set" + propetyName和"get" + propetyName)。
此文终结!