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