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