/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.animation; import android.util.FloatProperty; import android.util.IntProperty; import android.util.Log; import android.util.Property; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * This class holds information about a property and the values that that property * should take on during an animation. PropertyValuesHolder objects can be used to create * animations with ValueAnimator or ObjectAnimator that operate on several different properties * in parallel. */ public class PropertyValuesHolder implements Cloneable { /** * The name of the property associated with the values. This need not be a real property, * unless this object is being used with ObjectAnimator. But this is the name by which * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator. */ String mPropertyName; /** * @hide */ protected Property mProperty; /** * The setter function, if needed. ObjectAnimator hands off this functionality to * PropertyValuesHolder, since it holds all of the per-property information. This * property is automatically * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator. */ Method mSetter = null; /** * The getter function, if needed. ObjectAnimator hands off this functionality to * PropertyValuesHolder, since it holds all of the per-property information. This * property is automatically * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator. * The getter is only derived and used if one of the values is null. */ private Method mGetter = null; /** * The type of values supplied. This information is used both in deriving the setter/getter * functions and in deriving the type of TypeEvaluator. */ Class mValueType; /** * The set of keyframes (time/value pairs) that define this animation. */ KeyframeSet mKeyframeSet = null; // type evaluators for the primitive types handled by this implementation private static final TypeEvaluator sIntEvaluator = new IntEvaluator(); private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator(); // We try several different types when searching for appropriate setter/getter functions. // The caller may have supplied values in a type that does not match the setter/getter // functions (such as the integers 0 and 1 to represent floating point values for alpha). // Also, the use of generics in constructors means that we end up with the Object versions // of primitive types (Float vs. float). But most likely, the setter/getter functions // will take primitive types instead. // So we supply an ordered array of other types to try before giving up. private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class, Double.class, Integer.class}; private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class, Float.class, Double.class}; private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class, Float.class, Integer.class}; // These maps hold all property entries for a particular class. This map // is used to speed up property/setter/getter lookups for a given class/property // combination. No need to use reflection on the combination more than once. private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap = new HashMap<Class, HashMap<String, Method>>(); private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap = new HashMap<Class, HashMap<String, Method>>(); // This lock is used to ensure that only one thread is accessing the property maps // at a time. final ReentrantReadWriteLock mPropertyMapLock = new ReentrantReadWriteLock(); // Used to pass single value to varargs parameter in setter invocation final Object[] mTmpValueArray = new Object[1]; /** * The type evaluator used to calculate the animated values. This evaluator is determined * automatically based on the type of the start/end objects passed into the constructor, * but the system only knows about the primitive types int and float. Any other * type will need to set the evaluator to a custom evaluator for that type. */ private TypeEvaluator mEvaluator; /** * The value most recently calculated by calculateValue(). This is set during * that function and might be retrieved later either by ValueAnimator.animatedValue() or * by the property-setting logic in ObjectAnimator.animatedValue(). */ private Object mAnimatedValue; /** * Internal utility constructor, used by the factory methods to set the property name. * @param propertyName The name of the property for this holder. */ private PropertyValuesHolder(String propertyName) { mPropertyName = propertyName; } /** * Internal utility constructor, used by the factory methods to set the property. * @param property The property for this holder. */ private PropertyValuesHolder(Property property) { mProperty = property; if (property != null) { mPropertyName = property.getName(); } } /** * Constructs and returns a PropertyValuesHolder with a given property name and * set of int values. * @param propertyName The name of the property being animated. * @param values The values that the named property will animate between. * @return PropertyValuesHolder The constructed PropertyValuesHolder object. */ public static PropertyValuesHolder ofInt(String propertyName, int... values) { return new IntPropertyValuesHolder(propertyName, values); } /** * Constructs and returns a PropertyValuesHolder with a given property and * set of int values. * @param property The property being animated. Should not be null. * @param values The values that the property will animate between. * @return PropertyValuesHolder The constructed PropertyValuesHolder object. */ public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) { return new IntPropertyValuesHolder(property, values); } /** * Constructs and returns a PropertyValuesHolder with a given property name and * set of float values. * @param propertyName The name of the property being animated. * @param values The values that the named property will animate between. * @return PropertyValuesHolder The constructed PropertyValuesHolder object. */ public static PropertyValuesHolder ofFloat(String propertyName, float... values) { return new FloatPropertyValuesHolder(propertyName, values); } /** * Constructs and returns a PropertyValuesHolder with a given property and * set of float values. * @param property The property being animated. Should not be null. * @param values The values that the property will animate between. * @return PropertyValuesHolder The constructed PropertyValuesHolder object. */ public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) { return new FloatPropertyValuesHolder(property, values); } /** * Constructs and returns a PropertyValuesHolder with a given property name and * set of Object values. This variant also takes a TypeEvaluator because the system * cannot automatically interpolate between objects of unknown type. * * @param propertyName The name of the property being animated. * @param evaluator A TypeEvaluator that will be called on each animation frame to * provide the necessary interpolation between the Object values to derive the animated * value. * @param values The values that the named property will animate between. * @return PropertyValuesHolder The constructed PropertyValuesHolder object. */ public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator, Object... values) { PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); pvh.setObjectValues(values); pvh.setEvaluator(evaluator); return pvh; } /** * Constructs and returns a PropertyValuesHolder with a given property and * set of Object values. This variant also takes a TypeEvaluator because the system * cannot automatically interpolate between objects of unknown type. * * @param property The property being animated. Should not be null. * @param evaluator A TypeEvaluator that will be called on each animation frame to * provide the necessary interpolation between the Object values to derive the animated * value. * @param values The values that the property will animate between. * @return PropertyValuesHolder The constructed PropertyValuesHolder object. */ public static <V> PropertyValuesHolder ofObject(Property property, TypeEvaluator<V> evaluator, V... values) { PropertyValuesHolder pvh = new PropertyValuesHolder(property); pvh.setObjectValues(values); pvh.setEvaluator(evaluator); return pvh; } /** * Constructs and returns a PropertyValuesHolder object with the specified property name and set * of values. These values can be of any type, but the type should be consistent so that * an appropriate {@link android.animation.TypeEvaluator} can be found that matches * the common type. * <p>If there is only one value, it is assumed to be the end value of an animation, * and an initial value will be derived, if possible, by calling a getter function * on the object. Also, if any value is null, the value will be filled in when the animation * starts in the same way. This mechanism of automatically getting null values only works * if the PropertyValuesHolder object is used in conjunction * {@link ObjectAnimator}, and with a getter function * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has * no way of determining what the value should be. * @param propertyName The name of the property associated with this set of values. This * can be the actual property name to be used when using a ObjectAnimator object, or * just a name used to get animated values, such as if this object is used with an * ValueAnimator object. * @param values The set of values to animate between. */ public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) { KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); if (keyframeSet instanceof IntKeyframeSet) { return new IntPropertyValuesHolder(propertyName, (IntKeyframeSet) keyframeSet); } else if (keyframeSet instanceof FloatKeyframeSet) { return new FloatPropertyValuesHolder(propertyName, (FloatKeyframeSet) keyframeSet); } else { PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); pvh.mKeyframeSet = keyframeSet; pvh.mValueType = ((Keyframe)values[0]).getType(); return pvh; } } /** * Constructs and returns a PropertyValuesHolder object with the specified property and set * of values. These values can be of any type, but the type should be consistent so that * an appropriate {@link android.animation.TypeEvaluator} can be found that matches * the common type. * <p>If there is only one value, it is assumed to be the end value of an animation, * and an initial value will be derived, if possible, by calling the property's * {@link android.util.Property#get(Object)} function. * Also, if any value is null, the value will be filled in when the animation * starts in the same way. This mechanism of automatically getting null values only works * if the PropertyValuesHolder object is used in conjunction with * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has * no way of determining what the value should be. * @param property The property associated with this set of values. Should not be null. * @param values The set of values to animate between. */ public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) { KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); if (keyframeSet instanceof IntKeyframeSet) { return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet); } else if (keyframeSet instanceof FloatKeyframeSet) { return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet); } else { PropertyValuesHolder pvh = new PropertyValuesHolder(property); pvh.mKeyframeSet = keyframeSet; pvh.mValueType = ((Keyframe)values[0]).getType(); return pvh; } } /** * Set the animated values for this object to this set of ints. * If there is only one value, it is assumed to be the end value of an animation, * and an initial value will be derived, if possible, by calling a getter function * on the object. Also, if any value is null, the value will be filled in when the animation * starts in the same way. This mechanism of automatically getting null values only works * if the PropertyValuesHolder object is used in conjunction * {@link ObjectAnimator}, and with a getter function * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has * no way of determining what the value should be. * * @param values One or more values that the animation will animate between. */ public void setIntValues(int... values) { mValueType = int.class; mKeyframeSet = KeyframeSet.ofInt(values); } /** * Set the animated values for this object to this set of floats. * If there is only one value, it is assumed to be the end value of an animation, * and an initial value will be derived, if possible, by calling a getter function * on the object. Also, if any value is null, the value will be filled in when the animation * starts in the same way. This mechanism of automatically getting null values only works * if the PropertyValuesHolder object is used in conjunction * {@link ObjectAnimator}, and with a getter function * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has * no way of determining what the value should be. * * @param values One or more values that the animation will animate between. */ public void setFloatValues(float... values) { mValueType = float.class; mKeyframeSet = KeyframeSet.ofFloat(values); } /** * Set the animated values for this object to this set of Keyframes. * * @param values One or more values that the animation will animate between. */ public void setKeyframes(Keyframe... values) { int numKeyframes = values.length; Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)]; mValueType = ((Keyframe)values[0]).getType(); for (int i = 0; i < numKeyframes; ++i) { keyframes[i] = (Keyframe)values[i]; } mKeyframeSet = new KeyframeSet(keyframes); } /** * Set the animated values for this object to this set of Objects. * If there is only one value, it is assumed to be the end value of an animation, * and an initial value will be derived, if possible, by calling a getter function * on the object. Also, if any value is null, the value will be filled in when the animation * starts in the same way. This mechanism of automatically getting null values only works * if the PropertyValuesHolder object is used in conjunction * {@link ObjectAnimator}, and with a getter function * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has * no way of determining what the value should be. * * @param values One or more values that the animation will animate between. */ public void setObjectValues(Object... values) { mValueType = values[0].getClass(); mKeyframeSet = KeyframeSet.ofObject(values); } /** * Determine the setter or getter function using the JavaBeans convention of setFoo or * getFoo for a property named 'foo'. This function figures out what the name of the * function should be and uses reflection to find the Method with that name on the * target object. * * @param targetClass The class to search for the method * @param prefix "set" or "get", depending on whether we need a setter or getter. * @param valueType The type of the parameter (in the case of a setter). This type * is derived from the values set on this PropertyValuesHolder. This type is used as * a first guess at the parameter type, but we check for methods with several different * types to avoid problems with slight mis-matches between supplied values and actual * value types used on the setter. * @return Method the method associated with mPropertyName. */ private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) { // TODO: faster implementation... Method returnVal = null; String methodName = getMethodName(prefix, mPropertyName); Class args[] = null; if (valueType == null) { try { returnVal = targetClass.getMethod(methodName, args); } catch (NoSuchMethodException e) { Log.e("PropertyValuesHolder", "Couldn't find no-arg method for property " + mPropertyName + ": " + e); } } else { args = new Class[1]; Class typeVariants[]; if (mValueType.equals(Float.class)) { typeVariants = FLOAT_VARIANTS; } else if (mValueType.equals(Integer.class)) { typeVariants = INTEGER_VARIANTS; } else if (mValueType.equals(Double.class)) { typeVariants = DOUBLE_VARIANTS; } else { typeVariants = new Class[1]; typeVariants[0] = mValueType; } for (Class typeVariant : typeVariants) { args[0] = typeVariant; try { returnVal = targetClass.getMethod(methodName, args); // change the value type to suit mValueType = typeVariant; return returnVal; } catch (NoSuchMethodException e) { // Swallow the error and keep trying other variants } } // If we got here, then no appropriate function was found Log.e("PropertyValuesHolder", "Couldn't find setter/getter for property " + mPropertyName + " with value type "+ mValueType); } return returnVal; } /** * Returns the setter or getter requested. This utility function checks whether the * requested method exists in the propertyMapMap cache. If not, it calls another * utility function to request the Method from the targetClass directly. * @param targetClass The Class on which the requested method should exist. * @param propertyMapMap The cache of setters/getters derived so far. * @param prefix "set" or "get", for the setter or getter. * @param valueType The type of parameter passed into the method (null for getter). * @return Method the method associated with mPropertyName. */ private Method setupSetterOrGetter(Class targetClass, HashMap<Class, HashMap<String, Method>> propertyMapMap, String prefix, Class valueType) { Method setterOrGetter = null; try { // Have to lock property map prior to reading it, to guard against // another thread putting something in there after we've checked it // but before we've added an entry to it mPropertyMapLock.writeLock().lock(); HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass); if (propertyMap != null) { setterOrGetter = propertyMap.get(mPropertyName); } if (setterOrGetter == null) { setterOrGetter = getPropertyFunction(targetClass, prefix, valueType); if (propertyMap == null) { propertyMap = new HashMap<String, Method>(); propertyMapMap.put(targetClass, propertyMap); } propertyMap.put(mPropertyName, setterOrGetter); } } finally { mPropertyMapLock.writeLock().unlock(); } return setterOrGetter; } /** * Utility function to get the setter from targetClass * @param targetClass The Class on which the requested method should exist. */ void setupSetter(Class targetClass) { mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType); } /** * Utility function to get the getter from targetClass */ private void setupGetter(Class targetClass) { mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null); } /** * Internal function (called from ObjectAnimator) to set up the setter and getter * prior to running the animation. If the setter has not been manually set for this * object, it will be derived automatically given the property name, target object, and * types of values supplied. If no getter has been set, it will be supplied iff any of the * supplied values was null. If there is a null value, then the getter (supplied or derived) * will be called to set those null values to the current value of the property * on the target object. * @param target The object on which the setter (and possibly getter) exist. */ void setupSetterAndGetter(Object target) { if (mProperty != null) { // check to make sure that mProperty is on the class of target try { Object testValue = mProperty.get(target); for (Keyframe kf : mKeyframeSet.mKeyframes) { if (!kf.hasValue()) { kf.setValue(mProperty.get(target)); } } return; } catch (ClassCastException e) { Log.e("PropertyValuesHolder","No such property (" + mProperty.getName() + ") on target object " + target + ". Trying reflection instead"); mProperty = null; } } Class targetClass = target.getClass(); if (mSetter == null) { setupSetter(targetClass); } for (Keyframe kf : mKeyframeSet.mKeyframes) { if (!kf.hasValue()) { if (mGetter == null) { setupGetter(targetClass); } try { kf.setValue(mGetter.invoke(target)); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } } } /** * Utility function to set the value stored in a particular Keyframe. The value used is * whatever the value is for the property name specified in the keyframe on the target object. * * @param target The target object from which the current value should be extracted. * @param kf The keyframe which holds the property name and value. */ private void setupValue(Object target, Keyframe kf) { if (mProperty != null) { kf.setValue(mProperty.get(target)); } try { if (mGetter == null) { Class targetClass = target.getClass(); setupGetter(targetClass); } kf.setValue(mGetter.invoke(target)); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } /** * This function is called by ObjectAnimator when setting the start values for an animation. * The start values are set according to the current values in the target object. The * property whose value is extracted is whatever is specified by the propertyName of this * PropertyValuesHolder object. * * @param target The object which holds the start values that should be set. */ void setupStartValue(Object target) { setupValue(target, mKeyframeSet.mKeyframes.get(0)); } /** * This function is called by ObjectAnimator when setting the end values for an animation. * The end values are set according to the current values in the target object. The * property whose value is extracted is whatever is specified by the propertyName of this * PropertyValuesHolder object. * * @param target The object which holds the start values that should be set. */ void setupEndValue(Object target) { setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1)); } @Override public PropertyValuesHolder clone() { try { PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone(); newPVH.mPropertyName = mPropertyName; newPVH.mProperty = mProperty; newPVH.mKeyframeSet = mKeyframeSet.clone(); newPVH.mEvaluator = mEvaluator; return newPVH; } catch (CloneNotSupportedException e) { // won't reach here return null; } } /** * Internal function to set the value on the target object, using the setter set up * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator * to handle turning the value calculated by ValueAnimator into a value set on the object * according to the name of the property. * @param target The target object on which the value is set */ void setAnimatedValue(Object target) { if (mProperty != null) { mProperty.set(target, getAnimatedValue()); } if (mSetter != null) { try { mTmpValueArray[0] = getAnimatedValue(); mSetter.invoke(target, mTmpValueArray); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } } /** * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used * to calculate animated values. */ void init() { if (mEvaluator == null) { // We already handle int and float automatically, but not their Object // equivalents mEvaluator = (mValueType == Integer.class) ? sIntEvaluator : (mValueType == Float.class) ? sFloatEvaluator : null; } if (mEvaluator != null) { // KeyframeSet knows how to evaluate the common types - only give it a custom // evaluator if one has been set on this class mKeyframeSet.setEvaluator(mEvaluator); } } /** * The TypeEvaluator will the automatically determined based on the type of values * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so * desired. This may be important in cases where either the type of the values supplied * do not match the way that they should be interpolated between, or if the values * are of a custom type or one not currently understood by the animation system. Currently, * only values of type float and int (and their Object equivalents: Float * and Integer) are correctly interpolated; all other types require setting a TypeEvaluator. * @param evaluator */ public void setEvaluator(TypeEvaluator evaluator) { mEvaluator = evaluator; mKeyframeSet.setEvaluator(evaluator); } /** * Function used to calculate the value according to the evaluator set up for * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue(). * * @param fraction The elapsed, interpolated fraction of the animation. */ void calculateValue(float fraction) { mAnimatedValue = mKeyframeSet.getValue(fraction); } /** * Sets the name of the property that will be animated. This name is used to derive * a setter function that will be called to set animated values. * For example, a property name of <code>foo</code> will result * in a call to the function <code>setFoo()</code> on the target object. If either * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will * also be derived and called. * * <p>Note that the setter function derived from this property name * must take the same parameter type as the * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to * the setter function will fail.</p> * * @param propertyName The name of the property being animated. */ public void setPropertyName(String propertyName) { mPropertyName = propertyName; } /** * Sets the property that will be animated. * * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property * must exist on the target object specified in that ObjectAnimator.</p> * * @param property The property being animated. */ public void setProperty(Property property) { mProperty = property; } /** * Gets the name of the property that will be animated. This name will be used to derive * a setter function that will be called to set animated values. * For example, a property name of <code>foo</code> will result * in a call to the function <code>setFoo()</code> on the target object. If either * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will * also be derived and called. */ public String getPropertyName() { return mPropertyName; } /** * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value * most recently calculated in calculateValue(). * @return */ Object getAnimatedValue() { return mAnimatedValue; } @Override public String toString() { return mPropertyName + ": " + mKeyframeSet.toString(); } /** * Utility method to derive a setter/getter method name from a property name, where the * prefix is typically "set" or "get" and the first letter of the property name is * capitalized. * * @param prefix The precursor to the method name, before the property name begins, typically * "set" or "get". * @param propertyName The name of the property that represents the bulk of the method name * after the prefix. The first letter of this word will be capitalized in the resulting * method name. * @return String the property name converted to a method name according to the conventions * specified above. */ static String getMethodName(String prefix, String propertyName) { if (propertyName == null || propertyName.length() == 0) { // shouldn't get here return prefix; } char firstLetter = Character.toUpperCase(propertyName.charAt(0)); String theRest = propertyName.substring(1); return prefix + firstLetter + theRest; } static class IntPropertyValuesHolder extends PropertyValuesHolder { // Cache JNI functions to avoid looking them up twice private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = new HashMap<Class, HashMap<String, Integer>>(); int mJniSetter; private IntProperty mIntProperty; IntKeyframeSet mIntKeyframeSet; int mIntAnimatedValue; public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) { super(propertyName); mValueType = int.class; mKeyframeSet = keyframeSet; mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; } public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) { super(property); mValueType = int.class; mKeyframeSet = keyframeSet; mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; if (property instanceof IntProperty) { mIntProperty = (IntProperty) mProperty; } } public IntPropertyValuesHolder(String propertyName, int... values) { super(propertyName); setIntValues(values); } public IntPropertyValuesHolder(Property property, int... values) { super(property); setIntValues(values); if (property instanceof IntProperty) { mIntProperty = (IntProperty) mProperty; } } @Override public void setIntValues(int... values) { super.setIntValues(values); mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; } @Override void calculateValue(float fraction) { mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction); } @Override Object getAnimatedValue() { return mIntAnimatedValue; } @Override public IntPropertyValuesHolder clone() { IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone(); newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet; return newPVH; } /** * Internal function to set the value on the target object, using the setter set up * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator * to handle turning the value calculated by ValueAnimator into a value set on the object * according to the name of the property. * @param target The target object on which the value is set */ @Override void setAnimatedValue(Object target) { if (mIntProperty != null) { mIntProperty.setValue(target, mIntAnimatedValue); return; } if (mProperty != null) { mProperty.set(target, mIntAnimatedValue); return; } if (mJniSetter != 0) { nCallIntMethod(target, mJniSetter, mIntAnimatedValue); return; } if (mSetter != null) { try { mTmpValueArray[0] = mIntAnimatedValue; mSetter.invoke(target, mTmpValueArray); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } } @Override void setupSetter(Class targetClass) { if (mProperty != null) { return; } // Check new static hashmap<propName, int> for setter method try { mPropertyMapLock.writeLock().lock(); HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass); if (propertyMap != null) { Integer mJniSetterInteger = propertyMap.get(mPropertyName); if (mJniSetterInteger != null) { mJniSetter = mJniSetterInteger; } } if (mJniSetter == 0) { String methodName = getMethodName("set", mPropertyName); mJniSetter = nGetIntMethod(targetClass, methodName); if (mJniSetter != 0) { if (propertyMap == null) { propertyMap = new HashMap<String, Integer>(); sJNISetterPropertyMap.put(targetClass, propertyMap); } propertyMap.put(mPropertyName, mJniSetter); } } } catch (NoSuchMethodError e) { Log.d("PropertyValuesHolder", "Can't find native method using JNI, use reflection" + e); } finally { mPropertyMapLock.writeLock().unlock(); } if (mJniSetter == 0) { // Couldn't find method through fast JNI approach - just use reflection super.setupSetter(targetClass); } } } static class FloatPropertyValuesHolder extends PropertyValuesHolder { // Cache JNI functions to avoid looking them up twice private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = new HashMap<Class, HashMap<String, Integer>>(); int mJniSetter; private FloatProperty mFloatProperty; FloatKeyframeSet mFloatKeyframeSet; float mFloatAnimatedValue; public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) { super(propertyName); mValueType = float.class; mKeyframeSet = keyframeSet; mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; } public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) { super(property); mValueType = float.class; mKeyframeSet = keyframeSet; mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; if (property instanceof FloatProperty) { mFloatProperty = (FloatProperty) mProperty; } } public FloatPropertyValuesHolder(String propertyName, float... values) { super(propertyName); setFloatValues(values); } public FloatPropertyValuesHolder(Property property, float... values) { super(property); setFloatValues(values); if (property instanceof FloatProperty) { mFloatProperty = (FloatProperty) mProperty; } } @Override public void setFloatValues(float... values) { super.setFloatValues(values); mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; } @Override void calculateValue(float fraction) { mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction); } @Override Object getAnimatedValue() { return mFloatAnimatedValue; } @Override public FloatPropertyValuesHolder clone() { FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone(); newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet; return newPVH; } /** * Internal function to set the value on the target object, using the setter set up * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator * to handle turning the value calculated by ValueAnimator into a value set on the object * according to the name of the property. * @param target The target object on which the value is set */ @Override void setAnimatedValue(Object target) { if (mFloatProperty != null) { mFloatProperty.setValue(target, mFloatAnimatedValue); return; } if (mProperty != null) { mProperty.set(target, mFloatAnimatedValue); return; } if (mJniSetter != 0) { nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue); return; } if (mSetter != null) { try { mTmpValueArray[0] = mFloatAnimatedValue; mSetter.invoke(target, mTmpValueArray); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } } @Override void setupSetter(Class targetClass) { if (mProperty != null) { return; } // Check new static hashmap<propName, int> for setter method try { mPropertyMapLock.writeLock().lock(); HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass); if (propertyMap != null) { Integer mJniSetterInteger = propertyMap.get(mPropertyName); if (mJniSetterInteger != null) { mJniSetter = mJniSetterInteger; } } if (mJniSetter == 0) { String methodName = getMethodName("set", mPropertyName); mJniSetter = nGetFloatMethod(targetClass, methodName); if (mJniSetter != 0) { if (propertyMap == null) { propertyMap = new HashMap<String, Integer>(); sJNISetterPropertyMap.put(targetClass, propertyMap); } propertyMap.put(mPropertyName, mJniSetter); } } } catch (NoSuchMethodError e) { Log.d("PropertyValuesHolder", "Can't find native method using JNI, use reflection" + e); } finally { mPropertyMapLock.writeLock().unlock(); } if (mJniSetter == 0) { // Couldn't find method through fast JNI approach - just use reflection super.setupSetter(targetClass); } } } native static private int nGetIntMethod(Class targetClass, String methodName); native static private int nGetFloatMethod(Class targetClass, String methodName); native static private void nCallIntMethod(Object target, int methodID, int arg); native static private void nCallFloatMethod(Object target, int methodID, float arg); }