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