今天来分析下属性动画的源码,首先从ObjectAnimator的ofFloat方法出发
ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "translationY", 0f,1000f);
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {ObjectAnimator anim = new ObjectAnimator(target, propertyName);anim.setFloatValues(values);return anim;}
发现new了一个ObjectAnimator ,把target和propertyName传入
private ObjectAnimator(Object target, String propertyName) {setTarget(target);setPropertyName(propertyName);}@Overridepublic void setTarget(@Nullable Object target) {final Object oldTarget = getTarget();if (oldTarget != target) {if (isStarted()) {cancel();}mTarget = target == null ? null : new WeakReference<Object>(target);// New target should cause re-initialization prior to startingmInitialized = false;}}public void setPropertyName(@NonNull String propertyName) {// mValues could be null if this is being constructed piecemeal. Just record the// propertyName to be used later when setValues() is called if so.if (mValues != null) {PropertyValuesHolder valuesHolder = mValues[0];String oldName = valuesHolder.getPropertyName();valuesHolder.setPropertyName(propertyName);mValuesMap.remove(oldName);mValuesMap.put(propertyName, valuesHolder);}mPropertyName = propertyName;// New property/values/target should cause re-initialization prior to startingmInitialized = false;}
setTarget方法使用了弱引用,setPropertyName方法是用于设置属性名用的并用PropertyValuesHolder存放,mValues 这时为null,所以仅仅将propertyName设置给mPropertyName,这段不是特别重要,了解下就行
ofFloat方法中接下来又调用了anim.setFloatValues(values);
@Overridepublic void setFloatValues(float... values) {if (mValues == null || mValues.length == 0) {// No values yet - this animator is being constructed piecemeal. Init the values with// whatever the current propertyName isif (mProperty != null) {setValues(PropertyValuesHolder.ofFloat(mProperty, values));} else {setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));}} else {super.setFloatValues(values);}}
显然这时候的mValues为null,mProperty 也为null,调用的setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));该方法是父类ValueAnimator的方法
public void setValues(PropertyValuesHolder... values) {int numValues = values.length;mValues = values;mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);for (int i = 0; i < numValues; ++i) {PropertyValuesHolder valuesHolder = values[i];mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);}// New property/values/target should cause re-initialization prior to startingmInitialized = false;}
其中将mValues 赋值,其他都不是很关键,到此代码就没有后续了,因此前面肯定还有别的操作,我们来到setValues方法传入参数时的PropertyValuesHolder.ofFloat(mPropertyName, values)方法
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {return new FloatPropertyValuesHolder(propertyName, values);}public void setFloatValues(float... values) {mValueType = float.class;mKeyframes = KeyframeSet.ofFloat(values);}static class FloatPropertyValuesHolder extends PropertyValuesHolder {public FloatPropertyValuesHolder(String propertyName, float... values) {super(propertyName);setFloatValues(values);}@Overridepublic void setFloatValues(float... values) {//调用父类的方法super.setFloatValues(values);mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;}}
FloatPropertyValuesHolder 的setFloatValues方法调用super.setFloatValues(values),PropertyValuesHolder的setFloatValues方法调用KeyframeSet.ofFloat(values)为mKeyframes赋值,Keyframe是关键帧
(如:ObjectAnimator.ofFloat(iv, "translationY", 0f,1000f),其中0f,和1000f就是关键帧)
该方法解析每一帧到KeyFrames集合
public static KeyframeSet ofFloat(float... values) {boolean badValue = false;int numKeyframes = values.length;FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];if (numKeyframes == 1) {keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);if (Float.isNaN(values[0])) {badValue = true;}} else {keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);for (int i = 1; i < numKeyframes; ++i) {keyframes[i] =(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);if (Float.isNaN(values[i])) {badValue = true;}}}if (badValue) {Log.w("Animator", "Bad value (NaN) in float animator");}return new FloatKeyframeSet(keyframes);}
属性动画的初始化就到这里,我们再来看ObjectAnimator到底是怎么运作的,来到它的start()方法
@Overridepublic void start() {AnimationHandler.getInstance().autoCancelBasedOn(this);if (DBG) {Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());for (int i = 0; i < mValues.length; ++i) {PropertyValuesHolder pvh = mValues[i];Log.d(LOG_TAG, " Values[" + i + "]: " +pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +pvh.mKeyframes.getValue(1));}}super.start();}
调用父类ValueAnimator的start方法
@Overridepublic void start() {start(false);}private void start(boolean playBackwards) {if (Looper.myLooper() == null) {throw new AndroidRuntimeException("Animators may only be run on Looper threads");}mReversing = playBackwards;mSelfPulse = !mSuppressSelfPulseRequested;// Special case: reversing from seek-to-0 should act as if not seeked at all.if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {if (mRepeatCount == INFINITE) {// Calculate the fraction of the current iteration.float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));mSeekFraction = 1 - fraction;} else {mSeekFraction = 1 + mRepeatCount - mSeekFraction;}}mStarted = true;mPaused = false;mRunning = false;mAnimationEndRequested = false;// Resets mLastFrameTime when start() is called, so that if the animation was running,// calling start() would put the animation in the// started-but-not-yet-reached-the-first-frame phase.mLastFrameTime = -1;mFirstFrameTime = -1;mStartTime = -1;addAnimationCallback(0);if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {// If there's no start delay, init the animation and notify start listeners right away// to be consistent with the previous behavior. Otherwise, postpone this until the first// frame after the start delay.//正常情况下调用了这个方法startAnimation();if (mSeekFraction == -1) {// No seek, start at play time 0. Note that the reason we are not using fraction 0// is because for animations with 0 duration, we want to be consistent with pre-N// behavior: skip to the final value immediately.setCurrentPlayTime(0);} else {setCurrentFraction(mSeekFraction);}}}
再来到startAnimation方法
private void startAnimation() {if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),System.identityHashCode(this));}mAnimationEndRequested = false;//注意,这里很关键,ObjectAnimator也实现了initAnimation方法initAnimation();mRunning = true;if (mSeekFraction >= 0) {mOverallFraction = mSeekFraction;} else {mOverallFraction = 0f;}if (mListeners != null) {notifyStartListeners();}}
我们来到ObjectAnimator的initAnimation方法
@CallSuper@Overridevoid initAnimation() {if (!mInitialized) {// mValueType may change due to setter/getter setup; do this before calling super.init(),// which uses mValueType to set up the default type evaluator.final Object target = getTarget();if (target != null) {final int numValues = mValues.length;for (int i = 0; i < numValues; ++i) {mValues[i].setupSetterAndGetter(target);}}super.initAnimation();}}
mValues是一个PropertyValuesHolder数组,所以调用的是PropertyValuesHolder的setupSetterAndGetter方法,并把target传入
/*** Internal function (called from ObjectAnimator) to set up the setter and getter* prior to running the animation. If the setter has not been manually set for this* object, it will be derived automatically given the property name, target object, and* types of values supplied. If no getter has been set, it will be supplied iff any of the* supplied values was null. If there is a null value, then the getter (supplied or derived)* will be called to set those null values to the current value of the property* on the target object.* @param target The object on which the setter (and possibly getter) exist.*/void setupSetterAndGetter(Object target) {//这时mProperty为nullif (mProperty != null) {// check to make sure that mProperty is on the class of targettry {Object testValue = null;List<Keyframe> keyframes = mKeyframes.getKeyframes();int keyframeCount = keyframes == null ? 0 : keyframes.size();for (int i = 0; i < keyframeCount; i++) {Keyframe kf = keyframes.get(i);if (!kf.hasValue() || kf.valueWasSetOnStart()) {if (testValue == null) {testValue = convertBack(mProperty.get(target));}kf.setValue(testValue);kf.setValueWasSetOnStart(true);}}return;} catch (ClassCastException e) {Log.w("PropertyValuesHolder","No such property (" + mProperty.getName() +") on target object " + target + ". Trying reflection instead");mProperty = null;}}// We can't just say 'else' here because the catch statement sets mProperty to null.if (mProperty == null) {//获取了classClass targetClass = target.getClass();if (mSetter == null) {//进入判断setupSetter(targetClass);}List<Keyframe> keyframes = mKeyframes.getKeyframes();int keyframeCount = keyframes == null ? 0 : keyframes.size();for (int i = 0; i < keyframeCount; i++) {Keyframe kf = keyframes.get(i);if (!kf.hasValue() || kf.valueWasSetOnStart()) {if (mGetter == null) {setupGetter(targetClass);if (mGetter == null) {// Already logged the error - just return to avoid NPEreturn;}}try {Object value = convertBack(mGetter.invoke(target));kf.setValue(value);kf.setValueWasSetOnStart(true);} catch (InvocationTargetException e) {Log.e("PropertyValuesHolder", e.toString());} catch (IllegalAccessException e) {Log.e("PropertyValuesHolder", e.toString());}}}}}
我们再看setupSetter(targetClass)方法
/*** Utility function to get the setter from targetClass* @param targetClass The Class on which the requested method should exist.*/void setupSetter(Class targetClass) {Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();//将前缀"set"传入mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);}/*** Returns the setter or getter requested. This utility function checks whether the* requested method exists in the propertyMapMap cache. If not, it calls another* utility function to request the Method from the targetClass directly.* @param targetClass The Class on which the requested method should exist.* @param propertyMapMap The cache of setters/getters derived so far.* @param prefix "set" or "get", for the setter or getter.* @param valueType The type of parameter passed into the method (null for getter).* @return Method the method associated with mPropertyName.*/private Method setupSetterOrGetter(Class targetClass,HashMap<Class, HashMap<String, Method>> propertyMapMap,String prefix, Class valueType) {Method setterOrGetter = null;synchronized(propertyMapMap) {// Have to lock property map prior to reading it, to guard against// another thread putting something in there after we've checked it// but before we've added an entry to itHashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);boolean wasInMap = false;if (propertyMap != null) {wasInMap = propertyMap.containsKey(mPropertyName);if (wasInMap) {setterOrGetter = propertyMap.get(mPropertyName);}}if (!wasInMap) {//拼接setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);if (propertyMap == null) {propertyMap = new HashMap<String, Method>();propertyMapMap.put(targetClass, propertyMap);}//setterOrGetter是Method,将它存入propertyMapMap方便利用反射propertyMap.put(mPropertyName, setterOrGetter);}}return setterOrGetter;}
我们再看下getPropertyFunction方法
/*** Determine the setter or getter function using the JavaBeans convention of setFoo or* getFoo for a property named 'foo'. This function figures out what the name of the* function should be and uses reflection to find the Method with that name on the* target object.** @param targetClass The class to search for the method* @param prefix "set" or "get", depending on whether we need a setter or getter.* @param valueType The type of the parameter (in the case of a setter). This type* is derived from the values set on this PropertyValuesHolder. This type is used as* a first guess at the parameter type, but we check for methods with several different* types to avoid problems with slight mis-matches between supplied values and actual* value types used on the setter.* @return Method the method associated with mPropertyName.*/private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {// TODO: faster implementation...Method returnVal = null;//拼接String methodName = getMethodName(prefix, mPropertyName);Class args[] = null;if (valueType == null) {try {returnVal = targetClass.getMethod(methodName, args);} catch (NoSuchMethodException e) {// Swallow the error, log it later}} else {args = new Class[1];Class typeVariants[];if (valueType.equals(Float.class)) {typeVariants = FLOAT_VARIANTS;} else if (valueType.equals(Integer.class)) {typeVariants = INTEGER_VARIANTS;} else if (valueType.equals(Double.class)) {typeVariants = DOUBLE_VARIANTS;} else {typeVariants = new Class[1];typeVariants[0] = valueType;}for (Class typeVariant : typeVariants) {args[0] = typeVariant;try {returnVal = targetClass.getMethod(methodName, args);if (mConverter == null) {// change the value type to suitmValueType = typeVariant;}return returnVal;} catch (NoSuchMethodException e) {// Swallow the error and keep trying other variants}}// If we got here, then no appropriate function was found}if (returnVal == null) {Log.w("PropertyValuesHolder", "Method " +getMethodName(prefix, mPropertyName) + "() with type " + valueType +" not found on target class " + targetClass);}return returnVal;}/*** Utility method to derive a setter/getter method name from a property name, where the* prefix is typically "set" or "get" and the first letter of the property name is* capitalized.** @param prefix The precursor to the method name, before the property name begins, typically* "set" or "get".* @param propertyName The name of the property that represents the bulk of the method name* after the prefix. The first letter of this word will be capitalized in the resulting* method name.* @return String the property name converted to a method name according to the conventions* specified above.*/static String getMethodName(String prefix, String propertyName) {if (propertyName == null || propertyName.length() == 0) {// shouldn't get herereturn prefix;}//将第一个字母转为大写char firstLetter = Character.toUpperCase(propertyName.charAt(0));String theRest = propertyName.substring(1);return prefix + firstLetter + theRest;}
getMethodName方法将前缀和我们传入的属性名拼接,最终变成setXXX方法
我们回到ObjectAnimator的initAnimation方法,它又调用了super.initAnimation()
void initAnimation() {if (!mInitialized) {int numValues = mValues.length;for (int i = 0; i < numValues; ++i) {mValues[i].init();}mInitialized = true;}}
void init() {if (mEvaluator == null) {// We already handle int and float automatically, but not their Object// equivalentsmEvaluator = (mValueType == Integer.class) ? sIntEvaluator :(mValueType == Float.class) ? sFloatEvaluator :null;}if (mEvaluator != null) {// KeyframeSet knows how to evaluate the common types - only give it a custom// evaluator if one has been set on this classmKeyframes.setEvaluator(mEvaluator);}}
PropertyValuesHolder的init方法将估值器Evaluator和关键帧集合绑定
回到ValueAnimator的start方法
private void start(boolean playBackwards) {if (Looper.myLooper() == null) {throw new AndroidRuntimeException("Animators may only be run on Looper threads");}mReversing = playBackwards;mSelfPulse = !mSuppressSelfPulseRequested;// Special case: reversing from seek-to-0 should act as if not seeked at all.if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {if (mRepeatCount == INFINITE) {// Calculate the fraction of the current iteration.float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));mSeekFraction = 1 - fraction;} else {mSeekFraction = 1 + mRepeatCount - mSeekFraction;}}mStarted = true;mPaused = false;mRunning = false;mAnimationEndRequested = false;// Resets mLastFrameTime when start() is called, so that if the animation was running,// calling start() would put the animation in the// started-but-not-yet-reached-the-first-frame phase.mLastFrameTime = -1;mFirstFrameTime = -1;mStartTime = -1;//回调addAnimationCallback(0);if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {// If there's no start delay, init the animation and notify start listeners right away// to be consistent with the previous behavior. Otherwise, postpone this until the first// frame after the start delay.startAnimation();if (mSeekFraction == -1) {// No seek, start at play time 0. Note that the reason we are not using fraction 0// is because for animations with 0 duration, we want to be consistent with pre-N// behavior: skip to the final value immediately.setCurrentPlayTime(0);} else {setCurrentFraction(mSeekFraction);}}}
之前分析了startAnimation方法,我们再看addAnimationCallback方法
private void addAnimationCallback(long delay) {if (!mSelfPulse) {return;}getAnimationHandler().addAnimationFrameCallback(this, delay);}
来到AnimationHandler类
//之后会调用private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {@Overridepublic void doFrame(long frameTimeNanos) {doAnimationFrame(getProvider().getFrameTime());if (mAnimationCallbacks.size() > 0) {getProvider().postFrameCallback(this);}}};/*** Register to get a callback on the next frame after the delay.*/public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {//mAnimationCallbacks一开始为空if (mAnimationCallbacks.size() == 0) {getProvider().postFrameCallback(mFrameCallback);}if (!mAnimationCallbacks.contains(callback)) {mAnimationCallbacks.add(callback);}if (delay > 0) {mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));}}
再看getProvider().postFrameCallback(mFrameCallback)方法
private AnimationFrameCallbackProvider getProvider() {if (mProvider == null) {mProvider = new MyFrameCallbackProvider();}return mProvider;}/*** Default provider of timing pulse that uses Choreographer for frame callbacks.*/private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {final Choreographer mChoreographer = Choreographer.getInstance();@Overridepublic void postFrameCallback(Choreographer.FrameCallback callback) {mChoreographer.postFrameCallback(callback);}@Overridepublic void postCommitCallback(Runnable runnable) {//关键代码mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);}@Overridepublic long getFrameTime() {return mChoreographer.getFrameTime();}@Overridepublic long getFrameDelay() {return Choreographer.getFrameDelay();}@Overridepublic void setFrameDelay(long delay) {Choreographer.setFrameDelay(delay);}}
Choreographer类是一个垂直同步类---用于控制每隔16ms刷新的控制器,可以通过这个定时控制器不断调用上面的runnable线程,源码比较多,只要了解下就行了,我们发现先调用mChoreographer.postFrameCallback(callback),callback就是AnimationHandler中定义的mFrameCallback ,其中又会调用 doAnimationFrame(getProvider().getFrameTime());
来到doAnimationFrame方法
private void doAnimationFrame(long frameTime) {long currentTime = SystemClock.uptimeMillis();final int size = mAnimationCallbacks.size();for (int i = 0; i < size; i++) {final AnimationFrameCallback callback = mAnimationCallbacks.get(i);if (callback == null) {continue;}if (isCallbackDue(callback, currentTime)) {//调用callback.doAnimationFrame(frameTime);if (mCommitCallbacks.contains(callback)) {getProvider().postCommitCallback(new Runnable() {@Overridepublic void run() {commitAnimationFrame(callback, getProvider().getFrameTime());}});}}}cleanUpList();}
调用callback.doAnimationFrame(frameTime),callback就是AnimationFrameCallback接口实例化,我们又发现ValueAnimator实现了AnimationFrameCallback,所以这里调用的是ValueAnimator的doAnimationFrame方法
/*** Processes a frame of the animation, adjusting the start time if needed.** @param frameTime The frame time.* @return true if the animation has ended.* @hide*/public final boolean doAnimationFrame(long frameTime) {if (mStartTime < 0) {// First frame. If there is start delay, start delay count down will happen *after* this// frame.mStartTime = mReversing? frameTime: frameTime + (long) (mStartDelay * resolveDurationScale());}// Handle pause/resumeif (mPaused) {mPauseTime = frameTime;removeAnimationCallback();return false;} else if (mResumed) {mResumed = false;if (mPauseTime > 0) {// Offset by the duration that the animation was pausedmStartTime += (frameTime - mPauseTime);}}if (!mRunning) {// If not running, that means the animation is in the start delay phase of a forward// running animation. In the case of reversing, we want to run start delay in the end.if (mStartTime > frameTime && mSeekFraction == -1) {// This is when no seek fraction is set during start delay. If developers change the// seek fraction during the delay, animation will start from the seeked position// right away.return false;} else {// If mRunning is not set by now, that means non-zero start delay,// no seeking, not reversing. At this point, start delay has passed.mRunning = true;startAnimation();}}if (mLastFrameTime < 0) {if (mSeekFraction >= 0) {long seekTime = (long) (getScaledDuration() * mSeekFraction);mStartTime = frameTime - seekTime;mSeekFraction = -1;}mStartTimeCommitted = false; // allow start time to be compensated for jank}mLastFrameTime = frameTime;// The frame time might be before the start time during the first frame of// an animation. The "current time" must always be on or after the start// time to avoid animating frames at negative time intervals. In practice, this// is very rare and only happens when seeking backwards.final long currentTime = Math.max(frameTime, mStartTime);//关键代码boolean finished = animateBasedOnTime(currentTime);if (finished) {endAnimation();}return finished;}
关键代码是:boolean finished = animateBasedOnTime(currentTime);
/*** This internal function processes a single animation frame for a given animation. The* currentTime parameter is the timing pulse sent by the handler, used to calculate the* elapsed duration, and therefore* the elapsed fraction, of the animation. The return value indicates whether the animation* should be ended (which happens when the elapsed time of the animation exceeds the* animation's duration, including the repeatCount).** @param currentTime The current time, as tracked by the static timing handler* @return true if the animation's duration, including any repetitions due to* <code>repeatCount</code> has been exceeded and the animation should be ended.*/boolean animateBasedOnTime(long currentTime) {boolean done = false;if (mRunning) {final long scaledDuration = getScaledDuration();final float fraction = scaledDuration > 0 ?(float)(currentTime - mStartTime) / scaledDuration : 1f;final float lastFraction = mOverallFraction;final boolean newIteration = (int) fraction > (int) lastFraction;final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&(mRepeatCount != INFINITE);if (scaledDuration == 0) {// 0 duration animator, ignore the repeat count and skip to the enddone = true;} else if (newIteration && !lastIterationFinished) {// Time to repeatif (mListeners != null) {int numListeners = mListeners.size();for (int i = 0; i < numListeners; ++i) {mListeners.get(i).onAnimationRepeat(this);}}} else if (lastIterationFinished) {done = true;}mOverallFraction = clampFraction(fraction);float currentIterationFraction = getCurrentIterationFraction(mOverallFraction, mReversing);animateValue(currentIterationFraction);}return done;}
发现其中计算出了动画执行进度,并再最后调用animateValue方法,注意我们现在再ValueAnimator类中,animateValue方法也在ObjectAnimator中实现了,我们看ObjectAnimator的animateValue方法
void animateValue(float fraction) {final Object target = getTarget();if (mTarget != null && target == null) {// We lost the target reference, cancel and clean up. Note: we allow null target if the/// target has never been set.cancel();return;}super.animateValue(fraction);int numValues = mValues.length;for (int i = 0; i < numValues; ++i) {//关键代码mValues[i].setAnimatedValue(target);}}
我们在看FloatPropertyValuesHolder(我们一开始用的是ofFloat)的setAnimatedValue方法
/*** Internal function to set the value on the target object, using the setter set up* earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator* to handle turning the value calculated by ValueAnimator into a value set on the object* according to the name of the property.* @param target The target object on which the value is set*/@Overridevoid setAnimatedValue(Object target) {if (mFloatProperty != null) {mFloatProperty.setValue(target, mFloatAnimatedValue);return;}if (mProperty != null) {mProperty.set(target, mFloatAnimatedValue);return;}if (mJniSetter != 0) {nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);return;}if (mSetter != null) {try {mTmpValueArray[0] = mFloatAnimatedValue;mSetter.invoke(target, mTmpValueArray);} catch (InvocationTargetException e) {Log.e("PropertyValuesHolder", e.toString());} catch (IllegalAccessException e) {Log.e("PropertyValuesHolder", e.toString());}}}