美文网首页Android技术知识Android开发经验谈Android开发
Android属性动画基础:ObjectAnimator是如何修

Android属性动画基础:ObjectAnimator是如何修

作者: 乱世白衣 | 来源:发表于2018-06-04 17:04 被阅读41次

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

    相关文章

      网友评论

        本文标题:Android属性动画基础:ObjectAnimator是如何修

        本文链接:https://www.haomeiwen.com/subject/ofwesftx.html