PropertyValuesHolder.java revision a18a86b43e40e3c15dcca0ae0148d641be9b25fe
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.ArrayList;
24import java.util.HashMap;
25import java.util.concurrent.locks.ReentrantReadWriteLock;
26
27/**
28 * This class holds information about a property and the values that that property
29 * should take on during an animation. PropertyValuesHolder objects can be used to create
30 * animations with ValueAnimator or ObjectAnimator that operate on several different properties
31 * in parallel.
32 */
33public class PropertyValuesHolder<T> implements Cloneable {
34
35    /**
36     * The name of the property associated with the values. This need not be a real property,
37     * unless this object is being used with ObjectAnimator. But this is the name by which
38     * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator.
39     */
40    private String mPropertyName;
41
42    /**
43     * The setter function, if needed. ObjectAnimator hands off this functionality to
44     * PropertyValuesHolder, since it holds all of the per-property information. This
45     * property can be manually set via setSetter(). Otherwise, it is automatically
46     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
47     */
48    private Method mSetter = null;
49
50    /**
51     * The getter function, if needed. ObjectAnimator hands off this functionality to
52     * PropertyValuesHolder, since it holds all of the per-property information. This
53     * property can be manually set via setSetter(). Otherwise, it is automatically
54     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
55     * The getter is only derived and used if one of the values is null.
56     */
57    private Method mGetter = null;
58
59    /**
60     * The type of values supplied. This information is used both in deriving the setter/getter
61     * functions and in deriving the type of TypeEvaluator.
62     */
63    private Class mValueType;
64
65    /**
66     * The set of keyframes (time/value pairs) that define this animation.
67     */
68    private KeyframeSet mKeyframeSet = null;
69
70
71    // type evaluators for the three primitive types handled by this implementation
72    private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
73    private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
74    private static final TypeEvaluator sDoubleEvaluator = new DoubleEvaluator();
75
76    // We try several different types when searching for appropriate setter/getter functions.
77    // The caller may have supplied values in a type that does not match the setter/getter
78    // functions (such as the integers 0 and 1 to represent floating point values for alpha).
79    // Also, the use of generics in constructors means that we end up with the Object versions
80    // of primitive types (Float vs. float). But most likely, the setter/getter functions
81    // will take primitive types instead.
82    // So we supply an ordered array of other types to try before giving up.
83    private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
84            Double.class, Integer.class};
85    private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
86            Float.class, Double.class};
87    private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class,
88            Float.class, Integer.class};
89
90    // These maps hold all property entries for a particular class. This map
91    // is used to speed up property/setter/getter lookups for a given class/property
92    // combination. No need to use reflection on the combination more than once.
93    private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap =
94            new HashMap<Class, HashMap<String, Method>>();
95    private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap =
96            new HashMap<Class, HashMap<String, Method>>();
97
98    // This lock is used to ensure that only one thread is accessing the property maps
99    // at a time.
100    private ReentrantReadWriteLock propertyMapLock = new ReentrantReadWriteLock();
101
102    // Used to pass single value to varargs parameter in setter invocation
103    private Object[] mTmpValueArray = new Object[1];
104
105    /**
106     * The type evaluator used to calculate the animated values. This evaluator is determined
107     * automatically based on the type of the start/end objects passed into the constructor,
108     * but the system only knows about the primitive types int, double, and float. Any other
109     * type will need to set the evaluator to a custom evaluator for that type.
110     */
111    private TypeEvaluator mEvaluator;
112
113    /**
114     * The value most recently calculated by calculateValue(). This is set during
115     * that function and might be retrieved later either by ValueAnimator.animatedValue() or
116     * by the property-setting logic in ObjectAnimator.animatedValue().
117     */
118    private Object mAnimatedValue;
119
120    /**
121     * Constructs a PropertyValuesHolder object with just a set of values. This constructor
122     * is typically not used when animating objects with ObjectAnimator, because that
123     * object needs distinct and meaningful property names. Simpler animations of one
124     * set of values using ValueAnimator may use this constructor, however, because no
125     * distinguishing name is needed.
126     * @param values The set of values to animate between. If there is only one value, it
127     * is assumed to be the final value being animated to, and the initial value will be
128     * derived on the fly.
129     */
130    public PropertyValuesHolder(T...values) {
131        this(null, values);
132    }
133
134    /**
135     * Constructs a PropertyValuesHolder object with the specified property name and set of
136     * values. These values can be of any type, but the type should be consistent so that
137     * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
138     * the common type.
139     * <p>If there is only one value, it is assumed to be the end value of an animation,
140     * and an initial value will be derived, if possible, by calling a getter function
141     * on the object. Also, if any value is null, the value will be filled in when the animation
142     * starts in the same way. This mechanism of automatically getting null values only works
143     * if the PropertyValuesHolder object is used in conjunction
144     * {@link ObjectAnimator}, and with a getter function either
145     * derived automatically from <code>propertyName</code> or set explicitly via
146     * {@link #setGetter(java.lang.reflect.Method)}, since otherwise PropertyValuesHolder has
147     * no way of determining what the value should be.
148     * @param propertyName The name of the property associated with this set of values. This
149     * can be the actual property name to be used when using a ObjectAnimator object, or
150     * just a name used to get animated values, such as if this object is used with an
151     * ValueAnimator object.
152     * @param values The set of values to animate between.
153     */
154    public PropertyValuesHolder(String propertyName, T... values) {
155        mPropertyName = propertyName;
156        setValues(values);
157    }
158
159    /**
160     * Sets the values being animated between.
161     * If there is only one value, it is assumed to be the end value of an animation,
162     * and an initial value will be derived, if possible, by calling a getter function
163     * on the object. Also, if any value is null, the value will be filled in when the animation
164     * starts in the same way. This mechanism of automatically getting null values only works
165     * if the PropertyValuesHolder object is used in conjunction
166     * {@link ObjectAnimator}, and with a getter function either
167     * derived automatically from <code>propertyName</code> or set explicitly via
168     * {@link #setGetter(java.lang.reflect.Method)}, since otherwise PropertyValuesHolder has
169     * no way of determining what the value should be.
170     * @param values The set of values to animate between.
171     */
172    public void setValues(T... values) {
173        int numKeyframes = values.length;
174        for (int i = 0; i < numKeyframes; ++i) {
175            if (values[i] != null) {
176                Class thisValueType = values[i].getClass();
177                if (mValueType == null) {
178                    mValueType = thisValueType;
179                } else {
180                    if (thisValueType != mValueType) {
181                        if (mValueType == Integer.class &&
182                                (thisValueType == Float.class || thisValueType == Double.class)) {
183                            mValueType = thisValueType;
184                        } else if (mValueType == Float.class && thisValueType == Double.class) {
185                            mValueType = thisValueType;
186                        }
187                    }
188                }
189            }
190        }
191        Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)];
192        if (mValueType.equals(Keyframe.class)) {
193            mValueType = ((Keyframe)values[0]).getType();
194            for (int i = 0; i < numKeyframes; ++i) {
195                keyframes[i] = (Keyframe)values[i];
196            }
197        } else {
198            if (numKeyframes == 1) {
199                keyframes[0] = new Keyframe(0f, (Object) null);
200                keyframes[1] = new Keyframe(1f, values[0]);
201            } else {
202                keyframes[0] = new Keyframe(0f, values[0]);
203                for (int i = 1; i < numKeyframes; ++i) {
204                    if (values[i] != null && (values[i].getClass() != mValueType)) {
205
206                    }
207                    keyframes[i] = new Keyframe((float) i / (numKeyframes - 1), values[i]);
208                }
209            }
210        }
211        mKeyframeSet = new KeyframeSet(keyframes);
212    }
213
214
215
216    /**
217     * Determine the setter or getter function using the JavaBeans convention of setFoo or
218     * getFoo for a property named 'foo'. This function figures out what the name of the
219     * function should be and uses reflection to find the Method with that name on the
220     * target object.
221     *
222     * @param targetClass The class to search for the method
223     * @param prefix "set" or "get", depending on whether we need a setter or getter.
224     * @param valueType The type of the parameter (in the case of a setter). This type
225     * is derived from the values set on this PropertyValuesHolder. This type is used as
226     * a first guess at the parameter type, but we check for methods with several different
227     * types to avoid problems with slight mis-matches between supplied values and actual
228     * value types used on the setter.
229     * @return Method the method associated with mPropertyName.
230     */
231    private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
232        // TODO: faster implementation...
233        Method returnVal = null;
234        String firstLetter = mPropertyName.substring(0, 1);
235        String theRest = mPropertyName.substring(1);
236        firstLetter = firstLetter.toUpperCase();
237        String methodName = prefix + firstLetter + theRest;
238        Class args[] = null;
239        if (valueType == null) {
240            try {
241                returnVal = targetClass.getMethod(methodName, args);
242            } catch (NoSuchMethodException e) {
243                Log.e("PropertyValuesHolder",
244                        "Couldn't find no-arg method for property " + mPropertyName + ": " + e);
245            }
246        } else {
247            args = new Class[1];
248            Class typeVariants[];
249            if (mValueType.equals(Float.class)) {
250                typeVariants = FLOAT_VARIANTS;
251            } else if (mValueType.equals(Integer.class)) {
252                typeVariants = INTEGER_VARIANTS;
253            } else if (mValueType.equals(Double.class)) {
254                typeVariants = DOUBLE_VARIANTS;
255            } else {
256                typeVariants = new Class[1];
257                typeVariants[0] = mValueType;
258            }
259            for (Class typeVariant : typeVariants) {
260                args[0] = typeVariant;
261                try {
262                    returnVal = targetClass.getMethod(methodName, args);
263                    // change the value type to suit
264                    mValueType = typeVariant;
265                    return returnVal;
266                } catch (NoSuchMethodException e) {
267                    // Swallow the error and keep trying other variants
268                }
269            }
270            // If we got here, then no appropriate function was found
271            Log.e("PropertyValuesHolder",
272                    "Couldn't find setter/getter for property " + mPropertyName +
273                            "with value type "+ mValueType);
274        }
275
276        return returnVal;
277    }
278
279
280    /**
281     * Returns the setter or getter requested. This utility function checks whether the
282     * requested method exists in the propertyMapMap cache. If not, it calls another
283     * utility function to request the Method from the targetClass directly.
284     * @param targetClass The Class on which the requested method should exist.
285     * @param propertyMapMap The cache of setters/getters derived so far.
286     * @param prefix "set" or "get", for the setter or getter.
287     * @param valueType The type of parameter passed into the method (null for getter).
288     * @return Method the method associated with mPropertyName.
289     */
290    private Method setupSetterOrGetter(Class targetClass,
291            HashMap<Class, HashMap<String, Method>> propertyMapMap,
292            String prefix, Class valueType) {
293        Method setterOrGetter = null;
294        try {
295            // Have to lock property map prior to reading it, to guard against
296            // another thread putting something in there after we've checked it
297            // but before we've added an entry to it
298            // TODO: can we store the setter/getter per Class instead of per Object?
299            propertyMapLock.writeLock().lock();
300            HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
301            if (propertyMap != null) {
302                setterOrGetter = propertyMap.get(mPropertyName);
303            }
304            if (setterOrGetter == null) {
305                setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
306                if (propertyMap == null) {
307                    propertyMap = new HashMap<String, Method>();
308                    propertyMapMap.put(targetClass, propertyMap);
309                }
310                propertyMap.put(mPropertyName, setterOrGetter);
311            }
312        } finally {
313            propertyMapLock.writeLock().unlock();
314        }
315        return setterOrGetter;
316    }
317
318    /**
319     * Utility function to get the setter from targetClass
320     * @param targetClass The Class on which the requested method should exist.
321     */
322    private void setupSetter(Class targetClass) {
323        mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType);
324    }
325
326    /**
327     * Utility function to get the getter from targetClass
328     */
329    private void setupGetter(Class targetClass) {
330        mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
331    }
332
333    /**
334     * Internal function (called from ObjectAnimator) to set up the setter and getter
335     * prior to running the animation. If the setter has not been manually set for this
336     * object, it will be derived automatically given the property name, target object, and
337     * types of values supplied. If no getter has been set, it will be supplied iff any of the
338     * supplied values was null. If there is a null value, then the getter (supplied or derived)
339     * will be called to set those null values to the current value of the property
340     * on the target object.
341     * @param target The object on which the setter (and possibly getter) exist.
342     */
343    void setupSetterAndGetter(Object target) {
344        Class targetClass = target.getClass();
345        if (mSetter == null) {
346            setupSetter(targetClass);
347        }
348        for (Keyframe kf : mKeyframeSet.mKeyframes) {
349            if (kf.getValue() == null) {
350                if (mGetter == null) {
351                    setupGetter(targetClass);
352                }
353                try {
354                    kf.setValue((T) mGetter.invoke(target));
355                } catch (InvocationTargetException e) {
356                    Log.e("PropertyValuesHolder", e.toString());
357                } catch (IllegalAccessException e) {
358                    Log.e("PropertyValuesHolder", e.toString());
359                }
360            }
361        }
362    }
363
364    /**
365     * Utility function to set the value stored in a particular Keyframe. The value used is
366     * whatever the value is for the property name specified in the keyframe on the target object.
367     *
368     * @param target The target object from which the current value should be extracted.
369     * @param kf The keyframe which holds the property name and value.
370     */
371    private void setupValue(Object target, Keyframe kf) {
372        try {
373            if (mGetter == null) {
374                Class targetClass = target.getClass();
375                setupGetter(targetClass);
376            }
377            kf.setValue((T) mGetter.invoke(target));
378        } catch (InvocationTargetException e) {
379            Log.e("PropertyValuesHolder", e.toString());
380        } catch (IllegalAccessException e) {
381            Log.e("PropertyValuesHolder", e.toString());
382        }
383    }
384
385    /**
386     * This function is called by ObjectAnimator when setting the start values for an animation.
387     * The start values are set according to the current values in the target object. The
388     * property whose value is extracted is whatever is specified by the propertyName of this
389     * PropertyValuesHolder object.
390     *
391     * @param target The object which holds the start values that should be set.
392     */
393    void setupStartValue(Object target) {
394        setupValue(target, mKeyframeSet.mKeyframes.get(0));
395    }
396
397    /**
398     * This function is called by ObjectAnimator when setting the end values for an animation.
399     * The end values are set according to the current values in the target object. The
400     * property whose value is extracted is whatever is specified by the propertyName of this
401     * PropertyValuesHolder object.
402     *
403     * @param target The object which holds the start values that should be set.
404     */
405    void setupEndValue(Object target) {
406        setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1));
407    }
408
409    @Override
410    public PropertyValuesHolder clone() {
411        ArrayList<Keyframe> keyframes = mKeyframeSet.mKeyframes;
412        int numKeyframes = mKeyframeSet.mKeyframes.size();
413        Keyframe[] newKeyframes = new Keyframe[numKeyframes];
414        for (int i = 0; i < numKeyframes; ++i) {
415            newKeyframes[i] = keyframes.get(i).clone();
416        }
417        PropertyValuesHolder pvhClone = new PropertyValuesHolder(mPropertyName,
418                (Object[]) newKeyframes);
419        return pvhClone;
420    }
421    /**
422     * Internal function to set the value on the target object, using the setter set up
423     * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
424     * to handle turning the value calculated by ValueAnimator into a value set on the object
425     * according to the name of the property.
426     * @param target The target object on which the value is set
427     */
428    void setAnimatedValue(Object target) {
429        if (mSetter != null) {
430            try {
431                mTmpValueArray[0] = mAnimatedValue;
432                mSetter.invoke(target, mTmpValueArray);
433            } catch (InvocationTargetException e) {
434                Log.e("PropertyValuesHolder", e.toString());
435            } catch (IllegalAccessException e) {
436                Log.e("PropertyValuesHolder", e.toString());
437            }
438        }
439    }
440
441    /**
442     * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
443     * to calculate animated values.
444     */
445    void init() {
446        if (mEvaluator == null) {
447            mEvaluator = (mValueType == int.class || mValueType == Integer.class) ? sIntEvaluator :
448                (mValueType == double.class || mValueType == Double.class) ? sDoubleEvaluator :
449                        sFloatEvaluator;
450        }
451    }
452
453    /**
454     * The TypeEvaluator will the automatically determined based on the type of values
455     * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so
456     * desired. This may be important in cases where either the type of the values supplied
457     * do not match the way that they should be interpolated between, or if the values
458     * are of a custom type or one not currently understood by the animation system. Currently,
459     * only values of type float, double, and int (and their Object equivalents, Float, Double,
460     * and Integer) are  correctly interpolated; all other types require setting a TypeEvaluator.
461     * @param evaluator
462     */
463	public void setEvaluator(TypeEvaluator evaluator) {
464        mEvaluator = evaluator;
465    }
466
467    /**
468     * Function used to calculate the value according to the evaluator set up for
469     * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue().
470     *
471     * @param fraction The elapsed, interpolated fraction of the animation.
472     * @return The calculated value at this point in the animation.
473     */
474    Object calculateValue(float fraction) {
475        mAnimatedValue = mKeyframeSet.getValue(fraction, mEvaluator);
476        return mAnimatedValue;
477    }
478
479    /**
480     * Sets the <code>Method</code> that is called with the animated values calculated
481     * during the animation. Setting the setter method is an alternative to supplying a
482     * {@link #setPropertyName(String) propertyName} from which the method is derived. This
483     * approach is more direct, and is especially useful when a function must be called that does
484     * not correspond to the convention of <code>setName()</code>. For example, if a function
485     * called <code>offset()</code> is to be called with the animated values, there is no way
486     * to tell <code>ObjectAnimator</code> how to call that function simply through a property
487     * name, so a setter method should be supplied instead.
488     *
489     * <p>Note that the setter function must take the same parameter type as the
490     * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
491     * the setter function will fail.</p>
492     *
493     * @param setter The setter method that should be called with the animated values.
494     */
495    public void setSetter(Method setter) {
496        mSetter = setter;
497    }
498
499    /**
500     * Gets the <code>Method</code> that is called with the animated values calculated
501     * during the animation.
502     */
503    public Method getSetter() {
504        return mSetter;
505    }
506
507    /**
508     * Sets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
509     * <code>valueTo</code> properties. Setting the getter method is an alternative to supplying a
510     * {@link #setPropertyName(String) propertyName} from which the method is derived. This
511     * approach is more direct, and is especially useful when a function must be called that does
512     * not correspond to the convention of <code>setName()</code>. For example, if a function
513     * called <code>offset()</code> is to be called to get an initial value, there is no way
514     * to tell <code>ObjectAnimator</code> how to call that function simply through a property
515     * name, so a getter method should be supplied instead.
516     *
517     * <p>Note that the getter method is only called whether supplied here or derived
518     * from the property name, if one of <code>valueFrom</code> or <code>valueTo</code> are
519     * null. If both of those values are non-null, then there is no need to get one of the
520     * values and the getter is not called.
521     *
522     * <p>Note that the getter function must return the same parameter type as the
523     * <code>valueFrom</code> and <code>valueTo</code> properties (whichever of them are
524     * non-null), otherwise the call to the getter function will fail.</p>
525     *
526     * @param getter The getter method that should be called to get initial animation values.
527     */
528    public void setGetter(Method getter) {
529        mGetter = getter;
530    }
531
532    /**
533     * Gets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
534     * <code>valueTo</code> properties.
535     */
536    public Method getGetter() {
537        return mGetter;
538    }
539
540    /**
541     * Sets the name of the property that will be animated. This name is used to derive
542     * a setter function that will be called to set animated values.
543     * For example, a property name of <code>foo</code> will result
544     * in a call to the function <code>setFoo()</code> on the target object. If either
545     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
546     * also be derived and called.
547     *
548     * <p>Note that the setter function derived from this property name
549     * must take the same parameter type as the
550     * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
551     * the setter function will fail.</p>
552     *
553     * @param propertyName The name of the property being animated.
554     */
555    public void setPropertyName(String propertyName) {
556        mPropertyName = propertyName;
557    }
558
559    /**
560     * Gets the name of the property that will be animated. This name will be used to derive
561     * a setter function that will be called to set animated values.
562     * For example, a property name of <code>foo</code> will result
563     * in a call to the function <code>setFoo()</code> on the target object. If either
564     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
565     * also be derived and called.
566     */
567    public String getPropertyName() {
568        return mPropertyName;
569    }
570
571    /**
572     * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value
573     * most recently calculated in calculateValue().
574     * @return
575     */
576    Object getAnimatedValue() {
577        return mAnimatedValue;
578    }
579}