PropertyValuesHolder.java revision db4101c7d5a2af174bdc61dc706329faabaeb5c6
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.animation;
18
19import android.util.FloatProperty;
20import android.util.IntProperty;
21import android.util.Log;
22import android.util.Property;
23
24import java.lang.reflect.InvocationTargetException;
25import java.lang.reflect.Method;
26import java.util.HashMap;
27import java.util.concurrent.locks.ReentrantReadWriteLock;
28
29/**
30 * This class holds information about a property and the values that that property
31 * should take on during an animation. PropertyValuesHolder objects can be used to create
32 * animations with ValueAnimator or ObjectAnimator that operate on several different properties
33 * in parallel.
34 */
35public class PropertyValuesHolder implements Cloneable {
36
37    /**
38     * The name of the property associated with the values. This need not be a real property,
39     * unless this object is being used with ObjectAnimator. But this is the name by which
40     * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator.
41     */
42    String mPropertyName;
43
44    /**
45     * @hide
46     */
47    protected Property mProperty;
48
49    /**
50     * The setter function, if needed. ObjectAnimator hands off this functionality to
51     * PropertyValuesHolder, since it holds all of the per-property information. This
52     * property is automatically
53     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
54     */
55    Method mSetter = null;
56
57    /**
58     * The getter function, if needed. ObjectAnimator hands off this functionality to
59     * PropertyValuesHolder, since it holds all of the per-property information. This
60     * property is automatically
61     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
62     * The getter is only derived and used if one of the values is null.
63     */
64    private Method mGetter = null;
65
66    /**
67     * The type of values supplied. This information is used both in deriving the setter/getter
68     * functions and in deriving the type of TypeEvaluator.
69     */
70    Class mValueType;
71
72    /**
73     * The set of keyframes (time/value pairs) that define this animation.
74     */
75    KeyframeSet mKeyframeSet = null;
76
77
78    // type evaluators for the primitive types handled by this implementation
79    private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
80    private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
81
82    // We try several different types when searching for appropriate setter/getter functions.
83    // The caller may have supplied values in a type that does not match the setter/getter
84    // functions (such as the integers 0 and 1 to represent floating point values for alpha).
85    // Also, the use of generics in constructors means that we end up with the Object versions
86    // of primitive types (Float vs. float). But most likely, the setter/getter functions
87    // will take primitive types instead.
88    // So we supply an ordered array of other types to try before giving up.
89    private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
90            Double.class, Integer.class};
91    private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
92            Float.class, Double.class};
93    private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class,
94            Float.class, Integer.class};
95
96    // These maps hold all property entries for a particular class. This map
97    // is used to speed up property/setter/getter lookups for a given class/property
98    // combination. No need to use reflection on the combination more than once.
99    private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap =
100            new HashMap<Class, HashMap<String, Method>>();
101    private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap =
102            new HashMap<Class, HashMap<String, Method>>();
103
104    // This lock is used to ensure that only one thread is accessing the property maps
105    // at a time.
106    final ReentrantReadWriteLock mPropertyMapLock = new ReentrantReadWriteLock();
107
108    // Used to pass single value to varargs parameter in setter invocation
109    final Object[] mTmpValueArray = new Object[1];
110
111    /**
112     * The type evaluator used to calculate the animated values. This evaluator is determined
113     * automatically based on the type of the start/end objects passed into the constructor,
114     * but the system only knows about the primitive types int and float. Any other
115     * type will need to set the evaluator to a custom evaluator for that type.
116     */
117    private TypeEvaluator mEvaluator;
118
119    /**
120     * The value most recently calculated by calculateValue(). This is set during
121     * that function and might be retrieved later either by ValueAnimator.animatedValue() or
122     * by the property-setting logic in ObjectAnimator.animatedValue().
123     */
124    private Object mAnimatedValue;
125
126    /**
127     * Internal utility constructor, used by the factory methods to set the property name.
128     * @param propertyName The name of the property for this holder.
129     */
130    private PropertyValuesHolder(String propertyName) {
131        mPropertyName = propertyName;
132    }
133
134    /**
135     * Internal utility constructor, used by the factory methods to set the property.
136     * @param property The property for this holder.
137     */
138    private PropertyValuesHolder(Property property) {
139        mProperty = property;
140        if (property != null) {
141            mPropertyName = property.getName();
142        }
143    }
144
145    /**
146     * Constructs and returns a PropertyValuesHolder with a given property name and
147     * set of int values.
148     * @param propertyName The name of the property being animated.
149     * @param values The values that the named property will animate between.
150     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
151     */
152    public static PropertyValuesHolder ofInt(String propertyName, int... values) {
153        return new IntPropertyValuesHolder(propertyName, values);
154    }
155
156    /**
157     * Constructs and returns a PropertyValuesHolder with a given property and
158     * set of int values.
159     * @param property The property being animated. Should not be null.
160     * @param values The values that the property will animate between.
161     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
162     */
163    public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) {
164        return new IntPropertyValuesHolder(property, values);
165    }
166
167    /**
168     * Constructs and returns a PropertyValuesHolder with a given property name and
169     * set of float values.
170     * @param propertyName The name of the property being animated.
171     * @param values The values that the named property will animate between.
172     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
173     */
174    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
175        return new FloatPropertyValuesHolder(propertyName, values);
176    }
177
178    /**
179     * Constructs and returns a PropertyValuesHolder with a given property and
180     * set of float values.
181     * @param property The property being animated. Should not be null.
182     * @param values The values that the property will animate between.
183     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
184     */
185    public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
186        return new FloatPropertyValuesHolder(property, values);
187    }
188
189    /**
190     * Constructs and returns a PropertyValuesHolder with a given property name and
191     * set of Object values. This variant also takes a TypeEvaluator because the system
192     * cannot automatically interpolate between objects of unknown type.
193     *
194     * @param propertyName The name of the property being animated.
195     * @param evaluator A TypeEvaluator that will be called on each animation frame to
196     * provide the necessary interpolation between the Object values to derive the animated
197     * value.
198     * @param values The values that the named property will animate between.
199     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
200     */
201    public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,
202            Object... values) {
203        PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
204        pvh.setObjectValues(values);
205        pvh.setEvaluator(evaluator);
206        return pvh;
207    }
208
209    /**
210     * Constructs and returns a PropertyValuesHolder with a given property and
211     * set of Object values. This variant also takes a TypeEvaluator because the system
212     * cannot automatically interpolate between objects of unknown type.
213     *
214     * @param property The property being animated. Should not be null.
215     * @param evaluator A TypeEvaluator that will be called on each animation frame to
216     * provide the necessary interpolation between the Object values to derive the animated
217     * value.
218     * @param values The values that the property will animate between.
219     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
220     */
221    public static <V> PropertyValuesHolder ofObject(Property property,
222            TypeEvaluator<V> evaluator, V... values) {
223        PropertyValuesHolder pvh = new PropertyValuesHolder(property);
224        pvh.setObjectValues(values);
225        pvh.setEvaluator(evaluator);
226        return pvh;
227    }
228
229    /**
230     * Constructs and returns a PropertyValuesHolder object with the specified property name and set
231     * of values. These values can be of any type, but the type should be consistent so that
232     * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
233     * the common type.
234     * <p>If there is only one value, it is assumed to be the end value of an animation,
235     * and an initial value will be derived, if possible, by calling a getter function
236     * on the object. Also, if any value is null, the value will be filled in when the animation
237     * starts in the same way. This mechanism of automatically getting null values only works
238     * if the PropertyValuesHolder object is used in conjunction
239     * {@link ObjectAnimator}, and with a getter function
240     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
241     * no way of determining what the value should be.
242     * @param propertyName The name of the property associated with this set of values. This
243     * can be the actual property name to be used when using a ObjectAnimator object, or
244     * just a name used to get animated values, such as if this object is used with an
245     * ValueAnimator object.
246     * @param values The set of values to animate between.
247     */
248    public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) {
249        KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
250        if (keyframeSet instanceof IntKeyframeSet) {
251            return new IntPropertyValuesHolder(propertyName, (IntKeyframeSet) keyframeSet);
252        } else if (keyframeSet instanceof FloatKeyframeSet) {
253            return new FloatPropertyValuesHolder(propertyName, (FloatKeyframeSet) keyframeSet);
254        }
255        else {
256            PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
257            pvh.mKeyframeSet = keyframeSet;
258            pvh.mValueType = ((Keyframe)values[0]).getType();
259            return pvh;
260        }
261    }
262
263    /**
264     * Constructs and returns a PropertyValuesHolder object with the specified property and set
265     * of values. These values can be of any type, but the type should be consistent so that
266     * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
267     * the common type.
268     * <p>If there is only one value, it is assumed to be the end value of an animation,
269     * and an initial value will be derived, if possible, by calling the property's
270     * {@link android.util.Property#get(Object)} function.
271     * Also, if any value is null, the value will be filled in when the animation
272     * starts in the same way. This mechanism of automatically getting null values only works
273     * if the PropertyValuesHolder object is used in conjunction with
274     * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has
275     * no way of determining what the value should be.
276     * @param property The property associated with this set of values. Should not be null.
277     * @param values The set of values to animate between.
278     */
279    public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) {
280        KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
281        if (keyframeSet instanceof IntKeyframeSet) {
282            return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet);
283        } else if (keyframeSet instanceof FloatKeyframeSet) {
284            return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet);
285        }
286        else {
287            PropertyValuesHolder pvh = new PropertyValuesHolder(property);
288            pvh.mKeyframeSet = keyframeSet;
289            pvh.mValueType = ((Keyframe)values[0]).getType();
290            return pvh;
291        }
292    }
293
294    /**
295     * Set the animated values for this object to this set of ints.
296     * If there is only one value, it is assumed to be the end value of an animation,
297     * and an initial value will be derived, if possible, by calling a getter function
298     * on the object. Also, if any value is null, the value will be filled in when the animation
299     * starts in the same way. This mechanism of automatically getting null values only works
300     * if the PropertyValuesHolder object is used in conjunction
301     * {@link ObjectAnimator}, and with a getter function
302     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
303     * no way of determining what the value should be.
304     *
305     * @param values One or more values that the animation will animate between.
306     */
307    public void setIntValues(int... values) {
308        mValueType = int.class;
309        mKeyframeSet = KeyframeSet.ofInt(values);
310    }
311
312    /**
313     * Set the animated values for this object to this set of floats.
314     * If there is only one value, it is assumed to be the end value of an animation,
315     * and an initial value will be derived, if possible, by calling a getter function
316     * on the object. Also, if any value is null, the value will be filled in when the animation
317     * starts in the same way. This mechanism of automatically getting null values only works
318     * if the PropertyValuesHolder object is used in conjunction
319     * {@link ObjectAnimator}, and with a getter function
320     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
321     * no way of determining what the value should be.
322     *
323     * @param values One or more values that the animation will animate between.
324     */
325    public void setFloatValues(float... values) {
326        mValueType = float.class;
327        mKeyframeSet = KeyframeSet.ofFloat(values);
328    }
329
330    /**
331     * Set the animated values for this object to this set of Keyframes.
332     *
333     * @param values One or more values that the animation will animate between.
334     */
335    public void setKeyframes(Keyframe... values) {
336        int numKeyframes = values.length;
337        Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)];
338        mValueType = ((Keyframe)values[0]).getType();
339        for (int i = 0; i < numKeyframes; ++i) {
340            keyframes[i] = (Keyframe)values[i];
341        }
342        mKeyframeSet = new KeyframeSet(keyframes);
343    }
344
345    /**
346     * Set the animated values for this object to this set of Objects.
347     * If there is only one value, it is assumed to be the end value of an animation,
348     * and an initial value will be derived, if possible, by calling a getter function
349     * on the object. Also, if any value is null, the value will be filled in when the animation
350     * starts in the same way. This mechanism of automatically getting null values only works
351     * if the PropertyValuesHolder object is used in conjunction
352     * {@link ObjectAnimator}, and with a getter function
353     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
354     * no way of determining what the value should be.
355     *
356     * @param values One or more values that the animation will animate between.
357     */
358    public void setObjectValues(Object... values) {
359        mValueType = values[0].getClass();
360        mKeyframeSet = KeyframeSet.ofObject(values);
361    }
362
363    /**
364     * Determine the setter or getter function using the JavaBeans convention of setFoo or
365     * getFoo for a property named 'foo'. This function figures out what the name of the
366     * function should be and uses reflection to find the Method with that name on the
367     * target object.
368     *
369     * @param targetClass The class to search for the method
370     * @param prefix "set" or "get", depending on whether we need a setter or getter.
371     * @param valueType The type of the parameter (in the case of a setter). This type
372     * is derived from the values set on this PropertyValuesHolder. This type is used as
373     * a first guess at the parameter type, but we check for methods with several different
374     * types to avoid problems with slight mis-matches between supplied values and actual
375     * value types used on the setter.
376     * @return Method the method associated with mPropertyName.
377     */
378    private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
379        // TODO: faster implementation...
380        Method returnVal = null;
381        String methodName = getMethodName(prefix, mPropertyName);
382        Class args[] = null;
383        if (valueType == null) {
384            try {
385                returnVal = targetClass.getMethod(methodName, args);
386            } catch (NoSuchMethodException e) {
387                // Swallow the error, log it later
388            }
389        } else {
390            args = new Class[1];
391            Class typeVariants[];
392            if (mValueType.equals(Float.class)) {
393                typeVariants = FLOAT_VARIANTS;
394            } else if (mValueType.equals(Integer.class)) {
395                typeVariants = INTEGER_VARIANTS;
396            } else if (mValueType.equals(Double.class)) {
397                typeVariants = DOUBLE_VARIANTS;
398            } else {
399                typeVariants = new Class[1];
400                typeVariants[0] = mValueType;
401            }
402            for (Class typeVariant : typeVariants) {
403                args[0] = typeVariant;
404                try {
405                    returnVal = targetClass.getMethod(methodName, args);
406                    // change the value type to suit
407                    mValueType = typeVariant;
408                    return returnVal;
409                } catch (NoSuchMethodException e) {
410                    // Swallow the error and keep trying other variants
411                }
412            }
413            // If we got here, then no appropriate function was found
414        }
415
416        if (returnVal == null) {
417            Log.w("PropertyValuesHolder", "Method " +
418                    getMethodName(prefix, mPropertyName) + "() with type " + mValueType +
419                    " not found on target class " + targetClass);
420        }
421
422        return returnVal;
423    }
424
425
426    /**
427     * Returns the setter or getter requested. This utility function checks whether the
428     * requested method exists in the propertyMapMap cache. If not, it calls another
429     * utility function to request the Method from the targetClass directly.
430     * @param targetClass The Class on which the requested method should exist.
431     * @param propertyMapMap The cache of setters/getters derived so far.
432     * @param prefix "set" or "get", for the setter or getter.
433     * @param valueType The type of parameter passed into the method (null for getter).
434     * @return Method the method associated with mPropertyName.
435     */
436    private Method setupSetterOrGetter(Class targetClass,
437            HashMap<Class, HashMap<String, Method>> propertyMapMap,
438            String prefix, Class valueType) {
439        Method setterOrGetter = null;
440        try {
441            // Have to lock property map prior to reading it, to guard against
442            // another thread putting something in there after we've checked it
443            // but before we've added an entry to it
444            mPropertyMapLock.writeLock().lock();
445            HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
446            if (propertyMap != null) {
447                setterOrGetter = propertyMap.get(mPropertyName);
448            }
449            if (setterOrGetter == null) {
450                setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
451                if (propertyMap == null) {
452                    propertyMap = new HashMap<String, Method>();
453                    propertyMapMap.put(targetClass, propertyMap);
454                }
455                propertyMap.put(mPropertyName, setterOrGetter);
456            }
457        } finally {
458            mPropertyMapLock.writeLock().unlock();
459        }
460        return setterOrGetter;
461    }
462
463    /**
464     * Utility function to get the setter from targetClass
465     * @param targetClass The Class on which the requested method should exist.
466     */
467    void setupSetter(Class targetClass) {
468        mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType);
469    }
470
471    /**
472     * Utility function to get the getter from targetClass
473     */
474    private void setupGetter(Class targetClass) {
475        mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
476    }
477
478    /**
479     * Internal function (called from ObjectAnimator) to set up the setter and getter
480     * prior to running the animation. If the setter has not been manually set for this
481     * object, it will be derived automatically given the property name, target object, and
482     * types of values supplied. If no getter has been set, it will be supplied iff any of the
483     * supplied values was null. If there is a null value, then the getter (supplied or derived)
484     * will be called to set those null values to the current value of the property
485     * on the target object.
486     * @param target The object on which the setter (and possibly getter) exist.
487     */
488    void setupSetterAndGetter(Object target) {
489        if (mProperty != null) {
490            // check to make sure that mProperty is on the class of target
491            try {
492                Object testValue = mProperty.get(target);
493                for (Keyframe kf : mKeyframeSet.mKeyframes) {
494                    if (!kf.hasValue()) {
495                        kf.setValue(mProperty.get(target));
496                    }
497                }
498                return;
499            } catch (ClassCastException e) {
500                Log.w("PropertyValuesHolder","No such property (" + mProperty.getName() +
501                        ") on target object " + target + ". Trying reflection instead");
502                mProperty = null;
503            }
504        }
505        Class targetClass = target.getClass();
506        if (mSetter == null) {
507            setupSetter(targetClass);
508        }
509        for (Keyframe kf : mKeyframeSet.mKeyframes) {
510            if (!kf.hasValue()) {
511                if (mGetter == null) {
512                    setupGetter(targetClass);
513                    if (mGetter == null) {
514                        // Already logged the error - just return to avoid NPE
515                        return;
516                    }
517                }
518                try {
519                    kf.setValue(mGetter.invoke(target));
520                } catch (InvocationTargetException e) {
521                    Log.e("PropertyValuesHolder", e.toString());
522                } catch (IllegalAccessException e) {
523                    Log.e("PropertyValuesHolder", e.toString());
524                }
525            }
526        }
527    }
528
529    /**
530     * Utility function to set the value stored in a particular Keyframe. The value used is
531     * whatever the value is for the property name specified in the keyframe on the target object.
532     *
533     * @param target The target object from which the current value should be extracted.
534     * @param kf The keyframe which holds the property name and value.
535     */
536    private void setupValue(Object target, Keyframe kf) {
537        if (mProperty != null) {
538            kf.setValue(mProperty.get(target));
539        }
540        try {
541            if (mGetter == null) {
542                Class targetClass = target.getClass();
543                setupGetter(targetClass);
544                if (mGetter == null) {
545                    // Already logged the error - just return to avoid NPE
546                    return;
547                }
548            }
549            kf.setValue(mGetter.invoke(target));
550        } catch (InvocationTargetException e) {
551            Log.e("PropertyValuesHolder", e.toString());
552        } catch (IllegalAccessException e) {
553            Log.e("PropertyValuesHolder", e.toString());
554        }
555    }
556
557    /**
558     * This function is called by ObjectAnimator when setting the start values for an animation.
559     * The start values are set according to the current values in the target object. The
560     * property whose value is extracted is whatever is specified by the propertyName of this
561     * PropertyValuesHolder object.
562     *
563     * @param target The object which holds the start values that should be set.
564     */
565    void setupStartValue(Object target) {
566        setupValue(target, mKeyframeSet.mKeyframes.get(0));
567    }
568
569    /**
570     * This function is called by ObjectAnimator when setting the end values for an animation.
571     * The end values are set according to the current values in the target object. The
572     * property whose value is extracted is whatever is specified by the propertyName of this
573     * PropertyValuesHolder object.
574     *
575     * @param target The object which holds the start values that should be set.
576     */
577    void setupEndValue(Object target) {
578        setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1));
579    }
580
581    @Override
582    public PropertyValuesHolder clone() {
583        try {
584            PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone();
585            newPVH.mPropertyName = mPropertyName;
586            newPVH.mProperty = mProperty;
587            newPVH.mKeyframeSet = mKeyframeSet.clone();
588            newPVH.mEvaluator = mEvaluator;
589            return newPVH;
590        } catch (CloneNotSupportedException e) {
591            // won't reach here
592            return null;
593        }
594    }
595
596    /**
597     * Internal function to set the value on the target object, using the setter set up
598     * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
599     * to handle turning the value calculated by ValueAnimator into a value set on the object
600     * according to the name of the property.
601     * @param target The target object on which the value is set
602     */
603    void setAnimatedValue(Object target) {
604        if (mProperty != null) {
605            mProperty.set(target, getAnimatedValue());
606        }
607        if (mSetter != null) {
608            try {
609                mTmpValueArray[0] = getAnimatedValue();
610                mSetter.invoke(target, mTmpValueArray);
611            } catch (InvocationTargetException e) {
612                Log.e("PropertyValuesHolder", e.toString());
613            } catch (IllegalAccessException e) {
614                Log.e("PropertyValuesHolder", e.toString());
615            }
616        }
617    }
618
619    /**
620     * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
621     * to calculate animated values.
622     */
623    void init() {
624        if (mEvaluator == null) {
625            // We already handle int and float automatically, but not their Object
626            // equivalents
627            mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
628                    (mValueType == Float.class) ? sFloatEvaluator :
629                    null;
630        }
631        if (mEvaluator != null) {
632            // KeyframeSet knows how to evaluate the common types - only give it a custom
633            // evaluator if one has been set on this class
634            mKeyframeSet.setEvaluator(mEvaluator);
635        }
636    }
637
638    /**
639     * The TypeEvaluator will the automatically determined based on the type of values
640     * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so
641     * desired. This may be important in cases where either the type of the values supplied
642     * do not match the way that they should be interpolated between, or if the values
643     * are of a custom type or one not currently understood by the animation system. Currently,
644     * only values of type float and int (and their Object equivalents: Float
645     * and Integer) are  correctly interpolated; all other types require setting a TypeEvaluator.
646     * @param evaluator
647     */
648    public void setEvaluator(TypeEvaluator evaluator) {
649        mEvaluator = evaluator;
650        mKeyframeSet.setEvaluator(evaluator);
651    }
652
653    /**
654     * Function used to calculate the value according to the evaluator set up for
655     * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue().
656     *
657     * @param fraction The elapsed, interpolated fraction of the animation.
658     */
659    void calculateValue(float fraction) {
660        mAnimatedValue = mKeyframeSet.getValue(fraction);
661    }
662
663    /**
664     * Sets the name of the property that will be animated. This name is used to derive
665     * a setter function that will be called to set animated values.
666     * For example, a property name of <code>foo</code> will result
667     * in a call to the function <code>setFoo()</code> on the target object. If either
668     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
669     * also be derived and called.
670     *
671     * <p>Note that the setter function derived from this property name
672     * must take the same parameter type as the
673     * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
674     * the setter function will fail.</p>
675     *
676     * @param propertyName The name of the property being animated.
677     */
678    public void setPropertyName(String propertyName) {
679        mPropertyName = propertyName;
680    }
681
682    /**
683     * Sets the property that will be animated.
684     *
685     * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property
686     * must exist on the target object specified in that ObjectAnimator.</p>
687     *
688     * @param property The property being animated.
689     */
690    public void setProperty(Property property) {
691        mProperty = property;
692    }
693
694    /**
695     * Gets the name of the property that will be animated. This name will be used to derive
696     * a setter function that will be called to set animated values.
697     * For example, a property name of <code>foo</code> will result
698     * in a call to the function <code>setFoo()</code> on the target object. If either
699     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
700     * also be derived and called.
701     */
702    public String getPropertyName() {
703        return mPropertyName;
704    }
705
706    /**
707     * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value
708     * most recently calculated in calculateValue().
709     * @return
710     */
711    Object getAnimatedValue() {
712        return mAnimatedValue;
713    }
714
715    @Override
716    public String toString() {
717        return mPropertyName + ": " + mKeyframeSet.toString();
718    }
719
720    /**
721     * Utility method to derive a setter/getter method name from a property name, where the
722     * prefix is typically "set" or "get" and the first letter of the property name is
723     * capitalized.
724     *
725     * @param prefix The precursor to the method name, before the property name begins, typically
726     * "set" or "get".
727     * @param propertyName The name of the property that represents the bulk of the method name
728     * after the prefix. The first letter of this word will be capitalized in the resulting
729     * method name.
730     * @return String the property name converted to a method name according to the conventions
731     * specified above.
732     */
733    static String getMethodName(String prefix, String propertyName) {
734        if (propertyName == null || propertyName.length() == 0) {
735            // shouldn't get here
736            return prefix;
737        }
738        char firstLetter = Character.toUpperCase(propertyName.charAt(0));
739        String theRest = propertyName.substring(1);
740        return prefix + firstLetter + theRest;
741    }
742
743    static class IntPropertyValuesHolder extends PropertyValuesHolder {
744
745        // Cache JNI functions to avoid looking them up twice
746        private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap =
747                new HashMap<Class, HashMap<String, Integer>>();
748        int mJniSetter;
749        private IntProperty mIntProperty;
750
751        IntKeyframeSet mIntKeyframeSet;
752        int mIntAnimatedValue;
753
754        public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) {
755            super(propertyName);
756            mValueType = int.class;
757            mKeyframeSet = keyframeSet;
758            mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
759        }
760
761        public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) {
762            super(property);
763            mValueType = int.class;
764            mKeyframeSet = keyframeSet;
765            mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
766            if (property instanceof  IntProperty) {
767                mIntProperty = (IntProperty) mProperty;
768            }
769        }
770
771        public IntPropertyValuesHolder(String propertyName, int... values) {
772            super(propertyName);
773            setIntValues(values);
774        }
775
776        public IntPropertyValuesHolder(Property property, int... values) {
777            super(property);
778            setIntValues(values);
779            if (property instanceof  IntProperty) {
780                mIntProperty = (IntProperty) mProperty;
781            }
782        }
783
784        @Override
785        public void setIntValues(int... values) {
786            super.setIntValues(values);
787            mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
788        }
789
790        @Override
791        void calculateValue(float fraction) {
792            mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction);
793        }
794
795        @Override
796        Object getAnimatedValue() {
797            return mIntAnimatedValue;
798        }
799
800        @Override
801        public IntPropertyValuesHolder clone() {
802            IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone();
803            newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet;
804            return newPVH;
805        }
806
807        /**
808         * Internal function to set the value on the target object, using the setter set up
809         * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
810         * to handle turning the value calculated by ValueAnimator into a value set on the object
811         * according to the name of the property.
812         * @param target The target object on which the value is set
813         */
814        @Override
815        void setAnimatedValue(Object target) {
816            if (mIntProperty != null) {
817                mIntProperty.setValue(target, mIntAnimatedValue);
818                return;
819            }
820            if (mProperty != null) {
821                mProperty.set(target, mIntAnimatedValue);
822                return;
823            }
824            if (mJniSetter != 0) {
825                nCallIntMethod(target, mJniSetter, mIntAnimatedValue);
826                return;
827            }
828            if (mSetter != null) {
829                try {
830                    mTmpValueArray[0] = mIntAnimatedValue;
831                    mSetter.invoke(target, mTmpValueArray);
832                } catch (InvocationTargetException e) {
833                    Log.e("PropertyValuesHolder", e.toString());
834                } catch (IllegalAccessException e) {
835                    Log.e("PropertyValuesHolder", e.toString());
836                }
837            }
838        }
839
840        @Override
841        void setupSetter(Class targetClass) {
842            if (mProperty != null) {
843                return;
844            }
845            // Check new static hashmap<propName, int> for setter method
846            try {
847                mPropertyMapLock.writeLock().lock();
848                HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass);
849                if (propertyMap != null) {
850                    Integer mJniSetterInteger = propertyMap.get(mPropertyName);
851                    if (mJniSetterInteger != null) {
852                        mJniSetter = mJniSetterInteger;
853                    }
854                }
855                if (mJniSetter == 0) {
856                    String methodName = getMethodName("set", mPropertyName);
857                    mJniSetter = nGetIntMethod(targetClass, methodName);
858                    if (mJniSetter != 0) {
859                        if (propertyMap == null) {
860                            propertyMap = new HashMap<String, Integer>();
861                            sJNISetterPropertyMap.put(targetClass, propertyMap);
862                        }
863                        propertyMap.put(mPropertyName, mJniSetter);
864                    }
865                }
866            } catch (NoSuchMethodError e) {
867                // Couldn't find it via JNI - try reflection next. Probably means the method
868                // doesn't exist, or the type is wrong. An error will be logged later if
869                // reflection fails as well.
870            } finally {
871                mPropertyMapLock.writeLock().unlock();
872            }
873            if (mJniSetter == 0) {
874                // Couldn't find method through fast JNI approach - just use reflection
875                super.setupSetter(targetClass);
876            }
877        }
878    }
879
880    static class FloatPropertyValuesHolder extends PropertyValuesHolder {
881
882        // Cache JNI functions to avoid looking them up twice
883        private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap =
884                new HashMap<Class, HashMap<String, Integer>>();
885        int mJniSetter;
886        private FloatProperty mFloatProperty;
887
888        FloatKeyframeSet mFloatKeyframeSet;
889        float mFloatAnimatedValue;
890
891        public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) {
892            super(propertyName);
893            mValueType = float.class;
894            mKeyframeSet = keyframeSet;
895            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
896        }
897
898        public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) {
899            super(property);
900            mValueType = float.class;
901            mKeyframeSet = keyframeSet;
902            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
903            if (property instanceof FloatProperty) {
904                mFloatProperty = (FloatProperty) mProperty;
905            }
906        }
907
908        public FloatPropertyValuesHolder(String propertyName, float... values) {
909            super(propertyName);
910            setFloatValues(values);
911        }
912
913        public FloatPropertyValuesHolder(Property property, float... values) {
914            super(property);
915            setFloatValues(values);
916            if (property instanceof  FloatProperty) {
917                mFloatProperty = (FloatProperty) mProperty;
918            }
919        }
920
921        @Override
922        public void setFloatValues(float... values) {
923            super.setFloatValues(values);
924            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
925        }
926
927        @Override
928        void calculateValue(float fraction) {
929            mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction);
930        }
931
932        @Override
933        Object getAnimatedValue() {
934            return mFloatAnimatedValue;
935        }
936
937        @Override
938        public FloatPropertyValuesHolder clone() {
939            FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone();
940            newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet;
941            return newPVH;
942        }
943
944        /**
945         * Internal function to set the value on the target object, using the setter set up
946         * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
947         * to handle turning the value calculated by ValueAnimator into a value set on the object
948         * according to the name of the property.
949         * @param target The target object on which the value is set
950         */
951        @Override
952        void setAnimatedValue(Object target) {
953            if (mFloatProperty != null) {
954                mFloatProperty.setValue(target, mFloatAnimatedValue);
955                return;
956            }
957            if (mProperty != null) {
958                mProperty.set(target, mFloatAnimatedValue);
959                return;
960            }
961            if (mJniSetter != 0) {
962                nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
963                return;
964            }
965            if (mSetter != null) {
966                try {
967                    mTmpValueArray[0] = mFloatAnimatedValue;
968                    mSetter.invoke(target, mTmpValueArray);
969                } catch (InvocationTargetException e) {
970                    Log.e("PropertyValuesHolder", e.toString());
971                } catch (IllegalAccessException e) {
972                    Log.e("PropertyValuesHolder", e.toString());
973                }
974            }
975        }
976
977        @Override
978        void setupSetter(Class targetClass) {
979            if (mProperty != null) {
980                return;
981            }
982            // Check new static hashmap<propName, int> for setter method
983            try {
984                mPropertyMapLock.writeLock().lock();
985                HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass);
986                if (propertyMap != null) {
987                    Integer mJniSetterInteger = propertyMap.get(mPropertyName);
988                    if (mJniSetterInteger != null) {
989                        mJniSetter = mJniSetterInteger;
990                    }
991                }
992                if (mJniSetter == 0) {
993                    String methodName = getMethodName("set", mPropertyName);
994                    mJniSetter = nGetFloatMethod(targetClass, methodName);
995                    if (mJniSetter != 0) {
996                        if (propertyMap == null) {
997                            propertyMap = new HashMap<String, Integer>();
998                            sJNISetterPropertyMap.put(targetClass, propertyMap);
999                        }
1000                        propertyMap.put(mPropertyName, mJniSetter);
1001                    }
1002                }
1003            } catch (NoSuchMethodError e) {
1004                // Couldn't find it via JNI - try reflection next. Probably means the method
1005                // doesn't exist, or the type is wrong. An error will be logged later if
1006                // reflection fails as well.
1007            } finally {
1008                mPropertyMapLock.writeLock().unlock();
1009            }
1010            if (mJniSetter == 0) {
1011                // Couldn't find method through fast JNI approach - just use reflection
1012                super.setupSetter(targetClass);
1013            }
1014        }
1015
1016    }
1017
1018    native static private int nGetIntMethod(Class targetClass, String methodName);
1019    native static private int nGetFloatMethod(Class targetClass, String methodName);
1020    native static private void nCallIntMethod(Object target, int methodID, int arg);
1021    native static private void nCallFloatMethod(Object target, int methodID, float arg);
1022}