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