1 /*
2  * Copyright 2018 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 
17 package androidx.core.animation;
18 
19 import android.annotation.SuppressLint;
20 import android.graphics.Path;
21 import android.graphics.PointF;
22 import android.util.Log;
23 import android.util.Property;
24 
25 import org.jspecify.annotations.NonNull;
26 import org.jspecify.annotations.Nullable;
27 
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.util.HashMap;
31 import java.util.List;
32 
33 /**
34  * This class holds information about a property and the values that that property
35  * should take on during an animation. PropertyValuesHolder objects can be used to create
36  * animations with ValueAnimator or ObjectAnimator that operate on several different properties
37  * in parallel.
38  */
39 @SuppressWarnings("unchecked")
40 public class PropertyValuesHolder implements Cloneable {
41 
42     /**
43      * The name of the property associated with the values. This need not be a real property,
44      * unless this object is being used with ObjectAnimator. But this is the name by which
45      * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator.
46      */
47     String mPropertyName;
48 
49     Property mProperty;
50 
51     /**
52      * The setter function, if needed. ObjectAnimator hands off this functionality to
53      * PropertyValuesHolder, since it holds all of the per-property information. This
54      * property is automatically
55      * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
56      */
57     Method mSetter = null;
58 
59     /**
60      * The getter function, if needed. ObjectAnimator hands off this functionality to
61      * PropertyValuesHolder, since it holds all of the per-property information. This
62      * property is automatically
63      * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
64      * The getter is only derived and used if one of the values is null.
65      */
66     private Method mGetter = null;
67 
68     /**
69      * The type of values supplied. This information is used both in deriving the setter/getter
70      * functions and in deriving the type of TypeEvaluator.
71      */
72     Class<?> mValueType;
73 
74     /**
75      * The set of keyframes (time/value pairs) that define this animation.
76      */
77     Keyframes mKeyframes = null;
78 
79     // We try several different types when searching for appropriate setter/getter functions.
80     // The caller may have supplied values in a type that does not match the setter/getter
81     // functions (such as the integers 0 and 1 to represent floating point values for alpha).
82     // Also, the use of generics in constructors means that we end up with the Object versions
83     // of primitive types (Float vs. float). But most likely, the setter/getter functions
84     // will take primitive types instead.
85     // So we supply an ordered array of other types to try before giving up.
86     private static final Class<?>[] FLOAT_VARIANTS = {float.class, Float.class, double.class,
87             int.class, Double.class, Integer.class};
88     private static final Class<?>[] INTEGER_VARIANTS = {int.class, Integer.class, float.class,
89             double.class, Float.class, Double.class};
90     private static final Class<?>[] DOUBLE_VARIANTS = {double.class, Double.class, float.class,
91             int.class, Float.class, Integer.class};
92 
93     // These maps hold all property entries for a particular class. This map
94     // is used to speed up property/setter/getter lookups for a given class/property
95     // combination. No need to use reflection on the combination more than once.
96     static final HashMap<Class<?>, HashMap<String, Method>> sSetterPropertyMap =
97             new HashMap<>();
98     private static final HashMap<Class<?>, HashMap<String, Method>> sGetterPropertyMap =
99             new HashMap<>();
100 
101     // Used to pass single value to varargs parameter in setter invocation
102     final Object[] mTmpValueArray = new Object[1];
103 
104     /**
105      * The type evaluator used to calculate the animated values. This evaluator is determined
106      * automatically based on the type of the start/end objects passed into the constructor,
107      * but the system only knows about the primitive types int and float. Any other
108      * type will need to set the evaluator to a custom evaluator for that type.
109      */
110     private TypeEvaluator mEvaluator;
111 
112     /**
113      * The value most recently calculated by calculateValue(). This is set during
114      * that function and might be retrieved later either by ValueAnimator.animatedValue() or
115      * by the property-setting logic in ObjectAnimator.animatedValue().
116      */
117     private Object mAnimatedValue;
118 
119     /**
120      * Converts from the source Object type to the setter Object type.
121      */
122     private TypeConverter mConverter;
123 
124     /**
125      * Internal utility constructor, used by the factory methods to set the property name.
126      * @param propertyName The name of the property for this holder.
127      */
PropertyValuesHolder(String propertyName)128     PropertyValuesHolder(String propertyName) {
129         mPropertyName = propertyName;
130     }
131 
132     /**
133      * Internal utility constructor, used by the factory methods to set the property.
134      * @param property The property for this holder.
135      */
PropertyValuesHolder(Property property)136     PropertyValuesHolder(Property property) {
137         mProperty = property;
138         if (property != null) {
139             mPropertyName = property.getName();
140         }
141     }
142 
143     /**
144      * Constructs and returns a PropertyValuesHolder with a given property name and
145      * set of int values.
146      * @param propertyName The name of the property being animated.
147      * @param values The values that the named property will animate between.
148      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
149      */
ofInt(@onNull String propertyName, int @NonNull ... values)150     public static @NonNull PropertyValuesHolder ofInt(@NonNull String propertyName,
151             int @NonNull ... values) {
152         return new IntPropertyValuesHolder(propertyName, values);
153     }
154 
155     /**
156      * Constructs and returns a PropertyValuesHolder with a given property and
157      * set of int values.
158      * @param property The property being animated. Should not be null.
159      * @param values The values that the property will animate between.
160      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
161      */
ofInt(@onNull Property<?, Integer> property, int @NonNull ... values)162     public static @NonNull PropertyValuesHolder ofInt(@NonNull Property<?, Integer> property,
163             int @NonNull ... values) {
164         return new IntPropertyValuesHolder(property, values);
165     }
166 
167     /**
168      * Constructs and returns a PropertyValuesHolder with a given property name and
169      * set of <code>int[]</code> values. At least two <code>int[]</code> values must be supplied,
170      * a start and end value. If more values are supplied, the values will be animated from the
171      * start, through all intermediate values to the end value. When used with ObjectAnimator,
172      * the elements of the array represent the parameters of the setter function.
173      *
174      * @param propertyName The name of the property being animated. Can also be the
175      *                     case-sensitive name of the entire setter method. Should not be null.
176      * @param values The values that the property will animate between.
177      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
178      * @see IntArrayEvaluator#IntArrayEvaluator(int[])
179      * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[])
180      */
ofMultiInt( @onNull String propertyName, @SuppressLint("ArrayReturn") int @NonNull [][] values )181     public static @NonNull PropertyValuesHolder ofMultiInt(
182             @NonNull String propertyName,
183             @SuppressLint("ArrayReturn") /* Platform API */ int @NonNull [][] values
184     ) {
185         if (values.length < 2) {
186             throw new IllegalArgumentException("At least 2 values must be supplied");
187         }
188         int numParameters = 0;
189         for (int i = 0; i < values.length; i++) {
190             if (values[i] == null) {
191                 throw new IllegalArgumentException("values must not be null");
192             }
193             int length = values[i].length;
194             if (i == 0) {
195                 numParameters = length;
196             } else if (length != numParameters) {
197                 throw new IllegalArgumentException("Values must all have the same length");
198             }
199         }
200         IntArrayEvaluator evaluator = new IntArrayEvaluator(new int[numParameters]);
201         return new MultiIntValuesHolder(propertyName, null, evaluator, (Object[]) values);
202     }
203 
204     /**
205      * Constructs and returns a PropertyValuesHolder with a given property name to use
206      * as a multi-int setter. The values are animated along the path, with the first
207      * parameter of the setter set to the x coordinate and the second set to the y coordinate.
208      *
209      * @param propertyName The name of the property being animated. Can also be the
210      *                     case-sensitive name of the entire setter method. Should not be null.
211      *                     The setter must take exactly two <code>int</code> parameters.
212      * @param path The Path along which the values should be animated.
213      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
214      * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
215      */
ofMultiInt(@onNull String propertyName, @NonNull Path path)216     public static @NonNull PropertyValuesHolder ofMultiInt(@NonNull String propertyName,
217             @NonNull Path path) {
218         Keyframes keyframes = KeyframeSet.ofPath(path);
219         PointFToIntArray converter = new PointFToIntArray();
220         return new MultiIntValuesHolder(propertyName, converter, null, keyframes);
221     }
222 
223     /**
224      * Constructs and returns a PropertyValuesHolder with a given property and
225      * set of Object values for use with ObjectAnimator multi-value setters. The Object
226      * values are converted to <code>int[]</code> using the converter.
227      *
228      * @param propertyName The property being animated or complete name of the setter.
229      *                     Should not be null.
230      * @param converter Used to convert the animated value to setter parameters.
231      * @param evaluator A TypeEvaluator that will be called on each animation frame to
232      * provide the necessary interpolation between the Object values to derive the animated
233      * value.
234      * @param values The values that the property will animate between.
235      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
236      * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[])
237      * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
238      */
239     @SafeVarargs
ofMultiInt(@onNull String propertyName, @NonNull TypeConverter<V, int[]> converter, @NonNull TypeEvaluator<V> evaluator, V @NonNull ... values)240     public static <V> @NonNull PropertyValuesHolder ofMultiInt(@NonNull String propertyName,
241             @NonNull TypeConverter<V, int[]> converter,
242             @NonNull TypeEvaluator<V> evaluator, V @NonNull ... values) {
243         return new MultiIntValuesHolder(propertyName, converter, evaluator, values);
244     }
245 
246     /**
247      * Constructs and returns a PropertyValuesHolder object with the specified property name or
248      * setter name for use in a multi-int setter function using ObjectAnimator. The values can be
249      * of any type, but the type should be consistent so that the supplied
250      * {@link TypeEvaluator} can be used to to evaluate the animated value. The
251      * <code>converter</code> converts the values to parameters in the setter function.
252      *
253      * <p>At least two values must be supplied, a start and an end value.</p>
254      *
255      * @param propertyName The name of the property to associate with the set of values. This
256      *                     may also be the complete name of a setter function.
257      * @param converter    Converts <code>values</code> into int parameters for the setter.
258      *                     Can be null if the Keyframes have int[] values.
259      * @param evaluator    Used to interpolate between values.
260      * @param values       The values at specific fractional times to evaluate between
261      * @return A PropertyValuesHolder for a multi-int parameter setter.
262      */
263     @SafeVarargs
ofMultiInt(@onNull String propertyName, @Nullable TypeConverter<T, int[]> converter, @NonNull TypeEvaluator<T> evaluator, Keyframe @NonNull ... values)264     public static <T> @NonNull PropertyValuesHolder ofMultiInt(@NonNull String propertyName,
265             @Nullable TypeConverter<T, int[]> converter, @NonNull TypeEvaluator<T> evaluator,
266             Keyframe @NonNull ... values) {
267         KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
268         return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet);
269     }
270 
271     /**
272      * Constructs and returns a PropertyValuesHolder with a given property name and
273      * set of float values.
274      * @param propertyName The name of the property being animated.
275      * @param values The values that the named property will animate between.
276      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
277      */
ofFloat(@onNull String propertyName, float @NonNull ... values)278     public static @NonNull PropertyValuesHolder ofFloat(@NonNull String propertyName,
279             float @NonNull ... values) {
280         return new FloatPropertyValuesHolder(propertyName, values);
281     }
282 
283     /**
284      * Constructs and returns a PropertyValuesHolder with a given property and
285      * set of float values.
286      * @param property The property being animated. Should not be null.
287      * @param values The values that the property will animate between.
288      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
289      */
ofFloat(@onNull Property<?, Float> property, float @NonNull ... values)290     public static @NonNull PropertyValuesHolder ofFloat(@NonNull Property<?, Float> property,
291             float @NonNull ... values) {
292         return new FloatPropertyValuesHolder(property, values);
293     }
294 
295     /**
296      * Constructs and returns a PropertyValuesHolder with a given property name and set of
297      * <code>float[]</code> values. At least two <code>float[]</code> values must be supplied,
298      * a start and end value. If more values are supplied, the values will be animated from the
299      * start, through all intermediate values to the end value. When used with ObjectAnimator,
300      * the elements of the array represent the parameters of the setter function.
301      *
302      * @param propertyName The name of the property being animated. Can also be the
303      *                     case-sensitive name of the entire setter method. Should not be null.
304      * @param values The values that the property will animate between.
305      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
306      * @see FloatArrayEvaluator#FloatArrayEvaluator(float[])
307      * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[])
308      */
ofMultiFloat( @onNull String propertyName, @SuppressLint("ArrayReturn") float @NonNull [][] values )309     public static @NonNull PropertyValuesHolder ofMultiFloat(
310             @NonNull String propertyName,
311             @SuppressLint("ArrayReturn") /* Platform API */ float @NonNull [][] values
312     ) {
313         if (values.length < 2) {
314             throw new IllegalArgumentException("At least 2 values must be supplied");
315         }
316         int numParameters = 0;
317         for (int i = 0; i < values.length; i++) {
318             if (values[i] == null) {
319                 throw new IllegalArgumentException("values must not be null");
320             }
321             int length = values[i].length;
322             if (i == 0) {
323                 numParameters = length;
324             } else if (length != numParameters) {
325                 throw new IllegalArgumentException("Values must all have the same length");
326             }
327         }
328         FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[numParameters]);
329         return new MultiFloatValuesHolder(propertyName, null, evaluator, (Object[]) values);
330     }
331 
332     /**
333      * Constructs and returns a PropertyValuesHolder with a given property name to use
334      * as a multi-float setter. The values are animated along the path, with the first
335      * parameter of the setter set to the x coordinate and the second set to the y coordinate.
336      *
337      * @param propertyName The name of the property being animated. Can also be the
338      *                     case-sensitive name of the entire setter method. Should not be null.
339      *                     The setter must take exactly two <code>float</code> parameters.
340      * @param path The Path along which the values should be animated.
341      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
342      * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
343      */
ofMultiFloat(@onNull String propertyName, @NonNull Path path)344     public static @NonNull PropertyValuesHolder ofMultiFloat(@NonNull String propertyName,
345             @NonNull Path path) {
346         Keyframes keyframes = KeyframeSet.ofPath(path);
347         PointFToFloatArray converter = new PointFToFloatArray();
348         return new MultiFloatValuesHolder(propertyName, converter, null, keyframes);
349     }
350 
351     /**
352      * Constructs and returns a PropertyValuesHolder with a given property and
353      * set of Object values for use with ObjectAnimator multi-value setters. The Object
354      * values are converted to <code>float[]</code> using the converter.
355      *
356      * @param propertyName The property being animated or complete name of the setter.
357      *                     Should not be null.
358      * @param converter Used to convert the animated value to setter parameters.
359      * @param evaluator A TypeEvaluator that will be called on each animation frame to
360      * provide the necessary interpolation between the Object values to derive the animated
361      * value.
362      * @param values The values that the property will animate between.
363      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
364      * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[])
365      */
366     @SafeVarargs
ofMultiFloat(@onNull String propertyName, @NonNull TypeConverter<V, float[]> converter, @NonNull TypeEvaluator<V> evaluator, V @NonNull ... values)367     public static <V> @NonNull PropertyValuesHolder ofMultiFloat(@NonNull String propertyName,
368             @NonNull TypeConverter<V, float[]> converter,
369             @NonNull TypeEvaluator<V> evaluator, V @NonNull ... values) {
370         return new MultiFloatValuesHolder(propertyName, converter, evaluator, values);
371     }
372 
373     /**
374      * Constructs and returns a PropertyValuesHolder object with the specified property name or
375      * setter name for use in a multi-float setter function using ObjectAnimator. The values can be
376      * of any type, but the type should be consistent so that the supplied
377      * {@link TypeEvaluator} can be used to to evaluate the animated value. The
378      * <code>converter</code> converts the values to parameters in the setter function.
379      *
380      * <p>At least two values must be supplied, a start and an end value.</p>
381      *
382      * @param propertyName The name of the property to associate with the set of values. This
383      *                     may also be the complete name of a setter function.
384      * @param converter    Converts <code>values</code> into float parameters for the setter.
385      *                     Can be null if the Keyframes have float[] values.
386      * @param evaluator    Used to interpolate between values.
387      * @param values       The values at specific fractional times to evaluate between
388      * @return A PropertyValuesHolder for a multi-float parameter setter.
389      */
390     @SafeVarargs
ofMultiFloat(@onNull String propertyName, @Nullable TypeConverter<T, float[]> converter, @NonNull TypeEvaluator<T> evaluator, Keyframe @NonNull ... values)391     public static <T> @NonNull PropertyValuesHolder ofMultiFloat(@NonNull String propertyName,
392             @Nullable TypeConverter<T, float[]> converter,
393             @NonNull TypeEvaluator<T> evaluator,
394             Keyframe @NonNull ... values) {
395         KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
396         return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet);
397     }
398 
399     /**
400      * Constructs and returns a PropertyValuesHolder with a given property name and
401      * set of Object values. This variant also takes a TypeEvaluator because the system
402      * cannot automatically interpolate between objects of unknown type.
403      *
404      * <p><strong>Note:</strong> The Object values are stored as references to the original
405      * objects, which means that changes to those objects after this method is called will
406      * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
407      * after this method is called, callers should pass a copy of those objects instead.
408      *
409      * @param propertyName The name of the property being animated.
410      * @param evaluator A TypeEvaluator that will be called on each animation frame to
411      * provide the necessary interpolation between the Object values to derive the animated
412      * value.
413      * @param values The values that the named property will animate between.
414      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
415      */
ofObject(@onNull String propertyName, @NonNull TypeEvaluator evaluator, Object @NonNull ... values)416     public static @NonNull PropertyValuesHolder ofObject(@NonNull String propertyName,
417             @NonNull TypeEvaluator evaluator, Object @NonNull ... values) {
418         PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
419         pvh.setObjectValues(values);
420         pvh.setEvaluator(evaluator);
421         return pvh;
422     }
423 
424     /**
425      * Constructs and returns a PropertyValuesHolder with a given property name and
426      * a Path along which the values should be animated. This variant supports a
427      * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
428      * type.
429      *
430      * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
431      * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
432      * not be stored by the setter or TypeConverter.</p>
433      *
434      * @param propertyName The name of the property being animated.
435      * @param converter Converts a PointF to the type associated with the setter. May be
436      *                  null if conversion is unnecessary.
437      * @param path The Path along which the values should be animated.
438      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
439      */
ofObject(@onNull String propertyName, @Nullable TypeConverter<PointF, ?> converter, @NonNull Path path)440     public static @NonNull PropertyValuesHolder ofObject(@NonNull String propertyName,
441             @Nullable TypeConverter<PointF, ?> converter, @NonNull Path path) {
442         PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
443         pvh.mKeyframes = KeyframeSet.ofPath(path);
444         pvh.mValueType = PointF.class;
445         pvh.setConverter(converter);
446         return pvh;
447     }
448 
449     /**
450      * Constructs and returns a PropertyValuesHolder with a given property and
451      * set of Object values. This variant also takes a TypeEvaluator because the system
452      * cannot automatically interpolate between objects of unknown type.
453      *
454      * <p><strong>Note:</strong> The Object values are stored as references to the original
455      * objects, which means that changes to those objects after this method is called will
456      * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
457      * after this method is called, callers should pass a copy of those objects instead.
458      *
459      * @param property The property being animated. Should not be null.
460      * @param evaluator A TypeEvaluator that will be called on each animation frame to
461      * provide the necessary interpolation between the Object values to derive the animated
462      * value.
463      * @param values The values that the property will animate between.
464      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
465      */
466     @SafeVarargs
ofObject(@onNull Property property, @NonNull TypeEvaluator<V> evaluator, V @NonNull ... values)467     public static <V> @NonNull PropertyValuesHolder ofObject(@NonNull Property property,
468             @NonNull TypeEvaluator<V> evaluator, V @NonNull ... values) {
469         PropertyValuesHolder pvh = new PropertyValuesHolder(property);
470         pvh.setObjectValues(values);
471         pvh.setEvaluator(evaluator);
472         return pvh;
473     }
474 
475     /**
476      * Constructs and returns a PropertyValuesHolder with a given property and
477      * set of Object values. This variant also takes a TypeEvaluator because the system
478      * cannot automatically interpolate between objects of unknown type. This variant also
479      * takes a <code>TypeConverter</code> to convert from animated values to the type
480      * of the property. If only one value is supplied, the <code>TypeConverter</code>
481      * must be a {@link BidirectionalTypeConverter} to retrieve the current
482      * value.
483      *
484      * <p><strong>Note:</strong> The Object values are stored as references to the original
485      * objects, which means that changes to those objects after this method is called will
486      * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
487      * after this method is called, callers should pass a copy of those objects instead.
488      *
489      * @param property The property being animated. Should not be null.
490      * @param converter Converts the animated object to the Property type.
491      * @param evaluator A TypeEvaluator that will be called on each animation frame to provide the
492      *                 necessary interpolation between the Object values to derive the animated
493      *                 value.
494      * @param values The values that the property will animate between.
495      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
496      * @see #setConverter(TypeConverter)
497      * @see TypeConverter
498      */
499     @SafeVarargs
ofObject(@onNull Property<?, V> property, @NonNull TypeConverter<T, V> converter, @NonNull TypeEvaluator<T> evaluator, T @NonNull ... values)500     public static <T, V> @NonNull PropertyValuesHolder ofObject(@NonNull Property<?, V> property,
501             @NonNull TypeConverter<T, V> converter, @NonNull TypeEvaluator<T> evaluator,
502             T @NonNull ... values) {
503         PropertyValuesHolder pvh = new PropertyValuesHolder(property);
504         pvh.setConverter(converter);
505         pvh.setObjectValues(values);
506         pvh.setEvaluator(evaluator);
507         return pvh;
508     }
509 
510     /**
511      * Constructs and returns a PropertyValuesHolder with a given property and
512      * a Path along which the values should be animated. This variant supports a
513      * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
514      * type.
515      *
516      * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
517      * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
518      * not be stored by the setter or TypeConverter.</p>
519      *
520      * @param property The property being animated. Should not be null.
521      * @param converter Converts a PointF to the type associated with the setter. May be
522      *                  null if conversion is unnecessary.
523      * @param path The Path along which the values should be animated.
524      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
525      */
ofObject(@onNull Property<?, V> property, @Nullable TypeConverter<PointF, V> converter, @NonNull Path path)526     public static <V> @NonNull PropertyValuesHolder ofObject(@NonNull Property<?, V> property,
527             @Nullable TypeConverter<PointF, V> converter, @NonNull Path path) {
528         PropertyValuesHolder pvh = new PropertyValuesHolder(property);
529         pvh.mKeyframes = KeyframeSet.ofPath(path);
530         pvh.mValueType = PointF.class;
531         pvh.setConverter(converter);
532         return pvh;
533     }
534 
535     /**
536      * Constructs and returns a PropertyValuesHolder object with the specified property name and set
537      * of values. These values can be of any type, but the type should be consistent so that
538      * an appropriate {@link TypeEvaluator} can be found that matches
539      * the common type.
540      * <p>If there is only one value, it is assumed to be the end value of an animation,
541      * and an initial value will be derived, if possible, by calling a getter function
542      * on the object. Also, if any value is null, the value will be filled in when the animation
543      * starts in the same way. This mechanism of automatically getting null values only works
544      * if the PropertyValuesHolder object is used in conjunction {@link ObjectAnimator}, and with a
545      * getter function derived automatically from <code>propertyName</code>, since otherwise
546      * PropertyValuesHolder has no way of determining what the value should be.
547      * @param propertyName The name of the property associated with this set of values. This
548      * can be the actual property name to be used when using a ObjectAnimator object, or
549      * just a name used to get animated values, such as if this object is used with an
550      * ValueAnimator object.
551      * @param values The set of values to animate between.
552      */
553     @SafeVarargs
ofKeyframe(@onNull String propertyName, Keyframe @NonNull ... values)554     public static @NonNull PropertyValuesHolder ofKeyframe(@NonNull String propertyName,
555             Keyframe @NonNull ... values) {
556         KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
557         return ofKeyframes(propertyName, keyframeSet);
558     }
559 
560     /**
561      * Constructs and returns a PropertyValuesHolder object with the specified property and set
562      * of values. These values can be of any type, but the type should be consistent so that
563      * an appropriate {@link androidx.core.animation.TypeEvaluator} can be found that matches
564      * the common type.
565      * <p>If there is only one value, it is assumed to be the end value of an animation,
566      * and an initial value will be derived, if possible, by calling the property's
567      * {@link android.util.Property#get(Object)} function.
568      * Also, if any value is null, the value will be filled in when the animation
569      * starts in the same way. This mechanism of automatically getting null values only works
570      * if the PropertyValuesHolder object is used in conjunction with
571      * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has
572      * no way of determining what the value should be.
573      * @param property The property associated with this set of values. Should not be null.
574      * @param values The set of values to animate between.
575      */
576     @SafeVarargs
ofKeyframe(@onNull Property property, Keyframe @NonNull ... values)577     public static @NonNull PropertyValuesHolder ofKeyframe(@NonNull Property property,
578             Keyframe @NonNull ... values) {
579         KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
580         return ofKeyframes(property, keyframeSet);
581     }
582 
ofKeyframes(String propertyName, Keyframes keyframes)583     static PropertyValuesHolder ofKeyframes(String propertyName, Keyframes keyframes) {
584         if (keyframes instanceof Keyframes.IntKeyframes) {
585             return new IntPropertyValuesHolder(propertyName, (Keyframes.IntKeyframes) keyframes);
586         } else if (keyframes instanceof Keyframes.FloatKeyframes) {
587             return new FloatPropertyValuesHolder(propertyName,
588                     (Keyframes.FloatKeyframes) keyframes);
589         } else {
590             PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
591             pvh.mKeyframes = keyframes;
592             pvh.mValueType = keyframes.getType();
593             return pvh;
594         }
595     }
596 
ofKeyframes(Property property, Keyframes keyframes)597     static PropertyValuesHolder ofKeyframes(Property property, Keyframes keyframes) {
598         if (keyframes instanceof Keyframes.IntKeyframes) {
599             return new IntPropertyValuesHolder(property, (Keyframes.IntKeyframes) keyframes);
600         } else if (keyframes instanceof Keyframes.FloatKeyframes) {
601             return new FloatPropertyValuesHolder(property, (Keyframes.FloatKeyframes) keyframes);
602         } else {
603             PropertyValuesHolder pvh = new PropertyValuesHolder(property);
604             pvh.mKeyframes = keyframes;
605             pvh.mValueType = keyframes.getType();
606             return pvh;
607         }
608     }
609 
610     /**
611      * Set the animated values for this object to this set of ints.
612      * If there is only one value, it is assumed to be the end value of an animation,
613      * and an initial value will be derived, if possible, by calling a getter function
614      * on the object. Also, if any value is null, the value will be filled in when the animation
615      * starts in the same way. This mechanism of automatically getting null values only works
616      * if the PropertyValuesHolder object is used in conjunction {@link ObjectAnimator}, and with a
617      * getter function derived automatically from <code>propertyName</code>, since otherwise
618      * PropertyValuesHolder has no way of determining what the value should be.
619      *
620      * @param values One or more values that the animation will animate between.
621      */
setIntValues(int @NonNull ... values)622     public void setIntValues(int @NonNull ... values) {
623         mValueType = int.class;
624         mKeyframes = KeyframeSet.ofInt(values);
625     }
626 
627     /**
628      * Set the animated values for this object to this set of floats.
629      * If there is only one value, it is assumed to be the end value of an animation,
630      * and an initial value will be derived, if possible, by calling a getter function
631      * on the object. Also, if any value is null, the value will be filled in when the animation
632      * starts in the same way. This mechanism of automatically getting null values only works
633      * if the PropertyValuesHolder object is used in conjunction {@link ObjectAnimator}, and with a
634      * getter function derived automatically from <code>propertyName</code>, since otherwise
635      * PropertyValuesHolder has no way of determining what the value should be.
636      *
637      * @param values One or more values that the animation will animate between.
638      */
setFloatValues(float @NonNull ... values)639     public void setFloatValues(float @NonNull ... values) {
640         mValueType = float.class;
641         mKeyframes = KeyframeSet.ofFloat(values);
642     }
643 
644     /**
645      * Set the animated values for this object to this set of Keyframes.
646      *
647      * @param values One or more values that the animation will animate between.
648      */
setKeyframes(Keyframe @onNull .... values)649     public void setKeyframes(Keyframe @NonNull ... values) {
650         int numKeyframes = values.length;
651         Keyframe[] keyframes = new Keyframe[Math.max(numKeyframes, 2)];
652         mValueType = values[0].getType();
653         for (int i = 0; i < numKeyframes; ++i) {
654             keyframes[i] = values[i];
655         }
656         mKeyframes = new KeyframeSet(keyframes);
657     }
658 
659     /**
660      * Set the animated values for this object to this set of Objects.
661      * If there is only one value, it is assumed to be the end value of an animation,
662      * and an initial value will be derived, if possible, by calling a getter function
663      * on the object. Also, if any value is null, the value will be filled in when the animation
664      * starts in the same way. This mechanism of automatically getting null values only works
665      * if the PropertyValuesHolder object is used in conjunction {@link ObjectAnimator}, and with
666      * a getter function derived automatically from <code>propertyName</code>, since otherwise
667      * PropertyValuesHolder has no way of determining what the value should be.
668      *
669      * <p><strong>Note:</strong> The Object values are stored as references to the original
670      * objects, which means that changes to those objects after this method is called will
671      * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
672      * after this method is called, callers should pass a copy of those objects instead.
673      *
674      * @param values One or more values that the animation will animate between.
675      */
setObjectValues(Object @onNull .... values)676     public void setObjectValues(Object @NonNull ... values) {
677         mValueType = values[0].getClass();
678         mKeyframes = KeyframeSet.ofObject(values);
679         if (mEvaluator != null) {
680             mKeyframes.setEvaluator(mEvaluator);
681         }
682     }
683 
684     /**
685      * Sets the converter to convert from the values type to the setter's parameter type.
686      * If only one value is supplied, <var>converter</var> must be a
687      * {@link BidirectionalTypeConverter}.
688      * @param converter The converter to use to convert values.
689      */
setConverter(@ullable TypeConverter converter)690     public void setConverter(@Nullable TypeConverter converter) {
691         mConverter = converter;
692     }
693 
694     /**
695      * Determine the setter or getter function using the JavaBeans convention of setFoo or
696      * getFoo for a property named 'foo'. This function figures out what the name of the
697      * function should be and uses reflection to find the Method with that name on the
698      * target object.
699      *
700      * @param targetClass The class to search for the method
701      * @param prefix "set" or "get", depending on whether we need a setter or getter.
702      * @param valueType The type of the parameter (in the case of a setter). This type
703      * is derived from the values set on this PropertyValuesHolder. This type is used as
704      * a first guess at the parameter type, but we check for methods with several different
705      * types to avoid problems with slight mis-matches between supplied values and actual
706      * value types used on the setter.
707      * @return Method the method associated with mPropertyName.
708      */
getPropertyFunction(Class<?> targetClass, String prefix, Class<?> valueType)709     private Method getPropertyFunction(Class<?> targetClass, String prefix, Class<?> valueType) {
710         // TODO: faster implementation...
711         Method returnVal = null;
712         String methodName = getMethodName(prefix, mPropertyName);
713         Class<?>[] args = null;
714         if (valueType == null) {
715             try {
716                 returnVal = targetClass.getMethod(methodName, args);
717             } catch (NoSuchMethodException e) {
718                 // Swallow the error, log it later
719             }
720         } else {
721             args = new Class<?>[1];
722             Class<?>[] typeVariants;
723             if (valueType.equals(Float.class)) {
724                 typeVariants = FLOAT_VARIANTS;
725             } else if (valueType.equals(Integer.class)) {
726                 typeVariants = INTEGER_VARIANTS;
727             } else if (valueType.equals(Double.class)) {
728                 typeVariants = DOUBLE_VARIANTS;
729             } else {
730                 typeVariants = new Class<?>[1];
731                 typeVariants[0] = valueType;
732             }
733             for (Class<?> typeVariant : typeVariants) {
734                 args[0] = typeVariant;
735                 try {
736                     returnVal = targetClass.getMethod(methodName, args);
737                     if (mConverter == null) {
738                         // change the value type to suit
739                         mValueType = typeVariant;
740                     }
741                     return returnVal;
742                 } catch (NoSuchMethodException e) {
743                     // Swallow the error and keep trying other variants
744                 }
745                 try {
746                     returnVal = targetClass.getDeclaredMethod(methodName, args);
747                     returnVal.setAccessible(true);
748                     if (mConverter == null) {
749                         // change the value type to suit
750                         mValueType = typeVariant;
751                     }
752                     return returnVal;
753                 } catch (NoSuchMethodException e) {
754                     // Swallow the error and keep trying other variants
755                 }
756             }
757             // If we got here, then no appropriate function was found
758         }
759 
760         if (returnVal == null) {
761             Log.w("PropertyValuesHolder", "Method "
762                     + getMethodName(prefix, mPropertyName) + "() with type " + valueType
763                     + " not found on target class " + targetClass);
764         }
765 
766         return returnVal;
767     }
768 
769 
770     /**
771      * Returns the setter or getter requested. This utility function checks whether the
772      * requested method exists in the propertyMapMap cache. If not, it calls another
773      * utility function to request the Method from the targetClass directly.
774      * @param targetClass The Class on which the requested method should exist.
775      * @param propertyMapMap The cache of setters/getters derived so far.
776      * @param prefix "set" or "get", for the setter or getter.
777      * @param valueType The type of parameter passed into the method (null for getter).
778      * @return Method the method associated with mPropertyName.
779      */
setupSetterOrGetter(Class<?> targetClass, HashMap<Class<?>, HashMap<String, Method>> propertyMapMap, String prefix, Class<?> valueType)780     private Method setupSetterOrGetter(Class<?> targetClass,
781             HashMap<Class<?>, HashMap<String, Method>> propertyMapMap,
782             String prefix, Class<?> valueType) {
783         Method setterOrGetter = null;
784         synchronized (propertyMapMap) {
785             // Have to lock property map prior to reading it, to guard against
786             // another thread putting something in there after we've checked it
787             // but before we've added an entry to it
788             HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
789             boolean wasInMap = false;
790             if (propertyMap != null) {
791                 wasInMap = propertyMap.containsKey(mPropertyName);
792                 if (wasInMap) {
793                     setterOrGetter = propertyMap.get(mPropertyName);
794                 }
795             }
796             if (!wasInMap) {
797                 setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
798                 if (propertyMap == null) {
799                     propertyMap = new HashMap<String, Method>();
800                     propertyMapMap.put(targetClass, propertyMap);
801                 }
802                 propertyMap.put(mPropertyName, setterOrGetter);
803             }
804         }
805         return setterOrGetter;
806     }
807 
808     /**
809      * Utility function to get the setter from targetClass
810      * @param targetClass The Class on which the requested method should exist.
811      */
setupSetter(Class<?> targetClass)812     void setupSetter(Class<?> targetClass) {
813         Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
814         mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
815     }
816 
817     /**
818      * Utility function to get the getter from targetClass
819      */
setupGetter(Class<?> targetClass)820     private void setupGetter(Class<?> targetClass) {
821         mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
822     }
823 
824     /**
825      * Internal function (called from ObjectAnimator) to set up the setter and getter
826      * prior to running the animation. If the setter has not been manually set for this
827      * object, it will be derived automatically given the property name, target object, and
828      * types of values supplied. If no getter has been set, it will be supplied iff any of the
829      * supplied values was null. If there is a null value, then the getter (supplied or derived)
830      * will be called to set those null values to the current value of the property
831      * on the target object.
832      * @param target The object on which the setter (and possibly getter) exist.
833      */
setupSetterAndGetter(Object target)834     void setupSetterAndGetter(Object target) {
835         if (mProperty != null) {
836             // check to make sure that mProperty is on the class of target
837             try {
838                 Object testValue = null;
839                 List<Keyframe> keyframes = mKeyframes.getKeyframes();
840                 int keyframeCount = keyframes == null ? 0 : keyframes.size();
841                 for (int i = 0; i < keyframeCount; i++) {
842                     Keyframe kf = keyframes.get(i);
843                     if (!kf.hasValue() || kf.valueWasSetOnStart()) {
844                         if (testValue == null) {
845                             testValue = convertBack(mProperty.get(target));
846                         }
847                         kf.setValue(testValue);
848                         kf.setValueWasSetOnStart(true);
849                     }
850                 }
851                 return;
852             } catch (ClassCastException e) {
853                 Log.w("PropertyValuesHolder", "No such property (" + mProperty.getName()
854                         + ") on target object " + target + ". Trying reflection instead");
855                 mProperty = null;
856             }
857         }
858         // We can't just say 'else' here because the catch statement sets mProperty to null.
859         if (mProperty == null) {
860             Class<?> targetClass = target.getClass();
861             if (mSetter == null) {
862                 setupSetter(targetClass);
863             }
864             List<Keyframe> keyframes = mKeyframes.getKeyframes();
865             int keyframeCount = keyframes == null ? 0 : keyframes.size();
866             for (int i = 0; i < keyframeCount; i++) {
867                 Keyframe kf = keyframes.get(i);
868                 if (!kf.hasValue() || kf.valueWasSetOnStart()) {
869                     if (mGetter == null) {
870                         setupGetter(targetClass);
871                         if (mGetter == null) {
872                             // Already logged the error - just return to avoid NPE
873                             return;
874                         }
875                     }
876                     try {
877                         Object value = convertBack(mGetter.invoke(target));
878                         kf.setValue(value);
879                         kf.setValueWasSetOnStart(true);
880                     } catch (InvocationTargetException e) {
881                         Log.e("PropertyValuesHolder", e.toString());
882                     } catch (IllegalAccessException e) {
883                         Log.e("PropertyValuesHolder", e.toString());
884                     }
885                 }
886             }
887         }
888     }
889 
convertBack(Object value)890     private Object convertBack(Object value) {
891         if (mConverter != null) {
892             if (!(mConverter instanceof BidirectionalTypeConverter)) {
893                 throw new IllegalArgumentException("Converter "
894                         + mConverter.getClass().getName()
895                         + " must be a BidirectionalTypeConverter");
896             }
897             value = ((BidirectionalTypeConverter) mConverter).convertBack(value);
898         }
899         return value;
900     }
901 
902     /**
903      * Utility function to set the value stored in a particular Keyframe. The value used is
904      * whatever the value is for the property name specified in the keyframe on the target object.
905      *
906      * @param target The target object from which the current value should be extracted.
907      * @param kf The keyframe which holds the property name and value.
908      */
setupValue(Object target, Keyframe kf)909     private void setupValue(Object target, Keyframe kf) {
910         if (mProperty != null) {
911             Object value = convertBack(mProperty.get(target));
912             kf.setValue(value);
913         } else {
914             try {
915                 if (mGetter == null) {
916                     Class<?> targetClass = target.getClass();
917                     setupGetter(targetClass);
918                     if (mGetter == null) {
919                         // Already logged the error - just return to avoid NPE
920                         return;
921                     }
922                 }
923                 Object value = convertBack(mGetter.invoke(target));
924                 kf.setValue(value);
925             } catch (InvocationTargetException e) {
926                 Log.e("PropertyValuesHolder", e.toString());
927             } catch (IllegalAccessException e) {
928                 Log.e("PropertyValuesHolder", e.toString());
929             }
930         }
931     }
932 
933     /**
934      * This function is called by ObjectAnimator when setting the start values for an animation.
935      * The start values are set according to the current values in the target object. The
936      * property whose value is extracted is whatever is specified by the propertyName of this
937      * PropertyValuesHolder object.
938      *
939      * @param target The object which holds the start values that should be set.
940      */
setupStartValue(Object target)941     void setupStartValue(Object target) {
942         List<Keyframe> keyframes = mKeyframes.getKeyframes();
943         if (!keyframes.isEmpty()) {
944             setupValue(target, keyframes.get(0));
945         }
946     }
947 
948     /**
949      * This function is called by ObjectAnimator when setting the end values for an animation.
950      * The end values are set according to the current values in the target object. The
951      * property whose value is extracted is whatever is specified by the propertyName of this
952      * PropertyValuesHolder object.
953      *
954      * @param target The object which holds the start values that should be set.
955      */
setupEndValue(Object target)956     void setupEndValue(Object target) {
957         List<Keyframe> keyframes = mKeyframes.getKeyframes();
958         if (!keyframes.isEmpty()) {
959             setupValue(target, keyframes.get(keyframes.size() - 1));
960         }
961     }
962 
963     @SuppressLint("NoClone") /* Platform API */
964     @Override
clone()965     public @NonNull PropertyValuesHolder clone() {
966         try {
967             PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone();
968             newPVH.mPropertyName = mPropertyName;
969             newPVH.mProperty = mProperty;
970             newPVH.mKeyframes = mKeyframes.clone();
971             newPVH.mEvaluator = mEvaluator;
972             return newPVH;
973         } catch (CloneNotSupportedException e) {
974             // won't reach here
975             return null;
976         }
977     }
978 
979     /**
980      * Internal function to set the value on the target object, using the setter set up
981      * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
982      * to handle turning the value calculated by ValueAnimator into a value set on the object
983      * according to the name of the property.
984      * @param target The target object on which the value is set
985      */
setAnimatedValue(Object target)986     void setAnimatedValue(Object target) {
987         if (mProperty != null) {
988             mProperty.set(target, getAnimatedValue());
989         }
990         if (mSetter != null) {
991             try {
992                 mTmpValueArray[0] = getAnimatedValue();
993                 mSetter.invoke(target, mTmpValueArray);
994             } catch (InvocationTargetException e) {
995                 Log.e("PropertyValuesHolder", e.toString());
996             } catch (IllegalAccessException e) {
997                 Log.e("PropertyValuesHolder", e.toString());
998             }
999         }
1000     }
1001 
1002     /**
1003      * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
1004      * to calculate animated values.
1005      */
init()1006     void init() {
1007         if (mEvaluator == null) {
1008             // We already handle int and float automatically, but not their Object
1009             // equivalents
1010             mEvaluator = (mValueType == Integer.class) ? IntEvaluator.getInstance() :
1011                     (mValueType == Float.class) ? FloatEvaluator.getInstance() :
1012                     null;
1013         }
1014         if (mEvaluator != null) {
1015             // KeyframeSet knows how to evaluate the common types - only give it a custom
1016             // evaluator if one has been set on this class
1017             mKeyframes.setEvaluator(mEvaluator);
1018         }
1019     }
1020 
1021     /**
1022      * The TypeEvaluator will be automatically determined based on the type of values
1023      * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so
1024      * desired. This may be important in cases where either the type of the values supplied
1025      * do not match the way that they should be interpolated between, or if the values
1026      * are of a custom type or one not currently understood by the animation system. Currently,
1027      * only values of type float and int (and their Object equivalents: Float
1028      * and Integer) are  correctly interpolated; all other types require setting a TypeEvaluator.
1029      * @param evaluator
1030      */
setEvaluator(@onNull TypeEvaluator evaluator)1031     public void setEvaluator(@NonNull TypeEvaluator evaluator) {
1032         mEvaluator = evaluator;
1033         mKeyframes.setEvaluator(evaluator);
1034     }
1035 
1036     /**
1037      * Function used to calculate the value according to the evaluator set up for
1038      * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue().
1039      *
1040      * @param fraction The elapsed, interpolated fraction of the animation.
1041      */
calculateValue(float fraction)1042     void calculateValue(float fraction) {
1043         Object value = mKeyframes.getValue(fraction);
1044         mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
1045     }
1046 
1047     /**
1048      * Sets the name of the property that will be animated. This name is used to derive
1049      * a setter function that will be called to set animated values.
1050      * For example, a property name of <code>foo</code> will result
1051      * in a call to the function <code>setFoo()</code> on the target object. If either
1052      * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
1053      * also be derived and called.
1054      *
1055      * <p>Note that the setter function derived from this property name
1056      * must take the same parameter type as the
1057      * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
1058      * the setter function will fail.</p>
1059      *
1060      * @param propertyName The name of the property being animated.
1061      */
setPropertyName(@onNull String propertyName)1062     public void setPropertyName(@NonNull String propertyName) {
1063         mPropertyName = propertyName;
1064     }
1065 
1066     /**
1067      * Sets the property that will be animated.
1068      *
1069      * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property
1070      * must exist on the target object specified in that ObjectAnimator.</p>
1071      *
1072      * @param property The property being animated.
1073      */
setProperty(@onNull Property property)1074     public void setProperty(@NonNull Property property) {
1075         mProperty = property;
1076     }
1077 
1078     /**
1079      * Gets the name of the property that will be animated. This name will be used to derive
1080      * a setter function that will be called to set animated values.
1081      * For example, a property name of <code>foo</code> will result
1082      * in a call to the function <code>setFoo()</code> on the target object. If either
1083      * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
1084      * also be derived and called.
1085      */
getPropertyName()1086     public @NonNull String getPropertyName() {
1087         return mPropertyName;
1088     }
1089 
1090     /**
1091      * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value
1092      * most recently calculated in calculateValue().
1093      * @return
1094      */
getAnimatedValue()1095     Object getAnimatedValue() {
1096         return mAnimatedValue;
1097     }
1098 
getValueType()1099     Class<?> getValueType() {
1100         return mValueType;
1101     }
1102 
1103     @Override
toString()1104     public @NonNull String toString() {
1105         return mPropertyName + ": " + mKeyframes.toString();
1106     }
1107 
1108     /**
1109      * Utility method to derive a setter/getter method name from a property name, where the
1110      * prefix is typically "set" or "get" and the first letter of the property name is
1111      * capitalized.
1112      *
1113      * @param prefix The precursor to the method name, before the property name begins, typically
1114      * "set" or "get".
1115      * @param propertyName The name of the property that represents the bulk of the method name
1116      * after the prefix. The first letter of this word will be capitalized in the resulting
1117      * method name.
1118      * @return String the property name converted to a method name according to the conventions
1119      * specified above.
1120      */
getMethodName(String prefix, String propertyName)1121     static String getMethodName(String prefix, String propertyName) {
1122         if (propertyName == null || propertyName.length() == 0) {
1123             // shouldn't get here
1124             return prefix;
1125         }
1126         char firstLetter = Character.toUpperCase(propertyName.charAt(0));
1127         String theRest = propertyName.substring(1);
1128         return prefix + firstLetter + theRest;
1129     }
1130 
1131     static class IntPropertyValuesHolder extends PropertyValuesHolder {
1132 
1133         private IntProperty mIntProperty;
1134 
1135         Keyframes.IntKeyframes mIntKeyframes;
1136         int mIntAnimatedValue;
1137 
IntPropertyValuesHolder(String propertyName, Keyframes.IntKeyframes keyframes)1138         IntPropertyValuesHolder(String propertyName, Keyframes.IntKeyframes keyframes) {
1139             super(propertyName);
1140             mValueType = int.class;
1141             mKeyframes = keyframes;
1142             mIntKeyframes = keyframes;
1143         }
1144 
IntPropertyValuesHolder(Property property, Keyframes.IntKeyframes keyframes)1145         IntPropertyValuesHolder(Property property, Keyframes.IntKeyframes keyframes) {
1146             super(property);
1147             mValueType = int.class;
1148             mKeyframes = keyframes;
1149             mIntKeyframes = keyframes;
1150             if (property instanceof  IntProperty) {
1151                 mIntProperty = (IntProperty) mProperty;
1152             }
1153         }
1154 
IntPropertyValuesHolder(String propertyName, int... values)1155         IntPropertyValuesHolder(String propertyName, int... values) {
1156             super(propertyName);
1157             setIntValues(values);
1158         }
1159 
IntPropertyValuesHolder(Property property, int... values)1160         IntPropertyValuesHolder(Property property, int... values) {
1161             super(property);
1162             setIntValues(values);
1163             if (property instanceof  IntProperty) {
1164                 mIntProperty = (IntProperty) mProperty;
1165             }
1166         }
1167 
1168         @Override
setProperty(@onNull Property property)1169         public void setProperty(@NonNull Property property) {
1170             if (property instanceof IntProperty) {
1171                 mIntProperty = (IntProperty) property;
1172             } else {
1173                 super.setProperty(property);
1174             }
1175         }
1176 
1177         @Override
setIntValues(int @NonNull ... values)1178         public void setIntValues(int @NonNull ... values) {
1179             super.setIntValues(values);
1180             mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
1181         }
1182 
1183         @Override
calculateValue(float fraction)1184         void calculateValue(float fraction) {
1185             mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);
1186         }
1187 
1188         @Override
getAnimatedValue()1189         Object getAnimatedValue() {
1190             return mIntAnimatedValue;
1191         }
1192 
1193         @Override
clone()1194         public @NonNull IntPropertyValuesHolder clone() {
1195             IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone();
1196             newPVH.mIntKeyframes = (Keyframes.IntKeyframes) newPVH.mKeyframes;
1197             return newPVH;
1198         }
1199 
1200         /**
1201          * Internal function to set the value on the target object, using the setter set up
1202          * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
1203          * to handle turning the value calculated by ValueAnimator into a value set on the object
1204          * according to the name of the property.
1205          * @param target The target object on which the value is set
1206          */
1207         @Override
setAnimatedValue(Object target)1208         void setAnimatedValue(Object target) {
1209             if (mIntProperty != null) {
1210                 mIntProperty.setValue(target, mIntAnimatedValue);
1211                 return;
1212             }
1213             if (mProperty != null) {
1214                 mProperty.set(target, mIntAnimatedValue);
1215                 return;
1216             }
1217 
1218             try {
1219                 mTmpValueArray[0] = mIntAnimatedValue;
1220                 mSetter.invoke(target, mTmpValueArray);
1221             } catch (InvocationTargetException e) {
1222                 Log.e("PropertyValuesHolder", e.toString());
1223             } catch (IllegalAccessException e) {
1224                 Log.e("PropertyValuesHolder", e.toString());
1225             }
1226         }
1227     }
1228 
1229     static class FloatPropertyValuesHolder extends PropertyValuesHolder {
1230 
1231         private FloatProperty mFloatProperty;
1232 
1233         Keyframes.FloatKeyframes mFloatKeyframes;
1234         float mFloatAnimatedValue;
1235 
FloatPropertyValuesHolder(String propertyName, Keyframes.FloatKeyframes keyframes)1236         FloatPropertyValuesHolder(String propertyName, Keyframes.FloatKeyframes keyframes) {
1237             super(propertyName);
1238             mValueType = float.class;
1239             mKeyframes = keyframes;
1240             mFloatKeyframes = keyframes;
1241         }
1242 
FloatPropertyValuesHolder(Property property, Keyframes.FloatKeyframes keyframes)1243         FloatPropertyValuesHolder(Property property, Keyframes.FloatKeyframes keyframes) {
1244             super(property);
1245             mValueType = float.class;
1246             mKeyframes = keyframes;
1247             mFloatKeyframes = keyframes;
1248             if (property instanceof FloatProperty) {
1249                 mFloatProperty = (FloatProperty) mProperty;
1250             }
1251         }
1252 
FloatPropertyValuesHolder(String propertyName, float... values)1253         FloatPropertyValuesHolder(String propertyName, float... values) {
1254             super(propertyName);
1255             setFloatValues(values);
1256         }
1257 
FloatPropertyValuesHolder(Property property, float... values)1258         FloatPropertyValuesHolder(Property property, float... values) {
1259             super(property);
1260             setFloatValues(values);
1261             if (property instanceof  FloatProperty) {
1262                 mFloatProperty = (FloatProperty) mProperty;
1263             }
1264         }
1265 
1266         @Override
setProperty(@onNull Property property)1267         public void setProperty(@NonNull Property property) {
1268             if (property instanceof FloatProperty) {
1269                 mFloatProperty = (FloatProperty) property;
1270             } else {
1271                 super.setProperty(property);
1272             }
1273         }
1274 
1275         @Override
setFloatValues(float @NonNull ... values)1276         public void setFloatValues(float @NonNull ... values) {
1277             super.setFloatValues(values);
1278             mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
1279         }
1280 
1281         @Override
calculateValue(float fraction)1282         void calculateValue(float fraction) {
1283             mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
1284         }
1285 
1286         @Override
getAnimatedValue()1287         Object getAnimatedValue() {
1288             return mFloatAnimatedValue;
1289         }
1290 
1291         @Override
clone()1292         public @NonNull FloatPropertyValuesHolder clone() {
1293             FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone();
1294             newPVH.mFloatKeyframes = (Keyframes.FloatKeyframes) newPVH.mKeyframes;
1295             return newPVH;
1296         }
1297 
1298         /**
1299          * Internal function to set the value on the target object, using the setter set up
1300          * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
1301          * to handle turning the value calculated by ValueAnimator into a value set on the object
1302          * according to the name of the property.
1303          * @param target The target object on which the value is set
1304          */
1305         @Override
setAnimatedValue(Object target)1306         void setAnimatedValue(Object target) {
1307             if (mFloatProperty != null) {
1308                 mFloatProperty.setValue(target, mFloatAnimatedValue);
1309                 return;
1310             }
1311             if (mProperty != null) {
1312                 mProperty.set(target, mFloatAnimatedValue);
1313                 return;
1314             }
1315             if (mSetter != null) {
1316                 try {
1317                     mTmpValueArray[0] = mFloatAnimatedValue;
1318                     mSetter.invoke(target, mTmpValueArray);
1319                 } catch (InvocationTargetException e) {
1320                     Log.e("PropertyValuesHolder", e.toString());
1321                 } catch (IllegalAccessException e) {
1322                     Log.e("PropertyValuesHolder", e.toString());
1323                 }
1324             }
1325         }
1326 
1327     }
1328 
1329     static class MultiFloatValuesHolder extends PropertyValuesHolder {
1330 
MultiFloatValuesHolder(String propertyName, TypeConverter converter, TypeEvaluator evaluator, Object... values)1331         MultiFloatValuesHolder(String propertyName, TypeConverter converter,
1332                 TypeEvaluator evaluator, Object... values) {
1333             super(propertyName);
1334             setConverter(converter);
1335             setObjectValues(values);
1336             setEvaluator(evaluator);
1337         }
1338 
MultiFloatValuesHolder(String propertyName, TypeConverter converter, TypeEvaluator evaluator, Keyframes keyframes)1339         MultiFloatValuesHolder(String propertyName, TypeConverter converter,
1340                 TypeEvaluator evaluator, Keyframes keyframes) {
1341             super(propertyName);
1342             setConverter(converter);
1343             mKeyframes = keyframes;
1344             setEvaluator(evaluator);
1345         }
1346 
1347         /**
1348          * Internal function to set the value on the target object, using the setter set up
1349          * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
1350          * to handle turning the value calculated by ValueAnimator into a value set on the object
1351          * according to the name of the property.
1352          * @param target The target object on which the value is set
1353          */
1354         @Override
setAnimatedValue(Object target)1355         void setAnimatedValue(Object target) {
1356             float[] values = (float[]) getAnimatedValue();
1357             Object[] boxedValues = new Float[values.length];
1358             for (int i = 0; i < boxedValues.length; i++) {
1359                 boxedValues[i] = values[i];
1360             }
1361             if (mSetter != null) {
1362                 try {
1363                     mSetter.invoke(target, boxedValues);
1364                 } catch (InvocationTargetException e) {
1365                     Log.e("PropertyValuesHolder", e.toString());
1366                 } catch (IllegalAccessException e) {
1367                     Log.e("PropertyValuesHolder", e.toString());
1368                 }
1369             }
1370         }
1371 
1372         /**
1373          * Internal function (called from ObjectAnimator) to set up the setter and getter
1374          * prior to running the animation. No getter can be used for multiple parameters.
1375          *
1376          * @param target The object on which the setter exists.
1377          */
1378         @Override
setupSetterAndGetter(Object target)1379         void setupSetterAndGetter(Object target) {
1380             setupSetter(target.getClass());
1381         }
1382 
1383         @Override
1384         @SuppressWarnings("CatchAndPrintStackTrace")
setupSetter(Class<?> targetClass)1385         void setupSetter(Class<?> targetClass) {
1386             synchronized (sSetterPropertyMap) {
1387                 // Have to lock property map prior to reading it, to guard against
1388                 // another thread putting something in there after we've checked it
1389                 // but before we've added an entry to it
1390                 HashMap<String, Method> propertyMap = sSetterPropertyMap.get(targetClass);
1391                 boolean wasInMap = false;
1392                 if (propertyMap != null) {
1393                     wasInMap = propertyMap.containsKey(mPropertyName);
1394                     if (wasInMap) {
1395                         mSetter = propertyMap.get(mPropertyName);
1396                     }
1397                 }
1398                 if (!wasInMap) {
1399                     String methodName = getMethodName("set", mPropertyName);
1400                     calculateValue(0f);
1401                     float[] values = (float[]) getAnimatedValue();
1402                     int numParams = values.length;
1403                     Class<?>[] parameterTypes = new Class<?>[values.length];
1404                     for (int i = 0; i < numParams; i++) {
1405                         parameterTypes[i] = float.class;
1406                     }
1407                     try {
1408                         mSetter = targetClass.getMethod(methodName, parameterTypes);
1409                         if (mSetter == null) {
1410                             for (int i = 0; i < numParams; i++) {
1411                                 parameterTypes[i] = Float.class;
1412                             }
1413                             mSetter = targetClass.getMethod(methodName, parameterTypes);
1414                         }
1415                     } catch (NoSuchMethodException e) {
1416                         e.printStackTrace();
1417                     }
1418 
1419                     if (propertyMap == null) {
1420                         propertyMap = new HashMap<>();
1421                         sSetterPropertyMap.put(targetClass, propertyMap);
1422                     }
1423                     propertyMap.put(mPropertyName, mSetter);
1424                 }
1425             }
1426         }
1427     }
1428 
1429     static class MultiIntValuesHolder extends PropertyValuesHolder {
1430 
MultiIntValuesHolder(String propertyName, TypeConverter converter, TypeEvaluator evaluator, Object... values)1431         MultiIntValuesHolder(String propertyName, TypeConverter converter,
1432                 TypeEvaluator evaluator, Object... values) {
1433             super(propertyName);
1434             setConverter(converter);
1435             setObjectValues(values);
1436             setEvaluator(evaluator);
1437         }
1438 
MultiIntValuesHolder(String propertyName, TypeConverter converter, TypeEvaluator evaluator, Keyframes keyframes)1439         MultiIntValuesHolder(String propertyName, TypeConverter converter,
1440                 TypeEvaluator evaluator, Keyframes keyframes) {
1441             super(propertyName);
1442             setConverter(converter);
1443             mKeyframes = keyframes;
1444             setEvaluator(evaluator);
1445         }
1446 
1447         /**
1448          * Internal function to set the value on the target object, using the setter set up
1449          * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
1450          * to handle turning the value calculated by ValueAnimator into a value set on the object
1451          * according to the name of the property.
1452          * @param target The target object on which the value is set
1453          */
1454         @Override
setAnimatedValue(Object target)1455         void setAnimatedValue(Object target) {
1456             int[] values = (int[]) getAnimatedValue();
1457             Object[] boxedValues = new Integer[values.length];
1458             for (int i = 0; i < boxedValues.length; i++) {
1459                 boxedValues[i] = values[i];
1460             }
1461             if (mSetter != null) {
1462                 try {
1463                     mSetter.invoke(target, boxedValues);
1464                 } catch (InvocationTargetException e) {
1465                     Log.e("PropertyValuesHolder", e.toString());
1466                 } catch (IllegalAccessException e) {
1467                     Log.e("PropertyValuesHolder", e.toString());
1468                 }
1469             }
1470         }
1471 
1472         /**
1473          * Internal function (called from ObjectAnimator) to set up the setter and getter
1474          * prior to running the animation. No getter can be used for multiple parameters.
1475          *
1476          * @param target The object on which the setter exists.
1477          */
1478         @Override
setupSetterAndGetter(Object target)1479         void setupSetterAndGetter(Object target) {
1480             setupSetter(target.getClass());
1481         }
1482 
1483         @Override
1484         @SuppressWarnings("CatchAndPrintStackTrace")
setupSetter(Class<?> targetClass)1485         void setupSetter(Class<?> targetClass) {
1486             synchronized (sSetterPropertyMap) {
1487                 // Have to lock property map prior to reading it, to guard against
1488                 // another thread putting something in there after we've checked it
1489                 // but before we've added an entry to it
1490                 HashMap<String, Method> propertyMap = sSetterPropertyMap.get(targetClass);
1491                 boolean wasInMap = false;
1492                 if (propertyMap != null) {
1493                     wasInMap = propertyMap.containsKey(mPropertyName);
1494                     if (wasInMap) {
1495                         mSetter = propertyMap.get(mPropertyName);
1496                     }
1497                 }
1498                 if (!wasInMap) {
1499                     String methodName = getMethodName("set", mPropertyName);
1500                     calculateValue(0f);
1501                     int[] values = (int[]) getAnimatedValue();
1502                     int numParams = values.length;
1503                     Class<?>[] parameterTypes = new Class<?>[values.length];
1504                     for (int i = 0; i < numParams; i++) {
1505                         parameterTypes[i] = int.class;
1506                     }
1507                     try {
1508                         mSetter = targetClass.getMethod(methodName, parameterTypes);
1509                         if (mSetter == null) {
1510                             for (int i = 0; i < numParams; i++) {
1511                                 parameterTypes[i] = Integer.class;
1512                             }
1513                             mSetter = targetClass.getMethod(methodName, parameterTypes);
1514                         }
1515                     } catch (NoSuchMethodException e) {
1516                         e.printStackTrace();
1517                     }
1518 
1519                     if (propertyMap == null) {
1520                         propertyMap = new HashMap<>();
1521                         sSetterPropertyMap.put(targetClass, propertyMap);
1522                     }
1523                     propertyMap.put(mPropertyName, mSetter);
1524                 }
1525             }
1526         }
1527     }
1528 
1529     /**
1530      * Convert from PointF to float[] for multi-float setters along a Path.
1531      */
1532     private static class PointFToFloatArray extends TypeConverter<PointF, float[]> {
1533         private float[] mCoordinates = new float[2];
1534 
PointFToFloatArray()1535         PointFToFloatArray() {
1536             super(PointF.class, float[].class);
1537         }
1538 
1539         @Override
convert(@onNull PointF value)1540         public float @NonNull [] convert(@NonNull PointF value) {
1541             mCoordinates[0] = value.x;
1542             mCoordinates[1] = value.y;
1543             return mCoordinates;
1544         }
1545     };
1546 
1547     /**
1548      * Convert from PointF to int[] for multi-int setters along a Path.
1549      */
1550     private static class PointFToIntArray extends TypeConverter<PointF, int[]> {
1551         private int[] mCoordinates = new int[2];
1552 
PointFToIntArray()1553         PointFToIntArray() {
1554             super(PointF.class, int[].class);
1555         }
1556 
1557         @Override
convert(@onNull PointF value)1558         public int @NonNull [] convert(@NonNull PointF value) {
1559             mCoordinates[0] = Math.round(value.x);
1560             mCoordinates[1] = Math.round(value.y);
1561             return mCoordinates;
1562         }
1563     };
1564 }
1565