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