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 // Swallow the error, log it later 388 } 389 } else { 390 args = new Class[1]; 391 Class typeVariants[]; 392 if (mValueType.equals(Float.class)) { 393 typeVariants = FLOAT_VARIANTS; 394 } else if (mValueType.equals(Integer.class)) { 395 typeVariants = INTEGER_VARIANTS; 396 } else if (mValueType.equals(Double.class)) { 397 typeVariants = DOUBLE_VARIANTS; 398 } else { 399 typeVariants = new Class[1]; 400 typeVariants[0] = mValueType; 401 } 402 for (Class typeVariant : typeVariants) { 403 args[0] = typeVariant; 404 try { 405 returnVal = targetClass.getMethod(methodName, args); 406 // change the value type to suit 407 mValueType = typeVariant; 408 return returnVal; 409 } catch (NoSuchMethodException e) { 410 // Swallow the error and keep trying other variants 411 } 412 } 413 // If we got here, then no appropriate function was found 414 } 415 416 if (returnVal == null) { 417 Log.w("PropertyValuesHolder", "Method " + 418 getMethodName(prefix, mPropertyName) + "() with type " + mValueType + 419 " not found on target class " + targetClass); 420 } 421 422 return returnVal; 423 } 424 425 426 /** 427 * Returns the setter or getter requested. This utility function checks whether the 428 * requested method exists in the propertyMapMap cache. If not, it calls another 429 * utility function to request the Method from the targetClass directly. 430 * @param targetClass The Class on which the requested method should exist. 431 * @param propertyMapMap The cache of setters/getters derived so far. 432 * @param prefix "set" or "get", for the setter or getter. 433 * @param valueType The type of parameter passed into the method (null for getter). 434 * @return Method the method associated with mPropertyName. 435 */ 436 private Method setupSetterOrGetter(Class targetClass, 437 HashMap<Class, HashMap<String, Method>> propertyMapMap, 438 String prefix, Class valueType) { 439 Method setterOrGetter = null; 440 try { 441 // Have to lock property map prior to reading it, to guard against 442 // another thread putting something in there after we've checked it 443 // but before we've added an entry to it 444 mPropertyMapLock.writeLock().lock(); 445 HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass); 446 if (propertyMap != null) { 447 setterOrGetter = propertyMap.get(mPropertyName); 448 } 449 if (setterOrGetter == null) { 450 setterOrGetter = getPropertyFunction(targetClass, prefix, valueType); 451 if (propertyMap == null) { 452 propertyMap = new HashMap<String, Method>(); 453 propertyMapMap.put(targetClass, propertyMap); 454 } 455 propertyMap.put(mPropertyName, setterOrGetter); 456 } 457 } finally { 458 mPropertyMapLock.writeLock().unlock(); 459 } 460 return setterOrGetter; 461 } 462 463 /** 464 * Utility function to get the setter from targetClass 465 * @param targetClass The Class on which the requested method should exist. 466 */ 467 void setupSetter(Class targetClass) { 468 mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType); 469 } 470 471 /** 472 * Utility function to get the getter from targetClass 473 */ 474 private void setupGetter(Class targetClass) { 475 mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null); 476 } 477 478 /** 479 * Internal function (called from ObjectAnimator) to set up the setter and getter 480 * prior to running the animation. If the setter has not been manually set for this 481 * object, it will be derived automatically given the property name, target object, and 482 * types of values supplied. If no getter has been set, it will be supplied iff any of the 483 * supplied values was null. If there is a null value, then the getter (supplied or derived) 484 * will be called to set those null values to the current value of the property 485 * on the target object. 486 * @param target The object on which the setter (and possibly getter) exist. 487 */ 488 void setupSetterAndGetter(Object target) { 489 if (mProperty != null) { 490 // check to make sure that mProperty is on the class of target 491 try { 492 Object testValue = mProperty.get(target); 493 for (Keyframe kf : mKeyframeSet.mKeyframes) { 494 if (!kf.hasValue()) { 495 kf.setValue(mProperty.get(target)); 496 } 497 } 498 return; 499 } catch (ClassCastException e) { 500 Log.w("PropertyValuesHolder","No such property (" + mProperty.getName() + 501 ") on target object " + target + ". Trying reflection instead"); 502 mProperty = null; 503 } 504 } 505 Class targetClass = target.getClass(); 506 if (mSetter == null) { 507 setupSetter(targetClass); 508 } 509 for (Keyframe kf : mKeyframeSet.mKeyframes) { 510 if (!kf.hasValue()) { 511 if (mGetter == null) { 512 setupGetter(targetClass); 513 if (mGetter == null) { 514 // Already logged the error - just return to avoid NPE 515 return; 516 } 517 } 518 try { 519 kf.setValue(mGetter.invoke(target)); 520 } catch (InvocationTargetException e) { 521 Log.e("PropertyValuesHolder", e.toString()); 522 } catch (IllegalAccessException e) { 523 Log.e("PropertyValuesHolder", e.toString()); 524 } 525 } 526 } 527 } 528 529 /** 530 * Utility function to set the value stored in a particular Keyframe. The value used is 531 * whatever the value is for the property name specified in the keyframe on the target object. 532 * 533 * @param target The target object from which the current value should be extracted. 534 * @param kf The keyframe which holds the property name and value. 535 */ 536 private void setupValue(Object target, Keyframe kf) { 537 if (mProperty != null) { 538 kf.setValue(mProperty.get(target)); 539 } 540 try { 541 if (mGetter == null) { 542 Class targetClass = target.getClass(); 543 setupGetter(targetClass); 544 if (mGetter == null) { 545 // Already logged the error - just return to avoid NPE 546 return; 547 } 548 } 549 kf.setValue(mGetter.invoke(target)); 550 } catch (InvocationTargetException e) { 551 Log.e("PropertyValuesHolder", e.toString()); 552 } catch (IllegalAccessException e) { 553 Log.e("PropertyValuesHolder", e.toString()); 554 } 555 } 556 557 /** 558 * This function is called by ObjectAnimator when setting the start values for an animation. 559 * The start values are set according to the current values in the target object. The 560 * property whose value is extracted is whatever is specified by the propertyName of this 561 * PropertyValuesHolder object. 562 * 563 * @param target The object which holds the start values that should be set. 564 */ 565 void setupStartValue(Object target) { 566 setupValue(target, mKeyframeSet.mKeyframes.get(0)); 567 } 568 569 /** 570 * This function is called by ObjectAnimator when setting the end values for an animation. 571 * The end values are set according to the current values in the target object. The 572 * property whose value is extracted is whatever is specified by the propertyName of this 573 * PropertyValuesHolder object. 574 * 575 * @param target The object which holds the start values that should be set. 576 */ 577 void setupEndValue(Object target) { 578 setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1)); 579 } 580 581 @Override 582 public PropertyValuesHolder clone() { 583 try { 584 PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone(); 585 newPVH.mPropertyName = mPropertyName; 586 newPVH.mProperty = mProperty; 587 newPVH.mKeyframeSet = mKeyframeSet.clone(); 588 newPVH.mEvaluator = mEvaluator; 589 return newPVH; 590 } catch (CloneNotSupportedException e) { 591 // won't reach here 592 return null; 593 } 594 } 595 596 /** 597 * Internal function to set the value on the target object, using the setter set up 598 * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator 599 * to handle turning the value calculated by ValueAnimator into a value set on the object 600 * according to the name of the property. 601 * @param target The target object on which the value is set 602 */ 603 void setAnimatedValue(Object target) { 604 if (mProperty != null) { 605 mProperty.set(target, getAnimatedValue()); 606 } 607 if (mSetter != null) { 608 try { 609 mTmpValueArray[0] = getAnimatedValue(); 610 mSetter.invoke(target, mTmpValueArray); 611 } catch (InvocationTargetException e) { 612 Log.e("PropertyValuesHolder", e.toString()); 613 } catch (IllegalAccessException e) { 614 Log.e("PropertyValuesHolder", e.toString()); 615 } 616 } 617 } 618 619 /** 620 * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used 621 * to calculate animated values. 622 */ 623 void init() { 624 if (mEvaluator == null) { 625 // We already handle int and float automatically, but not their Object 626 // equivalents 627 mEvaluator = (mValueType == Integer.class) ? sIntEvaluator : 628 (mValueType == Float.class) ? sFloatEvaluator : 629 null; 630 } 631 if (mEvaluator != null) { 632 // KeyframeSet knows how to evaluate the common types - only give it a custom 633 // evaluator if one has been set on this class 634 mKeyframeSet.setEvaluator(mEvaluator); 635 } 636 } 637 638 /** 639 * The TypeEvaluator will be automatically determined based on the type of values 640 * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so 641 * desired. This may be important in cases where either the type of the values supplied 642 * do not match the way that they should be interpolated between, or if the values 643 * are of a custom type or one not currently understood by the animation system. Currently, 644 * only values of type float and int (and their Object equivalents: Float 645 * and Integer) are correctly interpolated; all other types require setting a TypeEvaluator. 646 * @param evaluator 647 */ 648 public void setEvaluator(TypeEvaluator evaluator) { 649 mEvaluator = evaluator; 650 mKeyframeSet.setEvaluator(evaluator); 651 } 652 653 /** 654 * Function used to calculate the value according to the evaluator set up for 655 * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue(). 656 * 657 * @param fraction The elapsed, interpolated fraction of the animation. 658 */ 659 void calculateValue(float fraction) { 660 mAnimatedValue = mKeyframeSet.getValue(fraction); 661 } 662 663 /** 664 * Sets the name of the property that will be animated. This name is used to derive 665 * a setter function that will be called to set animated values. 666 * For example, a property name of <code>foo</code> will result 667 * in a call to the function <code>setFoo()</code> on the target object. If either 668 * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will 669 * also be derived and called. 670 * 671 * <p>Note that the setter function derived from this property name 672 * must take the same parameter type as the 673 * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to 674 * the setter function will fail.</p> 675 * 676 * @param propertyName The name of the property being animated. 677 */ 678 public void setPropertyName(String propertyName) { 679 mPropertyName = propertyName; 680 } 681 682 /** 683 * Sets the property that will be animated. 684 * 685 * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property 686 * must exist on the target object specified in that ObjectAnimator.</p> 687 * 688 * @param property The property being animated. 689 */ 690 public void setProperty(Property property) { 691 mProperty = property; 692 } 693 694 /** 695 * Gets the name of the property that will be animated. This name will be used to derive 696 * a setter function that will be called to set animated values. 697 * For example, a property name of <code>foo</code> will result 698 * in a call to the function <code>setFoo()</code> on the target object. If either 699 * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will 700 * also be derived and called. 701 */ 702 public String getPropertyName() { 703 return mPropertyName; 704 } 705 706 /** 707 * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value 708 * most recently calculated in calculateValue(). 709 * @return 710 */ 711 Object getAnimatedValue() { 712 return mAnimatedValue; 713 } 714 715 @Override 716 public String toString() { 717 return mPropertyName + ": " + mKeyframeSet.toString(); 718 } 719 720 /** 721 * Utility method to derive a setter/getter method name from a property name, where the 722 * prefix is typically "set" or "get" and the first letter of the property name is 723 * capitalized. 724 * 725 * @param prefix The precursor to the method name, before the property name begins, typically 726 * "set" or "get". 727 * @param propertyName The name of the property that represents the bulk of the method name 728 * after the prefix. The first letter of this word will be capitalized in the resulting 729 * method name. 730 * @return String the property name converted to a method name according to the conventions 731 * specified above. 732 */ 733 static String getMethodName(String prefix, String propertyName) { 734 if (propertyName == null || propertyName.length() == 0) { 735 // shouldn't get here 736 return prefix; 737 } 738 char firstLetter = Character.toUpperCase(propertyName.charAt(0)); 739 String theRest = propertyName.substring(1); 740 return prefix + firstLetter + theRest; 741 } 742 743 static class IntPropertyValuesHolder extends PropertyValuesHolder { 744 745 // Cache JNI functions to avoid looking them up twice 746 private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = 747 new HashMap<Class, HashMap<String, Integer>>(); 748 int mJniSetter; 749 private IntProperty mIntProperty; 750 751 IntKeyframeSet mIntKeyframeSet; 752 int mIntAnimatedValue; 753 754 public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) { 755 super(propertyName); 756 mValueType = int.class; 757 mKeyframeSet = keyframeSet; 758 mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; 759 } 760 761 public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) { 762 super(property); 763 mValueType = int.class; 764 mKeyframeSet = keyframeSet; 765 mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; 766 if (property instanceof IntProperty) { 767 mIntProperty = (IntProperty) mProperty; 768 } 769 } 770 771 public IntPropertyValuesHolder(String propertyName, int... values) { 772 super(propertyName); 773 setIntValues(values); 774 } 775 776 public IntPropertyValuesHolder(Property property, int... values) { 777 super(property); 778 setIntValues(values); 779 if (property instanceof IntProperty) { 780 mIntProperty = (IntProperty) mProperty; 781 } 782 } 783 784 @Override 785 public void setIntValues(int... values) { 786 super.setIntValues(values); 787 mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; 788 } 789 790 @Override 791 void calculateValue(float fraction) { 792 mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction); 793 } 794 795 @Override 796 Object getAnimatedValue() { 797 return mIntAnimatedValue; 798 } 799 800 @Override 801 public IntPropertyValuesHolder clone() { 802 IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone(); 803 newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet; 804 return newPVH; 805 } 806 807 /** 808 * Internal function to set the value on the target object, using the setter set up 809 * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator 810 * to handle turning the value calculated by ValueAnimator into a value set on the object 811 * according to the name of the property. 812 * @param target The target object on which the value is set 813 */ 814 @Override 815 void setAnimatedValue(Object target) { 816 if (mIntProperty != null) { 817 mIntProperty.setValue(target, mIntAnimatedValue); 818 return; 819 } 820 if (mProperty != null) { 821 mProperty.set(target, mIntAnimatedValue); 822 return; 823 } 824 if (mJniSetter != 0) { 825 nCallIntMethod(target, mJniSetter, mIntAnimatedValue); 826 return; 827 } 828 if (mSetter != null) { 829 try { 830 mTmpValueArray[0] = mIntAnimatedValue; 831 mSetter.invoke(target, mTmpValueArray); 832 } catch (InvocationTargetException e) { 833 Log.e("PropertyValuesHolder", e.toString()); 834 } catch (IllegalAccessException e) { 835 Log.e("PropertyValuesHolder", e.toString()); 836 } 837 } 838 } 839 840 @Override 841 void setupSetter(Class targetClass) { 842 if (mProperty != null) { 843 return; 844 } 845 // Check new static hashmap<propName, int> for setter method 846 try { 847 mPropertyMapLock.writeLock().lock(); 848 HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass); 849 if (propertyMap != null) { 850 Integer mJniSetterInteger = propertyMap.get(mPropertyName); 851 if (mJniSetterInteger != null) { 852 mJniSetter = mJniSetterInteger; 853 } 854 } 855 if (mJniSetter == 0) { 856 String methodName = getMethodName("set", mPropertyName); 857 mJniSetter = nGetIntMethod(targetClass, methodName); 858 if (mJniSetter != 0) { 859 if (propertyMap == null) { 860 propertyMap = new HashMap<String, Integer>(); 861 sJNISetterPropertyMap.put(targetClass, propertyMap); 862 } 863 propertyMap.put(mPropertyName, mJniSetter); 864 } 865 } 866 } catch (NoSuchMethodError e) { 867 // Couldn't find it via JNI - try reflection next. Probably means the method 868 // doesn't exist, or the type is wrong. An error will be logged later if 869 // reflection fails as well. 870 } finally { 871 mPropertyMapLock.writeLock().unlock(); 872 } 873 if (mJniSetter == 0) { 874 // Couldn't find method through fast JNI approach - just use reflection 875 super.setupSetter(targetClass); 876 } 877 } 878 } 879 880 static class FloatPropertyValuesHolder extends PropertyValuesHolder { 881 882 // Cache JNI functions to avoid looking them up twice 883 private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = 884 new HashMap<Class, HashMap<String, Integer>>(); 885 int mJniSetter; 886 private FloatProperty mFloatProperty; 887 888 FloatKeyframeSet mFloatKeyframeSet; 889 float mFloatAnimatedValue; 890 891 public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) { 892 super(propertyName); 893 mValueType = float.class; 894 mKeyframeSet = keyframeSet; 895 mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; 896 } 897 898 public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) { 899 super(property); 900 mValueType = float.class; 901 mKeyframeSet = keyframeSet; 902 mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; 903 if (property instanceof FloatProperty) { 904 mFloatProperty = (FloatProperty) mProperty; 905 } 906 } 907 908 public FloatPropertyValuesHolder(String propertyName, float... values) { 909 super(propertyName); 910 setFloatValues(values); 911 } 912 913 public FloatPropertyValuesHolder(Property property, float... values) { 914 super(property); 915 setFloatValues(values); 916 if (property instanceof FloatProperty) { 917 mFloatProperty = (FloatProperty) mProperty; 918 } 919 } 920 921 @Override 922 public void setFloatValues(float... values) { 923 super.setFloatValues(values); 924 mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; 925 } 926 927 @Override 928 void calculateValue(float fraction) { 929 mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction); 930 } 931 932 @Override 933 Object getAnimatedValue() { 934 return mFloatAnimatedValue; 935 } 936 937 @Override 938 public FloatPropertyValuesHolder clone() { 939 FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone(); 940 newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet; 941 return newPVH; 942 } 943 944 /** 945 * Internal function to set the value on the target object, using the setter set up 946 * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator 947 * to handle turning the value calculated by ValueAnimator into a value set on the object 948 * according to the name of the property. 949 * @param target The target object on which the value is set 950 */ 951 @Override 952 void setAnimatedValue(Object target) { 953 if (mFloatProperty != null) { 954 mFloatProperty.setValue(target, mFloatAnimatedValue); 955 return; 956 } 957 if (mProperty != null) { 958 mProperty.set(target, mFloatAnimatedValue); 959 return; 960 } 961 if (mJniSetter != 0) { 962 nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue); 963 return; 964 } 965 if (mSetter != null) { 966 try { 967 mTmpValueArray[0] = mFloatAnimatedValue; 968 mSetter.invoke(target, mTmpValueArray); 969 } catch (InvocationTargetException e) { 970 Log.e("PropertyValuesHolder", e.toString()); 971 } catch (IllegalAccessException e) { 972 Log.e("PropertyValuesHolder", e.toString()); 973 } 974 } 975 } 976 977 @Override 978 void setupSetter(Class targetClass) { 979 if (mProperty != null) { 980 return; 981 } 982 // Check new static hashmap<propName, int> for setter method 983 try { 984 mPropertyMapLock.writeLock().lock(); 985 HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass); 986 if (propertyMap != null) { 987 Integer mJniSetterInteger = propertyMap.get(mPropertyName); 988 if (mJniSetterInteger != null) { 989 mJniSetter = mJniSetterInteger; 990 } 991 } 992 if (mJniSetter == 0) { 993 String methodName = getMethodName("set", mPropertyName); 994 mJniSetter = nGetFloatMethod(targetClass, methodName); 995 if (mJniSetter != 0) { 996 if (propertyMap == null) { 997 propertyMap = new HashMap<String, Integer>(); 998 sJNISetterPropertyMap.put(targetClass, propertyMap); 999 } 1000 propertyMap.put(mPropertyName, mJniSetter); 1001 } 1002 } 1003 } catch (NoSuchMethodError e) { 1004 // Couldn't find it via JNI - try reflection next. Probably means the method 1005 // doesn't exist, or the type is wrong. An error will be logged later if 1006 // reflection fails as well. 1007 } finally { 1008 mPropertyMapLock.writeLock().unlock(); 1009 } 1010 if (mJniSetter == 0) { 1011 // Couldn't find method through fast JNI approach - just use reflection 1012 super.setupSetter(targetClass); 1013 } 1014 } 1015 1016 } 1017 1018 native static private int nGetIntMethod(Class targetClass, String methodName); 1019 native static private int nGetFloatMethod(Class targetClass, String methodName); 1020 native static private void nCallIntMethod(Object target, int methodID, int arg); 1021 native static private void nCallFloatMethod(Object target, int methodID, float arg); 1022 }