Android属性动画基础:ObjectAnimator是如何修改对象属性的

 

 


转载自https://www.jianshu.com/p/0404734f407a

96 乱世白衣 已关注

  您可能经常会听别人说或在相关资料中看到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)。
此文终结!

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试