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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容