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