author | Margaret Leibovic <margaret.leibovic@gmail.com> |
Fri, 25 Jul 2014 15:20:13 -0700 | |
changeset 196328 | 5853d370248d3193b18cbc8d7aa25383c9a63a87 |
parent 196327 | b5ae1c999f27a9025e5beae2cdf27e0ee6b3b94d |
child 196329 | f59f7e13d384e841d07b4862ea761d3956ec5e41 |
push id | 46844 |
push user | cbook@mozilla.com |
push date | Mon, 28 Jul 2014 14:30:47 +0000 |
treeherder | mozilla-inbound@7dd701896de8 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bnicholson |
bugs | 1044133 |
milestone | 34.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -77,16 +77,17 @@ endif JAVA_CLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_CLASSPATH))) ALL_JARS = \ gecko-R.jar \ gecko-browser.jar \ gecko-mozglue.jar \ gecko-util.jar \ + nineoldandroids.jar \ squareup-picasso.jar \ sync-thirdparty.jar \ websockets.jar \ $(NULL) ifdef MOZ_WEBRTC ALL_JARS += webrtc.jar endif
--- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -473,30 +473,64 @@ if CONFIG['MOZ_CRASHREPORTER']: ANDROID_RES_DIRS += [ SRCDIR + '/crashreporter/res' ] gbjar.sources += sync_java_files gbjar.generated_sources += sync_generated_java_files gbjar.extra_jars = [ 'gecko-R.jar', 'gecko-mozglue.jar', 'gecko-util.jar', + 'nineoldandroids.jar', 'squareup-picasso.jar', 'sync-thirdparty.jar', 'websockets.jar', ] if CONFIG['MOZ_NATIVE_DEVICES']: gbjar.extra_jars += [CONFIG['ANDROID_APPCOMPAT_LIB']] gbjar.extra_jars += [CONFIG['ANDROID_MEDIAROUTER_LIB']] gbjar.extra_jars += [CONFIG['GOOGLE_PLAY_SERVICES_LIB']] gbjar.sources += ['ChromeCast.java'] gbjar.sources += ['MediaPlayerManager.java'] gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough'] +noajar = add_java_jar('nineoldandroids') +noajar.sources += [ thirdparty_source_dir + f for f in [ + 'com/nineoldandroids/animation/Animator.java', + 'com/nineoldandroids/animation/AnimatorInflater.java', + 'com/nineoldandroids/animation/AnimatorListenerAdapter.java', + 'com/nineoldandroids/animation/AnimatorSet.java', + 'com/nineoldandroids/animation/ArgbEvaluator.java', + 'com/nineoldandroids/animation/FloatEvaluator.java', + 'com/nineoldandroids/animation/FloatKeyframeSet.java', + 'com/nineoldandroids/animation/IntEvaluator.java', + 'com/nineoldandroids/animation/IntKeyframeSet.java', + 'com/nineoldandroids/animation/Keyframe.java', + 'com/nineoldandroids/animation/KeyframeSet.java', + 'com/nineoldandroids/animation/ObjectAnimator.java', + 'com/nineoldandroids/animation/PreHoneycombCompat.java', + 'com/nineoldandroids/animation/PropertyValuesHolder.java', + 'com/nineoldandroids/animation/TimeAnimator.java', + 'com/nineoldandroids/animation/TypeEvaluator.java', + 'com/nineoldandroids/animation/ValueAnimator.java', + 'com/nineoldandroids/util/FloatProperty.java', + 'com/nineoldandroids/util/IntProperty.java', + 'com/nineoldandroids/util/NoSuchPropertyException.java', + 'com/nineoldandroids/util/Property.java', + 'com/nineoldandroids/util/ReflectiveProperty.java', + 'com/nineoldandroids/view/animation/AnimatorProxy.java', + 'com/nineoldandroids/view/ViewHelper.java', + 'com/nineoldandroids/view/ViewPropertyAnimator.java', + 'com/nineoldandroids/view/ViewPropertyAnimatorHC.java', + 'com/nineoldandroids/view/ViewPropertyAnimatorICS.java', + 'com/nineoldandroids/view/ViewPropertyAnimatorPreHC.java' +] ] +#noajar.javac_flags += ['-Xlint:all'] + spjar = add_java_jar('squareup-picasso') spjar.sources += [ thirdparty_source_dir + f for f in [ 'com/squareup/picasso/Action.java', 'com/squareup/picasso/AssetBitmapHunter.java', 'com/squareup/picasso/BitmapHunter.java', 'com/squareup/picasso/Cache.java', 'com/squareup/picasso/Callback.java', 'com/squareup/picasso/ContactsPhotoBitmapHunter.java', @@ -578,16 +612,17 @@ if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY'] search_activity = add_java_jar('search-activity') search_activity.sources += [search_source_dir + '/' + f for f in search_activity_sources] search_activity.javac_flags += ['-Xlint:all'] search_activity.extra_jars = [ 'gecko-R.jar', 'gecko-browser.jar', 'gecko-mozglue.jar', 'gecko-util.jar', + 'nineoldandroids.jar' ] generated_recursive_make_targets = ['.aapt.deps', '.locales.deps'] # Captures dependencies on Android manifest and all resources. generated = add_android_eclipse_library_project('FennecResourcesGenerated') generated.package_name = 'org.mozilla.fennec.resources.generated' generated.res = OBJDIR + '/res' generated.recursive_make_targets += generated_recursive_make_targets
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/Animator.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +import java.util.ArrayList; + +import android.view.animation.Interpolator; + +/** + * This is the superclass for classes which provide basic support for animations which can be + * started, ended, and have <code>AnimatorListeners</code> added to them. + */ +public abstract class Animator implements Cloneable { + + + /** + * The set of listeners to be sent events through the life of an animation. + */ + ArrayList<AnimatorListener> mListeners = null; + + /** + * Starts this animation. If the animation has a nonzero startDelay, the animation will start + * running after that delay elapses. A non-delayed animation will have its initial + * value(s) set immediately, followed by calls to + * {@link AnimatorListener#onAnimationStart(Animator)} for any listeners of this animator. + * + * <p>The animation started by calling this method will be run on the thread that called + * this method. This thread should have a Looper on it (a runtime exception will be thrown if + * this is not the case). Also, if the animation will animate + * properties of objects in the view hierarchy, then the calling thread should be the UI + * thread for that view hierarchy.</p> + * + */ + public void start() { + } + + /** + * Cancels the animation. Unlike {@link #end()}, <code>cancel()</code> causes the animation to + * stop in its tracks, sending an + * {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to + * its listeners, followed by an + * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message. + * + * <p>This method must be called on the thread that is running the animation.</p> + */ + public void cancel() { + } + + /** + * Ends the animation. This causes the animation to assign the end value of the property being + * animated, then calling the + * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on + * its listeners. + * + * <p>This method must be called on the thread that is running the animation.</p> + */ + public void end() { + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + * + * @return the number of milliseconds to delay running the animation + */ + public abstract long getStartDelay(); + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + + * @param startDelay The amount of the delay, in milliseconds + */ + public abstract void setStartDelay(long startDelay); + + + /** + * Sets the length of the animation. + * + * @param duration The length of the animation, in milliseconds. + */ + public abstract Animator setDuration(long duration); + + /** + * Gets the length of the animation. + * + * @return The length of the animation, in milliseconds. + */ + public abstract long getDuration(); + + /** + * The time interpolator used in calculating the elapsed fraction of this animation. The + * interpolator determines whether the animation runs with linear or non-linear motion, + * such as acceleration and deceleration. The default value is + * {@link android.view.animation.AccelerateDecelerateInterpolator} + * + * @param value the interpolator to be used by this animation + */ + public abstract void setInterpolator(/*Time*/Interpolator value); + + /** + * Returns whether this Animator is currently running (having been started and gone past any + * initial startDelay period and not yet ended). + * + * @return Whether the Animator is running. + */ + public abstract boolean isRunning(); + + /** + * Returns whether this Animator has been started and not yet ended. This state is a superset + * of the state of {@link #isRunning()}, because an Animator with a nonzero + * {@link #getStartDelay() startDelay} will return true for {@link #isStarted()} during the + * delay phase, whereas {@link #isRunning()} will return true only after the delay phase + * is complete. + * + * @return Whether the Animator has been started and not yet ended. + */ + public boolean isStarted() { + // Default method returns value for isRunning(). Subclasses should override to return a + // real value. + return isRunning(); + } + + /** + * Adds a listener to the set of listeners that are sent events through the life of an + * animation, such as start, repeat, and end. + * + * @param listener the listener to be added to the current set of listeners for this animation. + */ + public void addListener(AnimatorListener listener) { + if (mListeners == null) { + mListeners = new ArrayList<AnimatorListener>(); + } + mListeners.add(listener); + } + + /** + * Removes a listener from the set listening to this animation. + * + * @param listener the listener to be removed from the current set of listeners for this + * animation. + */ + public void removeListener(AnimatorListener listener) { + if (mListeners == null) { + return; + } + mListeners.remove(listener); + if (mListeners.size() == 0) { + mListeners = null; + } + } + + /** + * Gets the set of {@link android.animation.Animator.AnimatorListener} objects that are currently + * listening for events on this <code>Animator</code> object. + * + * @return ArrayList<AnimatorListener> The set of listeners. + */ + public ArrayList<AnimatorListener> getListeners() { + return mListeners; + } + + /** + * Removes all listeners from this object. This is equivalent to calling + * <code>getListeners()</code> followed by calling <code>clear()</code> on the + * returned list of listeners. + */ + public void removeAllListeners() { + if (mListeners != null) { + mListeners.clear(); + mListeners = null; + } + } + + @Override + public Animator clone() { + try { + final Animator anim = (Animator) super.clone(); + if (mListeners != null) { + ArrayList<AnimatorListener> oldListeners = mListeners; + anim.mListeners = new ArrayList<AnimatorListener>(); + int numListeners = oldListeners.size(); + for (int i = 0; i < numListeners; ++i) { + anim.mListeners.add(oldListeners.get(i)); + } + } + return anim; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + /** + * This method tells the object to use appropriate information to extract + * starting values for the animation. For example, a AnimatorSet object will pass + * this call to its child objects to tell them to set up the values. A + * ObjectAnimator object will use the information it has about its target object + * and PropertyValuesHolder objects to get the start values for its properties. + * An ValueAnimator object will ignore the request since it does not have enough + * information (such as a target object) to gather these values. + */ + public void setupStartValues() { + } + + /** + * This method tells the object to use appropriate information to extract + * ending values for the animation. For example, a AnimatorSet object will pass + * this call to its child objects to tell them to set up the values. A + * ObjectAnimator object will use the information it has about its target object + * and PropertyValuesHolder objects to get the start values for its properties. + * An ValueAnimator object will ignore the request since it does not have enough + * information (such as a target object) to gather these values. + */ + public void setupEndValues() { + } + + /** + * Sets the target object whose property will be animated by this animation. Not all subclasses + * operate on target objects (for example, {@link ValueAnimator}, but this method + * is on the superclass for the convenience of dealing generically with those subclasses + * that do handle targets. + * + * @param target The object being animated + */ + public void setTarget(Object target) { + } + + /** + * <p>An animation listener receives notifications from an animation. + * Notifications indicate animation related events, such as the end or the + * repetition of the animation.</p> + */ + public static interface AnimatorListener { + /** + * <p>Notifies the start of the animation.</p> + * + * @param animation The started animation. + */ + void onAnimationStart(Animator animation); + + /** + * <p>Notifies the end of the animation. This callback is not invoked + * for animations with repeat count set to INFINITE.</p> + * + * @param animation The animation which reached its end. + */ + void onAnimationEnd(Animator animation); + + /** + * <p>Notifies the cancellation of the animation. This callback is not invoked + * for animations with repeat count set to INFINITE.</p> + * + * @param animation The animation which was canceled. + */ + void onAnimationCancel(Animator animation); + + /** + * <p>Notifies the repetition of the animation.</p> + * + * @param animation The animation which was repeated. + */ + void onAnimationRepeat(Animator animation); + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorInflater.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.nineoldandroids.animation; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.content.res.Resources.NotFoundException; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.util.Xml; +import android.view.animation.AnimationUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class is used to instantiate animator XML files into Animator objects. + * <p> + * For performance reasons, inflation relies heavily on pre-processing of + * XML files that is done at build time. Therefore, it is not currently possible + * to use this inflater with an XmlPullParser over a plain XML file at runtime; + * it only works with an XmlPullParser returned from a compiled resource (R. + * <em>something</em> file.) + */ +public class AnimatorInflater { + private static final int[] AnimatorSet = new int[] { + /* 0 */ android.R.attr.ordering, + }; + private static final int AnimatorSet_ordering = 0; + + private static final int[] PropertyAnimator = new int[] { + /* 0 */ android.R.attr.propertyName, + }; + private static final int PropertyAnimator_propertyName = 0; + + private static final int[] Animator = new int[] { + /* 0 */ android.R.attr.interpolator, + /* 1 */ android.R.attr.duration, + /* 2 */ android.R.attr.startOffset, + /* 3 */ android.R.attr.repeatCount, + /* 4 */ android.R.attr.repeatMode, + /* 5 */ android.R.attr.valueFrom, + /* 6 */ android.R.attr.valueTo, + /* 7 */ android.R.attr.valueType, + }; + private static final int Animator_interpolator = 0; + private static final int Animator_duration = 1; + private static final int Animator_startOffset = 2; + private static final int Animator_repeatCount = 3; + private static final int Animator_repeatMode = 4; + private static final int Animator_valueFrom = 5; + private static final int Animator_valueTo = 6; + private static final int Animator_valueType = 7; + + /** + * These flags are used when parsing AnimatorSet objects + */ + private static final int TOGETHER = 0; + //private static final int SEQUENTIALLY = 1; + + /** + * Enum values used in XML attributes to indicate the value for mValueType + */ + private static final int VALUE_TYPE_FLOAT = 0; + //private static final int VALUE_TYPE_INT = 1; + //private static final int VALUE_TYPE_COLOR = 4; + //private static final int VALUE_TYPE_CUSTOM = 5; + + /** + * Loads an {@link Animator} object from a resource + * + * @param context Application context used to access resources + * @param id The resource id of the animation to load + * @return The animator object reference by the specified id + * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded + */ + public static Animator loadAnimator(Context context, int id) + throws NotFoundException { + + XmlResourceParser parser = null; + try { + parser = context.getResources().getAnimation(id); + return createAnimatorFromXml(context, parser); + } catch (XmlPullParserException ex) { + Resources.NotFoundException rnf = + new Resources.NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + Resources.NotFoundException rnf = + new Resources.NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } finally { + if (parser != null) parser.close(); + } + } + + private static Animator createAnimatorFromXml(Context c, XmlPullParser parser) + throws XmlPullParserException, IOException { + + return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0); + } + + private static Animator createAnimatorFromXml(Context c, XmlPullParser parser, + AttributeSet attrs, AnimatorSet parent, int sequenceOrdering) + throws XmlPullParserException, IOException { + + Animator anim = null; + ArrayList<Animator> childAnims = null; + + // Make sure we are on a start tag. + int type; + int depth = parser.getDepth(); + + while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + if (name.equals("objectAnimator")) { + anim = loadObjectAnimator(c, attrs); + } else if (name.equals("animator")) { + anim = loadAnimator(c, attrs, null); + } else if (name.equals("set")) { + anim = new AnimatorSet(); + TypedArray a = c.obtainStyledAttributes(attrs, + /*com.android.internal.R.styleable.*/AnimatorSet); + + TypedValue orderingValue = new TypedValue(); + a.getValue(/*com.android.internal.R.styleable.*/AnimatorSet_ordering, orderingValue); + int ordering = orderingValue.type == TypedValue.TYPE_INT_DEC ? orderingValue.data : TOGETHER; + + createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim, ordering); + a.recycle(); + } else { + throw new RuntimeException("Unknown animator name: " + parser.getName()); + } + + if (parent != null) { + if (childAnims == null) { + childAnims = new ArrayList<Animator>(); + } + childAnims.add(anim); + } + } + if (parent != null && childAnims != null) { + Animator[] animsArray = new Animator[childAnims.size()]; + int index = 0; + for (Animator a : childAnims) { + animsArray[index++] = a; + } + if (sequenceOrdering == TOGETHER) { + parent.playTogether(animsArray); + } else { + parent.playSequentially(animsArray); + } + } + + return anim; + + } + + private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs) + throws NotFoundException { + + ObjectAnimator anim = new ObjectAnimator(); + + loadAnimator(context, attrs, anim); + + TypedArray a = + context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/PropertyAnimator); + + String propertyName = a.getString(/*com.android.internal.R.styleable.*/PropertyAnimator_propertyName); + + anim.setPropertyName(propertyName); + + a.recycle(); + + return anim; + } + + /** + * Creates a new animation whose parameters come from the specified context and + * attributes set. + * + * @param context the application environment + * @param attrs the set of attributes holding the animation parameters + */ + private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim) + throws NotFoundException { + + TypedArray a = + context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/Animator); + + long duration = a.getInt(/*com.android.internal.R.styleable.*/Animator_duration, 0); + + long startDelay = a.getInt(/*com.android.internal.R.styleable.*/Animator_startOffset, 0); + + int valueType = a.getInt(/*com.android.internal.R.styleable.*/Animator_valueType, + VALUE_TYPE_FLOAT); + + if (anim == null) { + anim = new ValueAnimator(); + } + //TypeEvaluator evaluator = null; + + int valueFromIndex = /*com.android.internal.R.styleable.*/Animator_valueFrom; + int valueToIndex = /*com.android.internal.R.styleable.*/Animator_valueTo; + + boolean getFloats = (valueType == VALUE_TYPE_FLOAT); + + TypedValue tvFrom = a.peekValue(valueFromIndex); + boolean hasFrom = (tvFrom != null); + int fromType = hasFrom ? tvFrom.type : 0; + TypedValue tvTo = a.peekValue(valueToIndex); + boolean hasTo = (tvTo != null); + int toType = hasTo ? tvTo.type : 0; + + if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && + (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) || + (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) && + (toType <= TypedValue.TYPE_LAST_COLOR_INT))) { + // special case for colors: ignore valueType and get ints + getFloats = false; + anim.setEvaluator(new ArgbEvaluator()); + } + + if (getFloats) { + float valueFrom; + float valueTo; + if (hasFrom) { + if (fromType == TypedValue.TYPE_DIMENSION) { + valueFrom = a.getDimension(valueFromIndex, 0f); + } else { + valueFrom = a.getFloat(valueFromIndex, 0f); + } + if (hasTo) { + if (toType == TypedValue.TYPE_DIMENSION) { + valueTo = a.getDimension(valueToIndex, 0f); + } else { + valueTo = a.getFloat(valueToIndex, 0f); + } + anim.setFloatValues(valueFrom, valueTo); + } else { + anim.setFloatValues(valueFrom); + } + } else { + if (toType == TypedValue.TYPE_DIMENSION) { + valueTo = a.getDimension(valueToIndex, 0f); + } else { + valueTo = a.getFloat(valueToIndex, 0f); + } + anim.setFloatValues(valueTo); + } + } else { + int valueFrom; + int valueTo; + if (hasFrom) { + if (fromType == TypedValue.TYPE_DIMENSION) { + valueFrom = (int) a.getDimension(valueFromIndex, 0f); + } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && + (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) { + valueFrom = a.getColor(valueFromIndex, 0); + } else { + valueFrom = a.getInt(valueFromIndex, 0); + } + if (hasTo) { + if (toType == TypedValue.TYPE_DIMENSION) { + valueTo = (int) a.getDimension(valueToIndex, 0f); + } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) && + (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { + valueTo = a.getColor(valueToIndex, 0); + } else { + valueTo = a.getInt(valueToIndex, 0); + } + anim.setIntValues(valueFrom, valueTo); + } else { + anim.setIntValues(valueFrom); + } + } else { + if (hasTo) { + if (toType == TypedValue.TYPE_DIMENSION) { + valueTo = (int) a.getDimension(valueToIndex, 0f); + } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) && + (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { + valueTo = a.getColor(valueToIndex, 0); + } else { + valueTo = a.getInt(valueToIndex, 0); + } + anim.setIntValues(valueTo); + } + } + } + + anim.setDuration(duration); + anim.setStartDelay(startDelay); + + if (a.hasValue(/*com.android.internal.R.styleable.*/Animator_repeatCount)) { + anim.setRepeatCount( + a.getInt(/*com.android.internal.R.styleable.*/Animator_repeatCount, 0)); + } + if (a.hasValue(/*com.android.internal.R.styleable.*/Animator_repeatMode)) { + anim.setRepeatMode( + a.getInt(/*com.android.internal.R.styleable.*/Animator_repeatMode, + ValueAnimator.RESTART)); + } + //if (evaluator != null) { + // anim.setEvaluator(evaluator); + //} + + final int resID = + a.getResourceId(/*com.android.internal.R.styleable.*/Animator_interpolator, 0); + if (resID > 0) { + anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID)); + } + a.recycle(); + + return anim; + } +} \ No newline at end of file
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorListenerAdapter.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +/** + * This adapter class provides empty implementations of the methods from {@link android.animation.Animator.AnimatorListener}. + * Any custom listener that cares only about a subset of the methods of this listener can + * simply subclass this adapter class instead of implementing the interface directly. + */ +public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener { + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationCancel(Animator animation) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationEnd(Animator animation) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationRepeat(Animator animation) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationStart(Animator animation) { + } + +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorSet.java @@ -0,0 +1,1113 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +import android.view.animation.Interpolator; + +/** + * This class plays a set of {@link Animator} objects in the specified order. Animations + * can be set up to play together, in sequence, or after a specified delay. + * + * <p>There are two different approaches to adding animations to a <code>AnimatorSet</code>: + * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or + * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add + * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be + * used in conjunction with methods in the {@link AnimatorSet.Builder Builder} + * class to add animations + * one by one.</p> + * + * <p>It is possible to set up a <code>AnimatorSet</code> with circular dependencies between + * its animations. For example, an animation a1 could be set up to start before animation a2, a2 + * before a3, and a3 before a1. The results of this configuration are undefined, but will typically + * result in none of the affected animations being played. Because of this (and because + * circular dependencies do not make logical sense anyway), circular dependencies + * should be avoided, and the dependency flow of animations should only be in one direction. + */ +public final class AnimatorSet extends Animator { + + /** + * Internal variables + * NOTE: This object implements the clone() method, making a deep copy of any referenced + * objects. As other non-trivial fields are added to this class, make sure to add logic + * to clone() to make deep copies of them. + */ + + /** + * Tracks animations currently being played, so that we know what to + * cancel or end when cancel() or end() is called on this AnimatorSet + */ + private ArrayList<Animator> mPlayingSet = new ArrayList<Animator>(); + + /** + * Contains all nodes, mapped to their respective Animators. When new + * dependency information is added for an Animator, we want to add it + * to a single node representing that Animator, not create a new Node + * if one already exists. + */ + private HashMap<Animator, Node> mNodeMap = new HashMap<Animator, Node>(); + + /** + * Set of all nodes created for this AnimatorSet. This list is used upon + * starting the set, and the nodes are placed in sorted order into the + * sortedNodes collection. + */ + private ArrayList<Node> mNodes = new ArrayList<Node>(); + + /** + * The sorted list of nodes. This is the order in which the animations will + * be played. The details about when exactly they will be played depend + * on the dependency relationships of the nodes. + */ + private ArrayList<Node> mSortedNodes = new ArrayList<Node>(); + + /** + * Flag indicating whether the nodes should be sorted prior to playing. This + * flag allows us to cache the previous sorted nodes so that if the sequence + * is replayed with no changes, it does not have to re-sort the nodes again. + */ + private boolean mNeedsSort = true; + + private AnimatorSetListener mSetListener = null; + + /** + * Flag indicating that the AnimatorSet has been manually + * terminated (by calling cancel() or end()). + * This flag is used to avoid starting other animations when currently-playing + * child animations of this AnimatorSet end. It also determines whether cancel/end + * notifications are sent out via the normal AnimatorSetListener mechanism. + */ + boolean mTerminated = false; + + /** + * Indicates whether an AnimatorSet has been start()'d, whether or + * not there is a nonzero startDelay. + */ + private boolean mStarted = false; + + // The amount of time in ms to delay starting the animation after start() is called + private long mStartDelay = 0; + + // Animator used for a nonzero startDelay + private ValueAnimator mDelayAnim = null; + + + // How long the child animations should last in ms. The default value is negative, which + // simply means that there is no duration set on the AnimatorSet. When a real duration is + // set, it is passed along to the child animations. + private long mDuration = -1; + + + /** + * Sets up this AnimatorSet to play all of the supplied animations at the same time. + * + * @param items The animations that will be started simultaneously. + */ + public void playTogether(Animator... items) { + if (items != null) { + mNeedsSort = true; + Builder builder = play(items[0]); + for (int i = 1; i < items.length; ++i) { + builder.with(items[i]); + } + } + } + + /** + * Sets up this AnimatorSet to play all of the supplied animations at the same time. + * + * @param items The animations that will be started simultaneously. + */ + public void playTogether(Collection<Animator> items) { + if (items != null && items.size() > 0) { + mNeedsSort = true; + Builder builder = null; + for (Animator anim : items) { + if (builder == null) { + builder = play(anim); + } else { + builder.with(anim); + } + } + } + } + + /** + * Sets up this AnimatorSet to play each of the supplied animations when the + * previous animation ends. + * + * @param items The animations that will be started one after another. + */ + public void playSequentially(Animator... items) { + if (items != null) { + mNeedsSort = true; + if (items.length == 1) { + play(items[0]); + } else { + for (int i = 0; i < items.length - 1; ++i) { + play(items[i]).before(items[i+1]); + } + } + } + } + + /** + * Sets up this AnimatorSet to play each of the supplied animations when the + * previous animation ends. + * + * @param items The animations that will be started one after another. + */ + public void playSequentially(List<Animator> items) { + if (items != null && items.size() > 0) { + mNeedsSort = true; + if (items.size() == 1) { + play(items.get(0)); + } else { + for (int i = 0; i < items.size() - 1; ++i) { + play(items.get(i)).before(items.get(i+1)); + } + } + } + } + + /** + * Returns the current list of child Animator objects controlled by this + * AnimatorSet. This is a copy of the internal list; modifications to the returned list + * will not affect the AnimatorSet, although changes to the underlying Animator objects + * will affect those objects being managed by the AnimatorSet. + * + * @return ArrayList<Animator> The list of child animations of this AnimatorSet. + */ + public ArrayList<Animator> getChildAnimations() { + ArrayList<Animator> childList = new ArrayList<Animator>(); + for (Node node : mNodes) { + childList.add(node.animation); + } + return childList; + } + + /** + * Sets the target object for all current {@link #getChildAnimations() child animations} + * of this AnimatorSet that take targets ({@link ObjectAnimator} and + * AnimatorSet). + * + * @param target The object being animated + */ + @Override + public void setTarget(Object target) { + for (Node node : mNodes) { + Animator animation = node.animation; + if (animation instanceof AnimatorSet) { + ((AnimatorSet)animation).setTarget(target); + } else if (animation instanceof ObjectAnimator) { + ((ObjectAnimator)animation).setTarget(target); + } + } + } + + /** + * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations} + * of this AnimatorSet. + * + * @param interpolator the interpolator to be used by each child animation of this AnimatorSet + */ + @Override + public void setInterpolator(/*Time*/Interpolator interpolator) { + for (Node node : mNodes) { + node.animation.setInterpolator(interpolator); + } + } + + /** + * This method creates a <code>Builder</code> object, which is used to + * set up playing constraints. This initial <code>play()</code> method + * tells the <code>Builder</code> the animation that is the dependency for + * the succeeding commands to the <code>Builder</code>. For example, + * calling <code>play(a1).with(a2)</code> sets up the AnimatorSet to play + * <code>a1</code> and <code>a2</code> at the same time, + * <code>play(a1).before(a2)</code> sets up the AnimatorSet to play + * <code>a1</code> first, followed by <code>a2</code>, and + * <code>play(a1).after(a2)</code> sets up the AnimatorSet to play + * <code>a2</code> first, followed by <code>a1</code>. + * + * <p>Note that <code>play()</code> is the only way to tell the + * <code>Builder</code> the animation upon which the dependency is created, + * so successive calls to the various functions in <code>Builder</code> + * will all refer to the initial parameter supplied in <code>play()</code> + * as the dependency of the other animations. For example, calling + * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code> + * and <code>a3</code> when a1 ends; it does not set up a dependency between + * <code>a2</code> and <code>a3</code>.</p> + * + * @param anim The animation that is the dependency used in later calls to the + * methods in the returned <code>Builder</code> object. A null parameter will result + * in a null <code>Builder</code> return value. + * @return Builder The object that constructs the AnimatorSet based on the dependencies + * outlined in the calls to <code>play</code> and the other methods in the + * <code>Builder</code object. + */ + public Builder play(Animator anim) { + if (anim != null) { + mNeedsSort = true; + return new Builder(anim); + } + return null; + } + + /** + * {@inheritDoc} + * + * <p>Note that canceling a <code>AnimatorSet</code> also cancels all of the animations that it + * is responsible for.</p> + */ + @SuppressWarnings("unchecked") + @Override + public void cancel() { + mTerminated = true; + if (isStarted()) { + ArrayList<AnimatorListener> tmpListeners = null; + if (mListeners != null) { + tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationCancel(this); + } + } + if (mDelayAnim != null && mDelayAnim.isRunning()) { + // If we're currently in the startDelay period, just cancel that animator and + // send out the end event to all listeners + mDelayAnim.cancel(); + } else if (mSortedNodes.size() > 0) { + for (Node node : mSortedNodes) { + node.animation.cancel(); + } + } + if (tmpListeners != null) { + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationEnd(this); + } + } + mStarted = false; + } + } + + /** + * {@inheritDoc} + * + * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is + * responsible for.</p> + */ + @Override + public void end() { + mTerminated = true; + if (isStarted()) { + if (mSortedNodes.size() != mNodes.size()) { + // hasn't been started yet - sort the nodes now, then end them + sortNodes(); + for (Node node : mSortedNodes) { + if (mSetListener == null) { + mSetListener = new AnimatorSetListener(this); + } + node.animation.addListener(mSetListener); + } + } + if (mDelayAnim != null) { + mDelayAnim.cancel(); + } + if (mSortedNodes.size() > 0) { + for (Node node : mSortedNodes) { + node.animation.end(); + } + } + if (mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationEnd(this); + } + } + mStarted = false; + } + } + + /** + * Returns true if any of the child animations of this AnimatorSet have been started and have + * not yet ended. + * @return Whether this AnimatorSet has been started and has not yet ended. + */ + @Override + public boolean isRunning() { + for (Node node : mNodes) { + if (node.animation.isRunning()) { + return true; + } + } + return false; + } + + @Override + public boolean isStarted() { + return mStarted; + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + * + * @return the number of milliseconds to delay running the animation + */ + @Override + public long getStartDelay() { + return mStartDelay; + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + + * @param startDelay The amount of the delay, in milliseconds + */ + @Override + public void setStartDelay(long startDelay) { + mStartDelay = startDelay; + } + + /** + * Gets the length of each of the child animations of this AnimatorSet. This value may + * be less than 0, which indicates that no duration has been set on this AnimatorSet + * and each of the child animations will use their own duration. + * + * @return The length of the animation, in milliseconds, of each of the child + * animations of this AnimatorSet. + */ + @Override + public long getDuration() { + return mDuration; + } + + /** + * Sets the length of each of the current child animations of this AnimatorSet. By default, + * each child animation will use its own duration. If the duration is set on the AnimatorSet, + * then each child animation inherits this duration. + * + * @param duration The length of the animation, in milliseconds, of each of the child + * animations of this AnimatorSet. + */ + @Override + public AnimatorSet setDuration(long duration) { + if (duration < 0) { + throw new IllegalArgumentException("duration must be a value of zero or greater"); + } + for (Node node : mNodes) { + // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to + // insert "play-after" delays + node.animation.setDuration(duration); + } + mDuration = duration; + return this; + } + + @Override + public void setupStartValues() { + for (Node node : mNodes) { + node.animation.setupStartValues(); + } + } + + @Override + public void setupEndValues() { + for (Node node : mNodes) { + node.animation.setupEndValues(); + } + } + + /** + * {@inheritDoc} + * + * <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which + * it is responsible. The details of when exactly those animations are started depends on + * the dependency relationships that have been set up between the animations. + */ + @SuppressWarnings("unchecked") + @Override + public void start() { + mTerminated = false; + mStarted = true; + + // First, sort the nodes (if necessary). This will ensure that sortedNodes + // contains the animation nodes in the correct order. + sortNodes(); + + int numSortedNodes = mSortedNodes.size(); + for (int i = 0; i < numSortedNodes; ++i) { + Node node = mSortedNodes.get(i); + // First, clear out the old listeners + ArrayList<AnimatorListener> oldListeners = node.animation.getListeners(); + if (oldListeners != null && oldListeners.size() > 0) { + final ArrayList<AnimatorListener> clonedListeners = new + ArrayList<AnimatorListener>(oldListeners); + + for (AnimatorListener listener : clonedListeners) { + if (listener instanceof DependencyListener || + listener instanceof AnimatorSetListener) { + node.animation.removeListener(listener); + } + } + } + } + + // nodesToStart holds the list of nodes to be started immediately. We don't want to + // start the animations in the loop directly because we first need to set up + // dependencies on all of the nodes. For example, we don't want to start an animation + // when some other animation also wants to start when the first animation begins. + final ArrayList<Node> nodesToStart = new ArrayList<Node>(); + for (int i = 0; i < numSortedNodes; ++i) { + Node node = mSortedNodes.get(i); + if (mSetListener == null) { + mSetListener = new AnimatorSetListener(this); + } + if (node.dependencies == null || node.dependencies.size() == 0) { + nodesToStart.add(node); + } else { + int numDependencies = node.dependencies.size(); + for (int j = 0; j < numDependencies; ++j) { + Dependency dependency = node.dependencies.get(j); + dependency.node.animation.addListener( + new DependencyListener(this, node, dependency.rule)); + } + node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone(); + } + node.animation.addListener(mSetListener); + } + // Now that all dependencies are set up, start the animations that should be started. + if (mStartDelay <= 0) { + for (Node node : nodesToStart) { + node.animation.start(); + mPlayingSet.add(node.animation); + } + } else { + mDelayAnim = ValueAnimator.ofFloat(0f, 1f); + mDelayAnim.setDuration(mStartDelay); + mDelayAnim.addListener(new AnimatorListenerAdapter() { + boolean canceled = false; + public void onAnimationCancel(Animator anim) { + canceled = true; + } + public void onAnimationEnd(Animator anim) { + if (!canceled) { + int numNodes = nodesToStart.size(); + for (int i = 0; i < numNodes; ++i) { + Node node = nodesToStart.get(i); + node.animation.start(); + mPlayingSet.add(node.animation); + } + } + } + }); + mDelayAnim.start(); + } + if (mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationStart(this); + } + } + if (mNodes.size() == 0 && mStartDelay == 0) { + // Handle unusual case where empty AnimatorSet is started - should send out + // end event immediately since the event will not be sent out at all otherwise + mStarted = false; + if (mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationEnd(this); + } + } + } + } + + @Override + public AnimatorSet clone() { + final AnimatorSet anim = (AnimatorSet) super.clone(); + /* + * The basic clone() operation copies all items. This doesn't work very well for + * AnimatorSet, because it will copy references that need to be recreated and state + * that may not apply. What we need to do now is put the clone in an uninitialized + * state, with fresh, empty data structures. Then we will build up the nodes list + * manually, as we clone each Node (and its animation). The clone will then be sorted, + * and will populate any appropriate lists, when it is started. + */ + anim.mNeedsSort = true; + anim.mTerminated = false; + anim.mStarted = false; + anim.mPlayingSet = new ArrayList<Animator>(); + anim.mNodeMap = new HashMap<Animator, Node>(); + anim.mNodes = new ArrayList<Node>(); + anim.mSortedNodes = new ArrayList<Node>(); + + // Walk through the old nodes list, cloning each node and adding it to the new nodemap. + // One problem is that the old node dependencies point to nodes in the old AnimatorSet. + // We need to track the old/new nodes in order to reconstruct the dependencies in the clone. + HashMap<Node, Node> nodeCloneMap = new HashMap<Node, Node>(); // <old, new> + for (Node node : mNodes) { + Node nodeClone = node.clone(); + nodeCloneMap.put(node, nodeClone); + anim.mNodes.add(nodeClone); + anim.mNodeMap.put(nodeClone.animation, nodeClone); + // Clear out the dependencies in the clone; we'll set these up manually later + nodeClone.dependencies = null; + nodeClone.tmpDependencies = null; + nodeClone.nodeDependents = null; + nodeClone.nodeDependencies = null; + // clear out any listeners that were set up by the AnimatorSet; these will + // be set up when the clone's nodes are sorted + ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners(); + if (cloneListeners != null) { + ArrayList<AnimatorListener> listenersToRemove = null; + for (AnimatorListener listener : cloneListeners) { + if (listener instanceof AnimatorSetListener) { + if (listenersToRemove == null) { + listenersToRemove = new ArrayList<AnimatorListener>(); + } + listenersToRemove.add(listener); + } + } + if (listenersToRemove != null) { + for (AnimatorListener listener : listenersToRemove) { + cloneListeners.remove(listener); + } + } + } + } + // Now that we've cloned all of the nodes, we're ready to walk through their + // dependencies, mapping the old dependencies to the new nodes + for (Node node : mNodes) { + Node nodeClone = nodeCloneMap.get(node); + if (node.dependencies != null) { + for (Dependency dependency : node.dependencies) { + Node clonedDependencyNode = nodeCloneMap.get(dependency.node); + Dependency cloneDependency = new Dependency(clonedDependencyNode, + dependency.rule); + nodeClone.addDependency(cloneDependency); + } + } + } + + return anim; + } + + /** + * This class is the mechanism by which animations are started based on events in other + * animations. If an animation has multiple dependencies on other animations, then + * all dependencies must be satisfied before the animation is started. + */ + private static class DependencyListener implements AnimatorListener { + + private AnimatorSet mAnimatorSet; + + // The node upon which the dependency is based. + private Node mNode; + + // The Dependency rule (WITH or AFTER) that the listener should wait for on + // the node + private int mRule; + + public DependencyListener(AnimatorSet animatorSet, Node node, int rule) { + this.mAnimatorSet = animatorSet; + this.mNode = node; + this.mRule = rule; + } + + /** + * Ignore cancel events for now. We may want to handle this eventually, + * to prevent follow-on animations from running when some dependency + * animation is canceled. + */ + public void onAnimationCancel(Animator animation) { + } + + /** + * An end event is received - see if this is an event we are listening for + */ + public void onAnimationEnd(Animator animation) { + if (mRule == Dependency.AFTER) { + startIfReady(animation); + } + } + + /** + * Ignore repeat events for now + */ + public void onAnimationRepeat(Animator animation) { + } + + /** + * A start event is received - see if this is an event we are listening for + */ + public void onAnimationStart(Animator animation) { + if (mRule == Dependency.WITH) { + startIfReady(animation); + } + } + + /** + * Check whether the event received is one that the node was waiting for. + * If so, mark it as complete and see whether it's time to start + * the animation. + * @param dependencyAnimation the animation that sent the event. + */ + private void startIfReady(Animator dependencyAnimation) { + if (mAnimatorSet.mTerminated) { + // if the parent AnimatorSet was canceled, then don't start any dependent anims + return; + } + Dependency dependencyToRemove = null; + int numDependencies = mNode.tmpDependencies.size(); + for (int i = 0; i < numDependencies; ++i) { + Dependency dependency = mNode.tmpDependencies.get(i); + if (dependency.rule == mRule && + dependency.node.animation == dependencyAnimation) { + // rule fired - remove the dependency and listener and check to + // see whether it's time to start the animation + dependencyToRemove = dependency; + dependencyAnimation.removeListener(this); + break; + } + } + mNode.tmpDependencies.remove(dependencyToRemove); + if (mNode.tmpDependencies.size() == 0) { + // all dependencies satisfied: start the animation + mNode.animation.start(); + mAnimatorSet.mPlayingSet.add(mNode.animation); + } + } + + } + + private class AnimatorSetListener implements AnimatorListener { + + private AnimatorSet mAnimatorSet; + + AnimatorSetListener(AnimatorSet animatorSet) { + mAnimatorSet = animatorSet; + } + + public void onAnimationCancel(Animator animation) { + if (!mTerminated) { + // Listeners are already notified of the AnimatorSet canceling in cancel(). + // The logic below only kicks in when animations end normally + if (mPlayingSet.size() == 0) { + if (mListeners != null) { + int numListeners = mListeners.size(); + for (int i = 0; i < numListeners; ++i) { + mListeners.get(i).onAnimationCancel(mAnimatorSet); + } + } + } + } + } + + @SuppressWarnings("unchecked") + public void onAnimationEnd(Animator animation) { + animation.removeListener(this); + mPlayingSet.remove(animation); + Node animNode = mAnimatorSet.mNodeMap.get(animation); + animNode.done = true; + if (!mTerminated) { + // Listeners are already notified of the AnimatorSet ending in cancel() or + // end(); the logic below only kicks in when animations end normally + ArrayList<Node> sortedNodes = mAnimatorSet.mSortedNodes; + boolean allDone = true; + int numSortedNodes = sortedNodes.size(); + for (int i = 0; i < numSortedNodes; ++i) { + if (!sortedNodes.get(i).done) { + allDone = false; + break; + } + } + if (allDone) { + // If this was the last child animation to end, then notify listeners that this + // AnimatorSet has ended + if (mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationEnd(mAnimatorSet); + } + } + mAnimatorSet.mStarted = false; + } + } + } + + // Nothing to do + public void onAnimationRepeat(Animator animation) { + } + + // Nothing to do + public void onAnimationStart(Animator animation) { + } + + } + + /** + * This method sorts the current set of nodes, if needed. The sort is a simple + * DependencyGraph sort, which goes like this: + * - All nodes without dependencies become 'roots' + * - while roots list is not null + * - for each root r + * - add r to sorted list + * - remove r as a dependency from any other node + * - any nodes with no dependencies are added to the roots list + */ + private void sortNodes() { + if (mNeedsSort) { + mSortedNodes.clear(); + ArrayList<Node> roots = new ArrayList<Node>(); + int numNodes = mNodes.size(); + for (int i = 0; i < numNodes; ++i) { + Node node = mNodes.get(i); + if (node.dependencies == null || node.dependencies.size() == 0) { + roots.add(node); + } + } + ArrayList<Node> tmpRoots = new ArrayList<Node>(); + while (roots.size() > 0) { + int numRoots = roots.size(); + for (int i = 0; i < numRoots; ++i) { + Node root = roots.get(i); + mSortedNodes.add(root); + if (root.nodeDependents != null) { + int numDependents = root.nodeDependents.size(); + for (int j = 0; j < numDependents; ++j) { + Node node = root.nodeDependents.get(j); + node.nodeDependencies.remove(root); + if (node.nodeDependencies.size() == 0) { + tmpRoots.add(node); + } + } + } + } + roots.clear(); + roots.addAll(tmpRoots); + tmpRoots.clear(); + } + mNeedsSort = false; + if (mSortedNodes.size() != mNodes.size()) { + throw new IllegalStateException("Circular dependencies cannot exist" + + " in AnimatorSet"); + } + } else { + // Doesn't need sorting, but still need to add in the nodeDependencies list + // because these get removed as the event listeners fire and the dependencies + // are satisfied + int numNodes = mNodes.size(); + for (int i = 0; i < numNodes; ++i) { + Node node = mNodes.get(i); + if (node.dependencies != null && node.dependencies.size() > 0) { + int numDependencies = node.dependencies.size(); + for (int j = 0; j < numDependencies; ++j) { + Dependency dependency = node.dependencies.get(j); + if (node.nodeDependencies == null) { + node.nodeDependencies = new ArrayList<Node>(); + } + if (!node.nodeDependencies.contains(dependency.node)) { + node.nodeDependencies.add(dependency.node); + } + } + } + // nodes are 'done' by default; they become un-done when started, and done + // again when ended + node.done = false; + } + } + } + + /** + * Dependency holds information about the node that some other node is + * dependent upon and the nature of that dependency. + * + */ + private static class Dependency { + static final int WITH = 0; // dependent node must start with this dependency node + static final int AFTER = 1; // dependent node must start when this dependency node finishes + + // The node that the other node with this Dependency is dependent upon + public Node node; + + // The nature of the dependency (WITH or AFTER) + public int rule; + + public Dependency(Node node, int rule) { + this.node = node; + this.rule = rule; + } + } + + /** + * A Node is an embodiment of both the Animator that it wraps as well as + * any dependencies that are associated with that Animation. This includes + * both dependencies upon other nodes (in the dependencies list) as + * well as dependencies of other nodes upon this (in the nodeDependents list). + */ + private static class Node implements Cloneable { + public Animator animation; + + /** + * These are the dependencies that this node's animation has on other + * nodes. For example, if this node's animation should begin with some + * other animation ends, then there will be an item in this node's + * dependencies list for that other animation's node. + */ + public ArrayList<Dependency> dependencies = null; + + /** + * tmpDependencies is a runtime detail. We use the dependencies list for sorting. + * But we also use the list to keep track of when multiple dependencies are satisfied, + * but removing each dependency as it is satisfied. We do not want to remove + * the dependency itself from the list, because we need to retain that information + * if the AnimatorSet is launched in the future. So we create a copy of the dependency + * list when the AnimatorSet starts and use this tmpDependencies list to track the + * list of satisfied dependencies. + */ + public ArrayList<Dependency> tmpDependencies = null; + + /** + * nodeDependencies is just a list of the nodes that this Node is dependent upon. + * This information is used in sortNodes(), to determine when a node is a root. + */ + public ArrayList<Node> nodeDependencies = null; + + /** + * nodeDepdendents is the list of nodes that have this node as a dependency. This + * is a utility field used in sortNodes to facilitate removing this node as a + * dependency when it is a root node. + */ + public ArrayList<Node> nodeDependents = null; + + /** + * Flag indicating whether the animation in this node is finished. This flag + * is used by AnimatorSet to check, as each animation ends, whether all child animations + * are done and it's time to send out an end event for the entire AnimatorSet. + */ + public boolean done = false; + + /** + * Constructs the Node with the animation that it encapsulates. A Node has no + * dependencies by default; dependencies are added via the addDependency() + * method. + * + * @param animation The animation that the Node encapsulates. + */ + public Node(Animator animation) { + this.animation = animation; + } + + /** + * Add a dependency to this Node. The dependency includes information about the + * node that this node is dependency upon and the nature of the dependency. + * @param dependency + */ + public void addDependency(Dependency dependency) { + if (dependencies == null) { + dependencies = new ArrayList<Dependency>(); + nodeDependencies = new ArrayList<Node>(); + } + dependencies.add(dependency); + if (!nodeDependencies.contains(dependency.node)) { + nodeDependencies.add(dependency.node); + } + Node dependencyNode = dependency.node; + if (dependencyNode.nodeDependents == null) { + dependencyNode.nodeDependents = new ArrayList<Node>(); + } + dependencyNode.nodeDependents.add(this); + } + + @Override + public Node clone() { + try { + Node node = (Node) super.clone(); + node.animation = (Animator) animation.clone(); + return node; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + } + + /** + * The <code>Builder</code> object is a utility class to facilitate adding animations to a + * <code>AnimatorSet</code> along with the relationships between the various animations. The + * intention of the <code>Builder</code> methods, along with the {@link + * AnimatorSet#play(Animator) play()} method of <code>AnimatorSet</code> is to make it possible + * to express the dependency relationships of animations in a natural way. Developers can also + * use the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link + * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need, + * but it might be easier in some situations to express the AnimatorSet of animations in pairs. + * <p/> + * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed + * internally via a call to {@link AnimatorSet#play(Animator)}.</p> + * <p/> + * <p>For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to + * play when anim2 finishes, and anim4 to play when anim3 finishes:</p> + * <pre> + * AnimatorSet s = new AnimatorSet(); + * s.play(anim1).with(anim2); + * s.play(anim2).before(anim3); + * s.play(anim4).after(anim3); + * </pre> + * <p/> + * <p>Note in the example that both {@link Builder#before(Animator)} and {@link + * Builder#after(Animator)} are used. These are just different ways of expressing the same + * relationship and are provided to make it easier to say things in a way that is more natural, + * depending on the situation.</p> + * <p/> + * <p>It is possible to make several calls into the same <code>Builder</code> object to express + * multiple relationships. However, note that it is only the animation passed into the initial + * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive + * calls to the <code>Builder</code> object. For example, the following code starts both anim2 + * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and + * anim3: + * <pre> + * AnimatorSet s = new AnimatorSet(); + * s.play(anim1).before(anim2).before(anim3); + * </pre> + * If the desired result is to play anim1 then anim2 then anim3, this code expresses the + * relationship correctly:</p> + * <pre> + * AnimatorSet s = new AnimatorSet(); + * s.play(anim1).before(anim2); + * s.play(anim2).before(anim3); + * </pre> + * <p/> + * <p>Note that it is possible to express relationships that cannot be resolved and will not + * result in sensible results. For example, <code>play(anim1).after(anim1)</code> makes no + * sense. In general, circular dependencies like this one (or more indirect ones where a depends + * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets + * that can boil down to a simple, one-way relationship of animations starting with, before, and + * after other, different, animations.</p> + */ + public class Builder { + + /** + * This tracks the current node being processed. It is supplied to the play() method + * of AnimatorSet and passed into the constructor of Builder. + */ + private Node mCurrentNode; + + /** + * package-private constructor. Builders are only constructed by AnimatorSet, when the + * play() method is called. + * + * @param anim The animation that is the dependency for the other animations passed into + * the other methods of this Builder object. + */ + Builder(Animator anim) { + mCurrentNode = mNodeMap.get(anim); + if (mCurrentNode == null) { + mCurrentNode = new Node(anim); + mNodeMap.put(anim, mCurrentNode); + mNodes.add(mCurrentNode); + } + } + + /** + * Sets up the given animation to play at the same time as the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object. + * + * @param anim The animation that will play when the animation supplied to the + * {@link AnimatorSet#play(Animator)} method starts. + */ + public Builder with(Animator anim) { + Node node = mNodeMap.get(anim); + if (node == null) { + node = new Node(anim); + mNodeMap.put(anim, node); + mNodes.add(node); + } + Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH); + node.addDependency(dependency); + return this; + } + + /** + * Sets up the given animation to play when the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object + * ends. + * + * @param anim The animation that will play when the animation supplied to the + * {@link AnimatorSet#play(Animator)} method ends. + */ + public Builder before(Animator anim) { + Node node = mNodeMap.get(anim); + if (node == null) { + node = new Node(anim); + mNodeMap.put(anim, node); + mNodes.add(node); + } + Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER); + node.addDependency(dependency); + return this; + } + + /** + * Sets up the given animation to play when the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object + * to start when the animation supplied in this method call ends. + * + * @param anim The animation whose end will cause the animation supplied to the + * {@link AnimatorSet#play(Animator)} method to play. + */ + public Builder after(Animator anim) { + Node node = mNodeMap.get(anim); + if (node == null) { + node = new Node(anim); + mNodeMap.put(anim, node); + mNodes.add(node); + } + Dependency dependency = new Dependency(node, Dependency.AFTER); + mCurrentNode.addDependency(dependency); + return this; + } + + /** + * Sets up the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object + * to play when the given amount of time elapses. + * + * @param delay The number of milliseconds that should elapse before the + * animation starts. + */ + public Builder after(long delay) { + // setup dummy ValueAnimator just to run the clock + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(delay); + after(anim); + return this; + } + + } + +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/ArgbEvaluator.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +/** + * This evaluator can be used to perform type interpolation between integer + * values that represent ARGB colors. + */ +public class ArgbEvaluator implements TypeEvaluator { + + /** + * This function returns the calculated in-between value for a color + * given integers that represent the start and end values in the four + * bytes of the 32-bit int. Each channel is separately linearly interpolated + * and the resulting calculated values are recombined into the return value. + * + * @param fraction The fraction from the starting to the ending values + * @param startValue A 32-bit int value representing colors in the + * separate bytes of the parameter + * @param endValue A 32-bit int value representing colors in the + * separate bytes of the parameter + * @return A value that is calculated to be the linearly interpolated + * result, derived by separating the start and end values into separate + * color channels and interpolating each one separately, recombining the + * resulting values in the same way. + */ + public Object evaluate(float fraction, Object startValue, Object endValue) { + int startInt = (Integer) startValue; + int startA = (startInt >> 24); + int startR = (startInt >> 16) & 0xff; + int startG = (startInt >> 8) & 0xff; + int startB = startInt & 0xff; + + int endInt = (Integer) endValue; + int endA = (endInt >> 24); + int endR = (endInt >> 16) & 0xff; + int endG = (endInt >> 8) & 0xff; + int endB = endInt & 0xff; + + return (int)((startA + (int)(fraction * (endA - startA))) << 24) | + (int)((startR + (int)(fraction * (endR - startR))) << 16) | + (int)((startG + (int)(fraction * (endG - startG))) << 8) | + (int)((startB + (int)(fraction * (endB - startB)))); + } +} \ No newline at end of file
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/FloatEvaluator.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +/** + * This evaluator can be used to perform type interpolation between <code>float</code> values. + */ +public class FloatEvaluator implements TypeEvaluator<Number> { + + /** + * This function returns the result of linearly interpolating the start and end values, with + * <code>fraction</code> representing the proportion between the start and end values. The + * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>, + * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>, + * and <code>t</code> is <code>fraction</code>. + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start value; should be of type <code>float</code> or + * <code>Float</code> + * @param endValue The end value; should be of type <code>float</code> or <code>Float</code> + * @return A linear interpolation between the start and end values, given the + * <code>fraction</code> parameter. + */ + public Float evaluate(float fraction, Number startValue, Number endValue) { + float startFloat = startValue.floatValue(); + return startFloat + fraction * (endValue.floatValue() - startFloat); + } +} \ No newline at end of file
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/FloatKeyframeSet.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +import android.view.animation.Interpolator; + +import com.nineoldandroids.animation.Keyframe.FloatKeyframe; + +import java.util.ArrayList; + +/** + * This class holds a collection of FloatKeyframe objects and is called by ValueAnimator to calculate + * values between those keyframes for a given animation. The class internal to the animation + * package because it is an implementation detail of how Keyframes are stored and used. + * + * <p>This type-specific subclass of KeyframeSet, along with the other type-specific subclass for + * int, exists to speed up the getValue() method when there is no custom + * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the + * Object equivalents of these primitive types.</p> + */ +class FloatKeyframeSet extends KeyframeSet { + private float firstValue; + private float lastValue; + private float deltaValue; + private boolean firstTime = true; + + public FloatKeyframeSet(FloatKeyframe... keyframes) { + super(keyframes); + } + + @Override + public Object getValue(float fraction) { + return getFloatValue(fraction); + } + + @Override + public FloatKeyframeSet clone() { + ArrayList<Keyframe> keyframes = mKeyframes; + int numKeyframes = mKeyframes.size(); + FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone(); + } + FloatKeyframeSet newSet = new FloatKeyframeSet(newKeyframes); + return newSet; + } + + public float getFloatValue(float fraction) { + if (mNumKeyframes == 2) { + if (firstTime) { + firstTime = false; + firstValue = ((FloatKeyframe) mKeyframes.get(0)).getFloatValue(); + lastValue = ((FloatKeyframe) mKeyframes.get(1)).getFloatValue(); + deltaValue = lastValue - firstValue; + } + if (mInterpolator != null) { + fraction = mInterpolator.getInterpolation(fraction); + } + if (mEvaluator == null) { + return firstValue + fraction * deltaValue; + } else { + return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).floatValue(); + } + } + if (fraction <= 0f) { + final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0); + final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1); + float prevValue = prevKeyframe.getFloatValue(); + float nextValue = nextKeyframe.getFloatValue(); + float prevFraction = prevKeyframe.getFraction(); + float nextFraction = nextKeyframe.getFraction(); + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction); + return mEvaluator == null ? + prevValue + intervalFraction * (nextValue - prevValue) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). + floatValue(); + } else if (fraction >= 1f) { + final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2); + final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1); + float prevValue = prevKeyframe.getFloatValue(); + float nextValue = nextKeyframe.getFloatValue(); + float prevFraction = prevKeyframe.getFraction(); + float nextFraction = nextKeyframe.getFraction(); + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction); + return mEvaluator == null ? + prevValue + intervalFraction * (nextValue - prevValue) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). + floatValue(); + } + FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0); + for (int i = 1; i < mNumKeyframes; ++i) { + FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i); + if (fraction < nextKeyframe.getFraction()) { + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevKeyframe.getFraction()) / + (nextKeyframe.getFraction() - prevKeyframe.getFraction()); + float prevValue = prevKeyframe.getFloatValue(); + float nextValue = nextKeyframe.getFloatValue(); + return mEvaluator == null ? + prevValue + intervalFraction * (nextValue - prevValue) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). + floatValue(); + } + prevKeyframe = nextKeyframe; + } + // shouldn't get here + return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue(); + } + +} +
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/IntEvaluator.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +/** + * This evaluator can be used to perform type interpolation between <code>int</code> values. + */ +public class IntEvaluator implements TypeEvaluator<Integer> { + + /** + * This function returns the result of linearly interpolating the start and end values, with + * <code>fraction</code> representing the proportion between the start and end values. The + * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>, + * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>, + * and <code>t</code> is <code>fraction</code>. + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start value; should be of type <code>int</code> or + * <code>Integer</code> + * @param endValue The end value; should be of type <code>int</code> or <code>Integer</code> + * @return A linear interpolation between the start and end values, given the + * <code>fraction</code> parameter. + */ + public Integer evaluate(float fraction, Integer startValue, Integer endValue) { + int startInt = startValue; + return (int)(startInt + fraction * (endValue - startInt)); + } +} \ No newline at end of file
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/IntKeyframeSet.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +import android.view.animation.Interpolator; + +import com.nineoldandroids.animation.Keyframe.IntKeyframe; + +import java.util.ArrayList; + +/** + * This class holds a collection of IntKeyframe objects and is called by ValueAnimator to calculate + * values between those keyframes for a given animation. The class internal to the animation + * package because it is an implementation detail of how Keyframes are stored and used. + * + * <p>This type-specific subclass of KeyframeSet, along with the other type-specific subclass for + * float, exists to speed up the getValue() method when there is no custom + * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the + * Object equivalents of these primitive types.</p> + */ +class IntKeyframeSet extends KeyframeSet { + private int firstValue; + private int lastValue; + private int deltaValue; + private boolean firstTime = true; + + public IntKeyframeSet(IntKeyframe... keyframes) { + super(keyframes); + } + + @Override + public Object getValue(float fraction) { + return getIntValue(fraction); + } + + @Override + public IntKeyframeSet clone() { + ArrayList<Keyframe> keyframes = mKeyframes; + int numKeyframes = mKeyframes.size(); + IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + newKeyframes[i] = (IntKeyframe) keyframes.get(i).clone(); + } + IntKeyframeSet newSet = new IntKeyframeSet(newKeyframes); + return newSet; + } + + public int getIntValue(float fraction) { + if (mNumKeyframes == 2) { + if (firstTime) { + firstTime = false; + firstValue = ((IntKeyframe) mKeyframes.get(0)).getIntValue(); + lastValue = ((IntKeyframe) mKeyframes.get(1)).getIntValue(); + deltaValue = lastValue - firstValue; + } + if (mInterpolator != null) { + fraction = mInterpolator.getInterpolation(fraction); + } + if (mEvaluator == null) { + return firstValue + (int)(fraction * deltaValue); + } else { + return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue(); + } + } + if (fraction <= 0f) { + final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0); + final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1); + int prevValue = prevKeyframe.getIntValue(); + int nextValue = nextKeyframe.getIntValue(); + float prevFraction = prevKeyframe.getFraction(); + float nextFraction = nextKeyframe.getFraction(); + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction); + return mEvaluator == null ? + prevValue + (int)(intervalFraction * (nextValue - prevValue)) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). + intValue(); + } else if (fraction >= 1f) { + final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2); + final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1); + int prevValue = prevKeyframe.getIntValue(); + int nextValue = nextKeyframe.getIntValue(); + float prevFraction = prevKeyframe.getFraction(); + float nextFraction = nextKeyframe.getFraction(); + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction); + return mEvaluator == null ? + prevValue + (int)(intervalFraction * (nextValue - prevValue)) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue(); + } + IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0); + for (int i = 1; i < mNumKeyframes; ++i) { + IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i); + if (fraction < nextKeyframe.getFraction()) { + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevKeyframe.getFraction()) / + (nextKeyframe.getFraction() - prevKeyframe.getFraction()); + int prevValue = prevKeyframe.getIntValue(); + int nextValue = nextKeyframe.getIntValue(); + return mEvaluator == null ? + prevValue + (int)(intervalFraction * (nextValue - prevValue)) : + ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)). + intValue(); + } + prevKeyframe = nextKeyframe; + } + // shouldn't get here + return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue(); + } + +} +
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/Keyframe.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +import android.view.animation.Interpolator; + +/** + * This class holds a time/value pair for an animation. The Keyframe class is used + * by {@link ValueAnimator} to define the values that the animation target will have over the course + * of the animation. As the time proceeds from one keyframe to the other, the value of the + * target object will animate between the value at the previous keyframe and the value at the + * next keyframe. Each keyframe also holds an optional {@link TimeInterpolator} + * object, which defines the time interpolation over the intervalue preceding the keyframe. + * + * <p>The Keyframe class itself is abstract. The type-specific factory methods will return + * a subclass of Keyframe specific to the type of value being stored. This is done to improve + * performance when dealing with the most common cases (e.g., <code>float</code> and + * <code>int</code> values). Other types will fall into a more general Keyframe class that + * treats its values as Objects. Unless your animation requires dealing with a custom type + * or a data structure that needs to be animated directly (and evaluated using an implementation + * of {@link TypeEvaluator}), you should stick to using float and int as animations using those + * types have lower runtime overhead than other types.</p> + */ +public abstract class Keyframe implements Cloneable { + /** + * The time at which mValue will hold true. + */ + float mFraction; + + /** + * The type of the value in this Keyframe. This type is determined at construction time, + * based on the type of the <code>value</code> object passed into the constructor. + */ + Class mValueType; + + /** + * The optional time interpolator for the interval preceding this keyframe. A null interpolator + * (the default) results in linear interpolation over the interval. + */ + private /*Time*/Interpolator mInterpolator = null; + + /** + * Flag to indicate whether this keyframe has a valid value. This flag is used when an + * animation first starts, to populate placeholder keyframes with real values derived + * from the target object. + */ + boolean mHasValue = false; + + /** + * Constructs a Keyframe object with the given time and value. The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + */ + public static Keyframe ofInt(float fraction, int value) { + return new IntKeyframe(fraction, value); + } + + /** + * Constructs a Keyframe object with the given time. The value at this time will be derived + * from the target object when the animation first starts (note that this implies that keyframes + * with no initial value must be used as part of an {@link ObjectAnimator}). + * The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + */ + public static Keyframe ofInt(float fraction) { + return new IntKeyframe(fraction); + } + + /** + * Constructs a Keyframe object with the given time and value. The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + */ + public static Keyframe ofFloat(float fraction, float value) { + return new FloatKeyframe(fraction, value); + } + + /** + * Constructs a Keyframe object with the given time. The value at this time will be derived + * from the target object when the animation first starts (note that this implies that keyframes + * with no initial value must be used as part of an {@link ObjectAnimator}). + * The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + */ + public static Keyframe ofFloat(float fraction) { + return new FloatKeyframe(fraction); + } + + /** + * Constructs a Keyframe object with the given time and value. The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + */ + public static Keyframe ofObject(float fraction, Object value) { + return new ObjectKeyframe(fraction, value); + } + + /** + * Constructs a Keyframe object with the given time. The value at this time will be derived + * from the target object when the animation first starts (note that this implies that keyframes + * with no initial value must be used as part of an {@link ObjectAnimator}). + * The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + */ + public static Keyframe ofObject(float fraction) { + return new ObjectKeyframe(fraction, null); + } + + /** + * Indicates whether this keyframe has a valid value. This method is called internally when + * an {@link ObjectAnimator} first starts; keyframes without values are assigned values at + * that time by deriving the value for the property from the target object. + * + * @return boolean Whether this object has a value assigned. + */ + public boolean hasValue() { + return mHasValue; + } + + /** + * Gets the value for this Keyframe. + * + * @return The value for this Keyframe. + */ + public abstract Object getValue(); + + /** + * Sets the value for this Keyframe. + * + * @param value value for this Keyframe. + */ + public abstract void setValue(Object value); + + /** + * Gets the time for this keyframe, as a fraction of the overall animation duration. + * + * @return The time associated with this keyframe, as a fraction of the overall animation + * duration. This should be a value between 0 and 1. + */ + public float getFraction() { + return mFraction; + } + + /** + * Sets the time for this keyframe, as a fraction of the overall animation duration. + * + * @param fraction time associated with this keyframe, as a fraction of the overall animation + * duration. This should be a value between 0 and 1. + */ + public void setFraction(float fraction) { + mFraction = fraction; + } + + /** + * Gets the optional interpolator for this Keyframe. A value of <code>null</code> indicates + * that there is no interpolation, which is the same as linear interpolation. + * + * @return The optional interpolator for this Keyframe. + */ + public /*Time*/Interpolator getInterpolator() { + return mInterpolator; + } + + /** + * Sets the optional interpolator for this Keyframe. A value of <code>null</code> indicates + * that there is no interpolation, which is the same as linear interpolation. + * + * @return The optional interpolator for this Keyframe. + */ + public void setInterpolator(/*Time*/Interpolator interpolator) { + mInterpolator = interpolator; + } + + /** + * Gets the type of keyframe. This information is used by ValueAnimator to determine the type of + * {@link TypeEvaluator} to use when calculating values between keyframes. The type is based + * on the type of Keyframe created. + * + * @return The type of the value stored in the Keyframe. + */ + public Class getType() { + return mValueType; + } + + @Override + public abstract Keyframe clone(); + + /** + * This internal subclass is used for all types which are not int or float. + */ + static class ObjectKeyframe extends Keyframe { + + /** + * The value of the animation at the time mFraction. + */ + Object mValue; + + ObjectKeyframe(float fraction, Object value) { + mFraction = fraction; + mValue = value; + mHasValue = (value != null); + mValueType = mHasValue ? value.getClass() : Object.class; + } + + public Object getValue() { + return mValue; + } + + public void setValue(Object value) { + mValue = value; + mHasValue = (value != null); + } + + @Override + public ObjectKeyframe clone() { + ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), mValue); + kfClone.setInterpolator(getInterpolator()); + return kfClone; + } + } + + /** + * Internal subclass used when the keyframe value is of type int. + */ + static class IntKeyframe extends Keyframe { + + /** + * The value of the animation at the time mFraction. + */ + int mValue; + + IntKeyframe(float fraction, int value) { + mFraction = fraction; + mValue = value; + mValueType = int.class; + mHasValue = true; + } + + IntKeyframe(float fraction) { + mFraction = fraction; + mValueType = int.class; + } + + public int getIntValue() { + return mValue; + } + + public Object getValue() { + return mValue; + } + + public void setValue(Object value) { + if (value != null && value.getClass() == Integer.class) { + mValue = ((Integer)value).intValue(); + mHasValue = true; + } + } + + @Override + public IntKeyframe clone() { + IntKeyframe kfClone = new IntKeyframe(getFraction(), mValue); + kfClone.setInterpolator(getInterpolator()); + return kfClone; + } + } + + /** + * Internal subclass used when the keyframe value is of type float. + */ + static class FloatKeyframe extends Keyframe { + /** + * The value of the animation at the time mFraction. + */ + float mValue; + + FloatKeyframe(float fraction, float value) { + mFraction = fraction; + mValue = value; + mValueType = float.class; + mHasValue = true; + } + + FloatKeyframe(float fraction) { + mFraction = fraction; + mValueType = float.class; + } + + public float getFloatValue() { + return mValue; + } + + public Object getValue() { + return mValue; + } + + public void setValue(Object value) { + if (value != null && value.getClass() == Float.class) { + mValue = ((Float)value).floatValue(); + mHasValue = true; + } + } + + @Override + public FloatKeyframe clone() { + FloatKeyframe kfClone = new FloatKeyframe(getFraction(), mValue); + kfClone.setInterpolator(getInterpolator()); + return kfClone; + } + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/KeyframeSet.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +import java.util.ArrayList; +import java.util.Arrays; + +import android.view.animation.Interpolator; + +import com.nineoldandroids.animation.Keyframe.FloatKeyframe; +import com.nineoldandroids.animation.Keyframe.IntKeyframe; +import com.nineoldandroids.animation.Keyframe.ObjectKeyframe; + +/** + * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate + * values between those keyframes for a given animation. The class internal to the animation + * package because it is an implementation detail of how Keyframes are stored and used. + */ +class KeyframeSet { + + int mNumKeyframes; + + Keyframe mFirstKeyframe; + Keyframe mLastKeyframe; + /*Time*/Interpolator mInterpolator; // only used in the 2-keyframe case + ArrayList<Keyframe> mKeyframes; // only used when there are not 2 keyframes + TypeEvaluator mEvaluator; + + + public KeyframeSet(Keyframe... keyframes) { + mNumKeyframes = keyframes.length; + mKeyframes = new ArrayList<Keyframe>(); + mKeyframes.addAll(Arrays.asList(keyframes)); + mFirstKeyframe = mKeyframes.get(0); + mLastKeyframe = mKeyframes.get(mNumKeyframes - 1); + mInterpolator = mLastKeyframe.getInterpolator(); + } + + public static KeyframeSet ofInt(int... values) { + int numKeyframes = values.length; + IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)]; + if (numKeyframes == 1) { + keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f); + keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]); + } else { + keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]); + for (int i = 1; i < numKeyframes; ++i) { + keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]); + } + } + return new IntKeyframeSet(keyframes); + } + + public static KeyframeSet ofFloat(float... values) { + 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]); + } 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]); + } + } + return new FloatKeyframeSet(keyframes); + } + + public static KeyframeSet ofKeyframe(Keyframe... keyframes) { + // if all keyframes of same primitive type, create the appropriate KeyframeSet + int numKeyframes = keyframes.length; + boolean hasFloat = false; + boolean hasInt = false; + boolean hasOther = false; + for (int i = 0; i < numKeyframes; ++i) { + if (keyframes[i] instanceof FloatKeyframe) { + hasFloat = true; + } else if (keyframes[i] instanceof IntKeyframe) { + hasInt = true; + } else { + hasOther = true; + } + } + if (hasFloat && !hasInt && !hasOther) { + FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + floatKeyframes[i] = (FloatKeyframe) keyframes[i]; + } + return new FloatKeyframeSet(floatKeyframes); + } else if (hasInt && !hasFloat && !hasOther) { + IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + intKeyframes[i] = (IntKeyframe) keyframes[i]; + } + return new IntKeyframeSet(intKeyframes); + } else { + return new KeyframeSet(keyframes); + } + } + + public static KeyframeSet ofObject(Object... values) { + int numKeyframes = values.length; + ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)]; + if (numKeyframes == 1) { + keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f); + keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]); + } else { + keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]); + for (int i = 1; i < numKeyframes; ++i) { + keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]); + } + } + return new KeyframeSet(keyframes); + } + + /** + * Sets the TypeEvaluator to be used when calculating animated values. This object + * is required only for KeyframeSets that are not either IntKeyframeSet or FloatKeyframeSet, + * both of which assume their own evaluator to speed up calculations with those primitive + * types. + * + * @param evaluator The TypeEvaluator to be used to calculate animated values. + */ + public void setEvaluator(TypeEvaluator evaluator) { + mEvaluator = evaluator; + } + + @Override + public KeyframeSet clone() { + ArrayList<Keyframe> keyframes = mKeyframes; + int numKeyframes = mKeyframes.size(); + Keyframe[] newKeyframes = new Keyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + newKeyframes[i] = keyframes.get(i).clone(); + } + KeyframeSet newSet = new KeyframeSet(newKeyframes); + return newSet; + } + + /** + * Gets the animated value, given the elapsed fraction of the animation (interpolated by the + * animation's interpolator) and the evaluator used to calculate in-between values. This + * function maps the input fraction to the appropriate keyframe interval and a fraction + * between them and returns the interpolated value. Note that the input fraction may fall + * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a + * spring interpolation that might send the fraction past 1.0). We handle this situation by + * just using the two keyframes at the appropriate end when the value is outside those bounds. + * + * @param fraction The elapsed fraction of the animation + * @return The animated value. + */ + public Object getValue(float fraction) { + + // Special-case optimization for the common case of only two keyframes + if (mNumKeyframes == 2) { + if (mInterpolator != null) { + fraction = mInterpolator.getInterpolation(fraction); + } + return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(), + mLastKeyframe.getValue()); + } + if (fraction <= 0f) { + final Keyframe nextKeyframe = mKeyframes.get(1); + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + final float prevFraction = mFirstKeyframe.getFraction(); + float intervalFraction = (fraction - prevFraction) / + (nextKeyframe.getFraction() - prevFraction); + return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(), + nextKeyframe.getValue()); + } else if (fraction >= 1f) { + final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2); + final /*Time*/Interpolator interpolator = mLastKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + final float prevFraction = prevKeyframe.getFraction(); + float intervalFraction = (fraction - prevFraction) / + (mLastKeyframe.getFraction() - prevFraction); + return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), + mLastKeyframe.getValue()); + } + Keyframe prevKeyframe = mFirstKeyframe; + for (int i = 1; i < mNumKeyframes; ++i) { + Keyframe nextKeyframe = mKeyframes.get(i); + if (fraction < nextKeyframe.getFraction()) { + final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + final float prevFraction = prevKeyframe.getFraction(); + float intervalFraction = (fraction - prevFraction) / + (nextKeyframe.getFraction() - prevFraction); + return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), + nextKeyframe.getValue()); + } + prevKeyframe = nextKeyframe; + } + // shouldn't reach here + return mLastKeyframe.getValue(); + } + + @Override + public String toString() { + String returnVal = " "; + for (int i = 0; i < mNumKeyframes; ++i) { + returnVal += mKeyframes.get(i).getValue() + " "; + } + return returnVal; + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/ObjectAnimator.java @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import android.util.Log; +import android.view.View; + +import com.nineoldandroids.util.Property; +import com.nineoldandroids.view.animation.AnimatorProxy; + +/** + * This subclass of {@link ValueAnimator} provides support for animating properties on target objects. + * The constructors of this class take parameters to define the target object that will be animated + * as well as the name of the property that will be animated. Appropriate set/get functions + * are then determined internally and the animation will call these functions as necessary to + * animate the property. + * + * @see #setPropertyName(String) + * + */ +public final class ObjectAnimator extends ValueAnimator { + private static final boolean DBG = false; + private static final Map<String, Property> PROXY_PROPERTIES = new HashMap<String, Property>(); + + static { + PROXY_PROPERTIES.put("alpha", PreHoneycombCompat.ALPHA); + PROXY_PROPERTIES.put("pivotX", PreHoneycombCompat.PIVOT_X); + PROXY_PROPERTIES.put("pivotY", PreHoneycombCompat.PIVOT_Y); + PROXY_PROPERTIES.put("translationX", PreHoneycombCompat.TRANSLATION_X); + PROXY_PROPERTIES.put("translationY", PreHoneycombCompat.TRANSLATION_Y); + PROXY_PROPERTIES.put("rotation", PreHoneycombCompat.ROTATION); + PROXY_PROPERTIES.put("rotationX", PreHoneycombCompat.ROTATION_X); + PROXY_PROPERTIES.put("rotationY", PreHoneycombCompat.ROTATION_Y); + PROXY_PROPERTIES.put("scaleX", PreHoneycombCompat.SCALE_X); + PROXY_PROPERTIES.put("scaleY", PreHoneycombCompat.SCALE_Y); + PROXY_PROPERTIES.put("scrollX", PreHoneycombCompat.SCROLL_X); + PROXY_PROPERTIES.put("scrollY", PreHoneycombCompat.SCROLL_Y); + PROXY_PROPERTIES.put("x", PreHoneycombCompat.X); + PROXY_PROPERTIES.put("y", PreHoneycombCompat.Y); + } + + // The target object on which the property exists, set in the constructor + private Object mTarget; + + private String mPropertyName; + + private Property mProperty; + + /** + * Sets the name of the property that will be animated. This name is used to derive + * a setter function that will be called to set animated values. + * For example, a property name of <code>foo</code> will result + * in a call to the function <code>setFoo()</code> on the target object. If either + * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will + * also be derived and called. + * + * <p>For best performance of the mechanism that calls the setter function determined by the + * name of the property being animated, use <code>float</code> or <code>int</code> typed values, + * and make the setter function for those properties have a <code>void</code> return value. This + * will cause the code to take an optimized path for these constrained circumstances. Other + * property types and return types will work, but will have more overhead in processing + * the requests due to normal reflection mechanisms.</p> + * + * <p>Note that the setter function derived from this property name + * must take the same parameter type as the + * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to + * the setter function will fail.</p> + * + * <p>If this ObjectAnimator has been set up to animate several properties together, + * using more than one PropertyValuesHolder objects, then setting the propertyName simply + * sets the propertyName in the first of those PropertyValuesHolder objects.</p> + * + * @param propertyName The name of the property being animated. Should not be null. + */ + public void setPropertyName(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 starting + mInitialized = false; + } + + /** + * Sets the property that will be animated. Property objects will take precedence over + * properties specified by the {@link #setPropertyName(String)} method. Animations should + * be set up to use one or the other, not both. + * + * @param property The property being animated. Should not be null. + */ + public void setProperty(Property property) { + // 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.setProperty(property); + mValuesMap.remove(oldName); + mValuesMap.put(mPropertyName, valuesHolder); + } + if (mProperty != null) { + mPropertyName = property.getName(); + } + mProperty = property; + // New property/values/target should cause re-initialization prior to starting + mInitialized = false; + } + + /** + * Gets the name of the property that will be animated. This name will be used to derive + * a setter function that will be called to set animated values. + * For example, a property name of <code>foo</code> will result + * in a call to the function <code>setFoo()</code> on the target object. If either + * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will + * also be derived and called. + */ + public String getPropertyName() { + return mPropertyName; + } + + /** + * Creates a new ObjectAnimator object. This default constructor is primarily for + * use internally; the other constructors which take parameters are more generally + * useful. + */ + public ObjectAnimator() { + } + + /** + * Private utility constructor that initializes the target object and name of the + * property being animated. + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. + * @param propertyName The name of the property being animated. + */ + private ObjectAnimator(Object target, String propertyName) { + mTarget = target; + setPropertyName(propertyName); + } + + /** + * Private utility constructor that initializes the target object and property being animated. + * + * @param target The object whose property is to be animated. + * @param property The property being animated. + */ + private <T> ObjectAnimator(T target, Property<T, ?> property) { + mTarget = target; + setProperty(property); + } + + /** + * Constructs and returns an ObjectAnimator that animates between int values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. + * @param propertyName The name of the property being animated. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofInt(Object target, String propertyName, int... values) { + ObjectAnimator anim = new ObjectAnimator(target, propertyName); + anim.setIntValues(values); + return anim; + } + + /** + * Constructs and returns an ObjectAnimator that animates between int values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. + * @param property The property being animated. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> property, int... values) { + ObjectAnimator anim = new ObjectAnimator(target, property); + anim.setIntValues(values); + return anim; + } + + /** + * Constructs and returns an ObjectAnimator that animates between float values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. + * @param propertyName The name of the property being animated. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) { + ObjectAnimator anim = new ObjectAnimator(target, propertyName); + anim.setFloatValues(values); + return anim; + } + + /** + * Constructs and returns an ObjectAnimator that animates between float values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. + * @param property The property being animated. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> property, + float... values) { + ObjectAnimator anim = new ObjectAnimator(target, property); + anim.setFloatValues(values); + return anim; + } + + /** + * Constructs and returns an ObjectAnimator that animates between Object values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. + * @param propertyName The name of the property being animated. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofObject(Object target, String propertyName, + TypeEvaluator evaluator, Object... values) { + ObjectAnimator anim = new ObjectAnimator(target, propertyName); + anim.setObjectValues(values); + anim.setEvaluator(evaluator); + return anim; + } + + /** + * Constructs and returns an ObjectAnimator that animates between Object values. A single + * value implies that that value is the one being animated to. Two values imply a starting + * and ending values. More than two values imply a starting value, values to animate through + * along the way, and an ending value (these values will be distributed evenly across + * the duration of the animation). + * + * @param target The object whose property is to be animated. + * @param property The property being animated. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values A set of values that the animation will animate between over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static <T, V> ObjectAnimator ofObject(T target, Property<T, V> property, + TypeEvaluator<V> evaluator, V... values) { + ObjectAnimator anim = new ObjectAnimator(target, property); + anim.setObjectValues(values); + anim.setEvaluator(evaluator); + return anim; + } + + /** + * Constructs and returns an ObjectAnimator that animates between the sets of values specified + * in <code>PropertyValueHolder</code> objects. This variant should be used when animating + * several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows + * you to associate a set of animation values with a property name. + * + * @param target The object whose property is to be animated. Depending on how the + * PropertyValuesObjects were constructed, the target object should either have the {@link + * android.util.Property} objects used to construct the PropertyValuesHolder objects or (if the + * PropertyValuesHOlder objects were created with property names) the target object should have + * public methods on it called <code>setName()</code>, where <code>name</code> is the name of + * the property passed in as the <code>propertyName</code> parameter for each of the + * PropertyValuesHolder objects. + * @param values A set of PropertyValuesHolder objects whose values will be animated between + * over time. + * @return An ObjectAnimator object that is set up to animate between the given values. + */ + public static ObjectAnimator ofPropertyValuesHolder(Object target, + PropertyValuesHolder... values) { + ObjectAnimator anim = new ObjectAnimator(); + anim.mTarget = target; + anim.setValues(values); + return anim; + } + + @Override + public void setIntValues(int... values) { + if (mValues == null || mValues.length == 0) { + // No values yet - this animator is being constructed piecemeal. Init the values with + // whatever the current propertyName is + if (mProperty != null) { + setValues(PropertyValuesHolder.ofInt(mProperty, values)); + } else { + setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); + } + } else { + super.setIntValues(values); + } + } + + @Override + public 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 is + if (mProperty != null) { + setValues(PropertyValuesHolder.ofFloat(mProperty, values)); + } else { + setValues(PropertyValuesHolder.ofFloat(mPropertyName, values)); + } + } else { + super.setFloatValues(values); + } + } + + @Override + public void setObjectValues(Object... values) { + if (mValues == null || mValues.length == 0) { + // No values yet - this animator is being constructed piecemeal. Init the values with + // whatever the current propertyName is + if (mProperty != null) { + setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator)null, values)); + } else { + setValues(PropertyValuesHolder.ofObject(mPropertyName, (TypeEvaluator)null, values)); + } + } else { + super.setObjectValues(values); + } + } + + @Override + public void start() { + if (DBG) { + Log.d("ObjectAnimator", "Anim target, duration: " + mTarget + ", " + getDuration()); + for (int i = 0; i < mValues.length; ++i) { + PropertyValuesHolder pvh = mValues[i]; + ArrayList<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes; + Log.d("ObjectAnimator", " Values[" + i + "]: " + + pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " + + keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue()); + } + } + super.start(); + } + + /** + * This function is called immediately before processing the first animation + * frame of an animation. If there is a nonzero <code>startDelay</code>, the + * function is called after that delay ends. + * It takes care of the final initialization steps for the + * animation. This includes setting mEvaluator, if the user has not yet + * set it up, and the setter/getter methods, if the user did not supply + * them. + * + * <p>Overriders of this method should call the superclass method to cause + * internal mechanisms to be set up correctly.</p> + */ + @Override + void 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. + if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) { + setProperty(PROXY_PROPERTIES.get(mPropertyName)); + } + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setupSetterAndGetter(mTarget); + } + super.initAnimation(); + } + } + + /** + * Sets the length of the animation. The default duration is 300 milliseconds. + * + * @param duration The length of the animation, in milliseconds. + * @return ObjectAnimator The object called with setDuration(). This return + * value makes it easier to compose statements together that construct and then set the + * duration, as in + * <code>ObjectAnimator.ofInt(target, propertyName, 0, 10).setDuration(500).start()</code>. + */ + @Override + public ObjectAnimator setDuration(long duration) { + super.setDuration(duration); + return this; + } + + + /** + * The target object whose property will be animated by this animation + * + * @return The object being animated + */ + public Object getTarget() { + return mTarget; + } + + /** + * Sets the target object whose property will be animated by this animation + * + * @param target The object being animated + */ + @Override + public void setTarget(Object target) { + if (mTarget != target) { + final Object oldTarget = mTarget; + mTarget = target; + if (oldTarget != null && target != null && oldTarget.getClass() == target.getClass()) { + return; + } + // New target type should cause re-initialization prior to starting + mInitialized = false; + } + } + + @Override + public void setupStartValues() { + initAnimation(); + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setupStartValue(mTarget); + } + } + + @Override + public void setupEndValues() { + initAnimation(); + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setupEndValue(mTarget); + } + } + + /** + * This method is called with the elapsed fraction of the animation during every + * animation frame. This function turns the elapsed fraction into an interpolated fraction + * and then into an animated value (from the evaluator. The function is called mostly during + * animation updates, but it is also called when the <code>end()</code> + * function is called, to set the final value on the property. + * + * <p>Overrides of this method must call the superclass to perform the calculation + * of the animated value.</p> + * + * @param fraction The elapsed fraction of the animation. + */ + @Override + void animateValue(float fraction) { + super.animateValue(fraction); + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setAnimatedValue(mTarget); + } + } + + @Override + public ObjectAnimator clone() { + final ObjectAnimator anim = (ObjectAnimator) super.clone(); + return anim; + } + + @Override + public String toString() { + String returnVal = "ObjectAnimator@" + Integer.toHexString(hashCode()) + ", target " + + mTarget; + if (mValues != null) { + for (int i = 0; i < mValues.length; ++i) { + returnVal += "\n " + mValues[i].toString(); + } + } + return returnVal; + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/PreHoneycombCompat.java @@ -0,0 +1,168 @@ +package com.nineoldandroids.animation; + +import android.view.View; +import com.nineoldandroids.util.FloatProperty; +import com.nineoldandroids.util.IntProperty; +import com.nineoldandroids.util.Property; +import com.nineoldandroids.view.animation.AnimatorProxy; + +final class PreHoneycombCompat { + static Property<View, Float> ALPHA = new FloatProperty<View>("alpha") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setAlpha(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getAlpha(); + } + }; + static Property<View, Float> PIVOT_X = new FloatProperty<View>("pivotX") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setPivotX(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getPivotX(); + } + }; + static Property<View, Float> PIVOT_Y = new FloatProperty<View>("pivotY") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setPivotY(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getPivotY(); + } + }; + static Property<View, Float> TRANSLATION_X = new FloatProperty<View>("translationX") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setTranslationX(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getTranslationX(); + } + }; + static Property<View, Float> TRANSLATION_Y = new FloatProperty<View>("translationY") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setTranslationY(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getTranslationY(); + } + }; + static Property<View, Float> ROTATION = new FloatProperty<View>("rotation") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setRotation(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getRotation(); + } + }; + static Property<View, Float> ROTATION_X = new FloatProperty<View>("rotationX") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setRotationX(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getRotationX(); + } + }; + static Property<View, Float> ROTATION_Y = new FloatProperty<View>("rotationY") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setRotationY(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getRotationY(); + } + }; + static Property<View, Float> SCALE_X = new FloatProperty<View>("scaleX") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setScaleX(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getScaleX(); + } + }; + static Property<View, Float> SCALE_Y = new FloatProperty<View>("scaleY") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setScaleY(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getScaleY(); + } + }; + static Property<View, Integer> SCROLL_X = new IntProperty<View>("scrollX") { + @Override + public void setValue(View object, int value) { + AnimatorProxy.wrap(object).setScrollX(value); + } + + @Override + public Integer get(View object) { + return AnimatorProxy.wrap(object).getScrollX(); + } + }; + static Property<View, Integer> SCROLL_Y = new IntProperty<View>("scrollY") { + @Override + public void setValue(View object, int value) { + AnimatorProxy.wrap(object).setScrollY(value); + } + + @Override + public Integer get(View object) { + return AnimatorProxy.wrap(object).getScrollY(); + } + }; + static Property<View, Float> X = new FloatProperty<View>("x") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setX(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getX(); + } + }; + static Property<View, Float> Y = new FloatProperty<View>("y") { + @Override + public void setValue(View object, float value) { + AnimatorProxy.wrap(object).setY(value); + } + + @Override + public Float get(View object) { + return AnimatorProxy.wrap(object).getY(); + } + }; + + + //No instances + private PreHoneycombCompat() {} +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/PropertyValuesHolder.java @@ -0,0 +1,1030 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +import android.util.Log; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import com.nineoldandroids.util.FloatProperty; +import com.nineoldandroids.util.IntProperty; +import com.nineoldandroids.util.Property; + +/** + * This class holds information about a property and the values that that property + * should take on during an animation. PropertyValuesHolder objects can be used to create + * animations with ValueAnimator or ObjectAnimator that operate on several different properties + * in parallel. + */ +public class PropertyValuesHolder implements Cloneable { + + /** + * The name of the property associated with the values. This need not be a real property, + * unless this object is being used with ObjectAnimator. But this is the name by which + * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator. + */ + String mPropertyName; + + /** + * @hide + */ + protected Property mProperty; + + /** + * The setter function, if needed. ObjectAnimator hands off this functionality to + * PropertyValuesHolder, since it holds all of the per-property information. This + * property is automatically + * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator. + */ + Method mSetter = null; + + /** + * The getter function, if needed. ObjectAnimator hands off this functionality to + * PropertyValuesHolder, since it holds all of the per-property information. This + * property is automatically + * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator. + * The getter is only derived and used if one of the values is null. + */ + private Method mGetter = null; + + /** + * The type of values supplied. This information is used both in deriving the setter/getter + * functions and in deriving the type of TypeEvaluator. + */ + Class mValueType; + + /** + * The set of keyframes (time/value pairs) that define this animation. + */ + KeyframeSet mKeyframeSet = null; + + + // type evaluators for the primitive types handled by this implementation + private static final TypeEvaluator sIntEvaluator = new IntEvaluator(); + private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator(); + + // We try several different types when searching for appropriate setter/getter functions. + // The caller may have supplied values in a type that does not match the setter/getter + // functions (such as the integers 0 and 1 to represent floating point values for alpha). + // Also, the use of generics in constructors means that we end up with the Object versions + // of primitive types (Float vs. float). But most likely, the setter/getter functions + // will take primitive types instead. + // So we supply an ordered array of other types to try before giving up. + private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class, + Double.class, Integer.class}; + private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class, + Float.class, Double.class}; + private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class, + Float.class, Integer.class}; + + // These maps hold all property entries for a particular class. This map + // is used to speed up property/setter/getter lookups for a given class/property + // combination. No need to use reflection on the combination more than once. + private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap = + new HashMap<Class, HashMap<String, Method>>(); + private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap = + new HashMap<Class, HashMap<String, Method>>(); + + // This lock is used to ensure that only one thread is accessing the property maps + // at a time. + final ReentrantReadWriteLock mPropertyMapLock = new ReentrantReadWriteLock(); + + // Used to pass single value to varargs parameter in setter invocation + final Object[] mTmpValueArray = new Object[1]; + + /** + * The type evaluator used to calculate the animated values. This evaluator is determined + * automatically based on the type of the start/end objects passed into the constructor, + * but the system only knows about the primitive types int and float. Any other + * type will need to set the evaluator to a custom evaluator for that type. + */ + private TypeEvaluator mEvaluator; + + /** + * The value most recently calculated by calculateValue(). This is set during + * that function and might be retrieved later either by ValueAnimator.animatedValue() or + * by the property-setting logic in ObjectAnimator.animatedValue(). + */ + private Object mAnimatedValue; + + /** + * Internal utility constructor, used by the factory methods to set the property name. + * @param propertyName The name of the property for this holder. + */ + private PropertyValuesHolder(String propertyName) { + mPropertyName = propertyName; + } + + /** + * Internal utility constructor, used by the factory methods to set the property. + * @param property The property for this holder. + */ + private PropertyValuesHolder(Property property) { + mProperty = property; + if (property != null) { + mPropertyName = property.getName(); + } + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property name and + * set of int values. + * @param propertyName The name of the property being animated. + * @param values The values that the named property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static PropertyValuesHolder ofInt(String propertyName, int... values) { + return new IntPropertyValuesHolder(propertyName, values); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property and + * set of int values. + * @param property The property being animated. Should not be null. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) { + return new IntPropertyValuesHolder(property, values); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property name and + * set of float values. + * @param propertyName The name of the property being animated. + * @param values The values that the named property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static PropertyValuesHolder ofFloat(String propertyName, float... values) { + return new FloatPropertyValuesHolder(propertyName, values); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property and + * set of float values. + * @param property The property being animated. Should not be null. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) { + return new FloatPropertyValuesHolder(property, values); + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property name and + * set of Object values. This variant also takes a TypeEvaluator because the system + * cannot automatically interpolate between objects of unknown type. + * + * @param propertyName The name of the property being animated. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values The values that the named property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator, + Object... values) { + PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); + pvh.setObjectValues(values); + pvh.setEvaluator(evaluator); + return pvh; + } + + /** + * Constructs and returns a PropertyValuesHolder with a given property and + * set of Object values. This variant also takes a TypeEvaluator because the system + * cannot automatically interpolate between objects of unknown type. + * + * @param property The property being animated. Should not be null. + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the necessary interpolation between the Object values to derive the animated + * value. + * @param values The values that the property will animate between. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + public static <V> PropertyValuesHolder ofObject(Property property, + TypeEvaluator<V> evaluator, V... values) { + PropertyValuesHolder pvh = new PropertyValuesHolder(property); + pvh.setObjectValues(values); + pvh.setEvaluator(evaluator); + return pvh; + } + + /** + * Constructs and returns a PropertyValuesHolder object with the specified property name and set + * of values. These values can be of any type, but the type should be consistent so that + * an appropriate {@link android.animation.TypeEvaluator} can be found that matches + * the common type. + * <p>If there is only one value, it is assumed to be the end value of an animation, + * and an initial value will be derived, if possible, by calling a getter function + * on the object. Also, if any value is null, the value will be filled in when the animation + * starts in the same way. This mechanism of automatically getting null values only works + * if the PropertyValuesHolder object is used in conjunction + * {@link ObjectAnimator}, and with a getter function + * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has + * no way of determining what the value should be. + * @param propertyName The name of the property associated with this set of values. This + * can be the actual property name to be used when using a ObjectAnimator object, or + * just a name used to get animated values, such as if this object is used with an + * ValueAnimator object. + * @param values The set of values to animate between. + */ + public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) { + KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); + if (keyframeSet instanceof IntKeyframeSet) { + return new IntPropertyValuesHolder(propertyName, (IntKeyframeSet) keyframeSet); + } else if (keyframeSet instanceof FloatKeyframeSet) { + return new FloatPropertyValuesHolder(propertyName, (FloatKeyframeSet) keyframeSet); + } + else { + PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); + pvh.mKeyframeSet = keyframeSet; + pvh.mValueType = ((Keyframe)values[0]).getType(); + return pvh; + } + } + + /** + * Constructs and returns a PropertyValuesHolder object with the specified property and set + * of values. These values can be of any type, but the type should be consistent so that + * an appropriate {@link android.animation.TypeEvaluator} can be found that matches + * the common type. + * <p>If there is only one value, it is assumed to be the end value of an animation, + * and an initial value will be derived, if possible, by calling the property's + * {@link android.util.Property#get(Object)} function. + * Also, if any value is null, the value will be filled in when the animation + * starts in the same way. This mechanism of automatically getting null values only works + * if the PropertyValuesHolder object is used in conjunction with + * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has + * no way of determining what the value should be. + * @param property The property associated with this set of values. Should not be null. + * @param values The set of values to animate between. + */ + public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) { + KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); + if (keyframeSet instanceof IntKeyframeSet) { + return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet); + } else if (keyframeSet instanceof FloatKeyframeSet) { + return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet); + } + else { + PropertyValuesHolder pvh = new PropertyValuesHolder(property); + pvh.mKeyframeSet = keyframeSet; + pvh.mValueType = ((Keyframe)values[0]).getType(); + return pvh; + } + } + + /** + * Set the animated values for this object to this set of ints. + * If there is only one value, it is assumed to be the end value of an animation, + * and an initial value will be derived, if possible, by calling a getter function + * on the object. Also, if any value is null, the value will be filled in when the animation + * starts in the same way. This mechanism of automatically getting null values only works + * if the PropertyValuesHolder object is used in conjunction + * {@link ObjectAnimator}, and with a getter function + * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has + * no way of determining what the value should be. + * + * @param values One or more values that the animation will animate between. + */ + public void setIntValues(int... values) { + mValueType = int.class; + mKeyframeSet = KeyframeSet.ofInt(values); + } + + /** + * Set the animated values for this object to this set of floats. + * If there is only one value, it is assumed to be the end value of an animation, + * and an initial value will be derived, if possible, by calling a getter function + * on the object. Also, if any value is null, the value will be filled in when the animation + * starts in the same way. This mechanism of automatically getting null values only works + * if the PropertyValuesHolder object is used in conjunction + * {@link ObjectAnimator}, and with a getter function + * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has + * no way of determining what the value should be. + * + * @param values One or more values that the animation will animate between. + */ + public void setFloatValues(float... values) { + mValueType = float.class; + mKeyframeSet = KeyframeSet.ofFloat(values); + } + + /** + * Set the animated values for this object to this set of Keyframes. + * + * @param values One or more values that the animation will animate between. + */ + public void setKeyframes(Keyframe... values) { + int numKeyframes = values.length; + Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)]; + mValueType = ((Keyframe)values[0]).getType(); + for (int i = 0; i < numKeyframes; ++i) { + keyframes[i] = (Keyframe)values[i]; + } + mKeyframeSet = new KeyframeSet(keyframes); + } + + /** + * Set the animated values for this object to this set of Objects. + * If there is only one value, it is assumed to be the end value of an animation, + * and an initial value will be derived, if possible, by calling a getter function + * on the object. Also, if any value is null, the value will be filled in when the animation + * starts in the same way. This mechanism of automatically getting null values only works + * if the PropertyValuesHolder object is used in conjunction + * {@link ObjectAnimator}, and with a getter function + * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has + * no way of determining what the value should be. + * + * @param values One or more values that the animation will animate between. + */ + public void setObjectValues(Object... values) { + mValueType = values[0].getClass(); + mKeyframeSet = KeyframeSet.ofObject(values); + } + + /** + * 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) { + /* The native implementation uses JNI to do reflection, which allows access to private methods. + * getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback. + */ + try { + returnVal = targetClass.getDeclaredMethod(methodName, args); + returnVal.setAccessible(true); + } catch (NoSuchMethodException e2) { + Log.e("PropertyValuesHolder", + "Couldn't find no-arg method for property " + mPropertyName + ": " + e); + } + } + } else { + args = new Class[1]; + Class typeVariants[]; + if (mValueType.equals(Float.class)) { + typeVariants = FLOAT_VARIANTS; + } else if (mValueType.equals(Integer.class)) { + typeVariants = INTEGER_VARIANTS; + } else if (mValueType.equals(Double.class)) { + typeVariants = DOUBLE_VARIANTS; + } else { + typeVariants = new Class[1]; + typeVariants[0] = mValueType; + } + for (Class typeVariant : typeVariants) { + args[0] = typeVariant; + try { + returnVal = targetClass.getMethod(methodName, args); + // change the value type to suit + mValueType = typeVariant; + return returnVal; + } catch (NoSuchMethodException e) { + /* The native implementation uses JNI to do reflection, which allows access to private methods. + * getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback. + */ + try { + returnVal = targetClass.getDeclaredMethod(methodName, args); + returnVal.setAccessible(true); + // change the value type to suit + mValueType = typeVariant; + return returnVal; + } catch (NoSuchMethodException e2) { + // Swallow the error and keep trying other variants + } + } + } + // If we got here, then no appropriate function was found + Log.e("PropertyValuesHolder", + "Couldn't find setter/getter for property " + mPropertyName + + " with value type "+ mValueType); + } + + return returnVal; + } + + + /** + * 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; + try { + // 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 it + mPropertyMapLock.writeLock().lock(); + HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass); + if (propertyMap != null) { + setterOrGetter = propertyMap.get(mPropertyName); + } + if (setterOrGetter == null) { + setterOrGetter = getPropertyFunction(targetClass, prefix, valueType); + if (propertyMap == null) { + propertyMap = new HashMap<String, Method>(); + propertyMapMap.put(targetClass, propertyMap); + } + propertyMap.put(mPropertyName, setterOrGetter); + } + } finally { + mPropertyMapLock.writeLock().unlock(); + } + return setterOrGetter; + } + + /** + * Utility function to get the setter from targetClass + * @param targetClass The Class on which the requested method should exist. + */ + void setupSetter(Class targetClass) { + mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType); + } + + /** + * Utility function to get the getter from targetClass + */ + private void setupGetter(Class targetClass) { + mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null); + } + + /** + * 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) { + if (mProperty != null) { + // check to make sure that mProperty is on the class of target + try { + Object testValue = mProperty.get(target); + for (Keyframe kf : mKeyframeSet.mKeyframes) { + if (!kf.hasValue()) { + kf.setValue(mProperty.get(target)); + } + } + return; + } catch (ClassCastException e) { + Log.e("PropertyValuesHolder","No such property (" + mProperty.getName() + + ") on target object " + target + ". Trying reflection instead"); + mProperty = null; + } + } + Class targetClass = target.getClass(); + if (mSetter == null) { + setupSetter(targetClass); + } + for (Keyframe kf : mKeyframeSet.mKeyframes) { + if (!kf.hasValue()) { + if (mGetter == null) { + setupGetter(targetClass); + } + try { + kf.setValue(mGetter.invoke(target)); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); + } + } + } + } + + /** + * Utility function to set the value stored in a particular Keyframe. The value used is + * whatever the value is for the property name specified in the keyframe on the target object. + * + * @param target The target object from which the current value should be extracted. + * @param kf The keyframe which holds the property name and value. + */ + private void setupValue(Object target, Keyframe kf) { + if (mProperty != null) { + kf.setValue(mProperty.get(target)); + } + try { + if (mGetter == null) { + Class targetClass = target.getClass(); + setupGetter(targetClass); + } + kf.setValue(mGetter.invoke(target)); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); + } + } + + /** + * This function is called by ObjectAnimator when setting the start values for an animation. + * The start values are set according to the current values in the target object. The + * property whose value is extracted is whatever is specified by the propertyName of this + * PropertyValuesHolder object. + * + * @param target The object which holds the start values that should be set. + */ + void setupStartValue(Object target) { + setupValue(target, mKeyframeSet.mKeyframes.get(0)); + } + + /** + * This function is called by ObjectAnimator when setting the end values for an animation. + * The end values are set according to the current values in the target object. The + * property whose value is extracted is whatever is specified by the propertyName of this + * PropertyValuesHolder object. + * + * @param target The object which holds the start values that should be set. + */ + void setupEndValue(Object target) { + setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1)); + } + + @Override + public PropertyValuesHolder clone() { + try { + PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone(); + newPVH.mPropertyName = mPropertyName; + newPVH.mProperty = mProperty; + newPVH.mKeyframeSet = mKeyframeSet.clone(); + newPVH.mEvaluator = mEvaluator; + return newPVH; + } catch (CloneNotSupportedException e) { + // won't reach here + return null; + } + } + + /** + * 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 + */ + void setAnimatedValue(Object target) { + if (mProperty != null) { + mProperty.set(target, getAnimatedValue()); + } + if (mSetter != null) { + try { + mTmpValueArray[0] = getAnimatedValue(); + mSetter.invoke(target, mTmpValueArray); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); + } + } + } + + /** + * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used + * to calculate animated values. + */ + void init() { + if (mEvaluator == null) { + // We already handle int and float automatically, but not their Object + // equivalents + mEvaluator = (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 class + mKeyframeSet.setEvaluator(mEvaluator); + } + } + + /** + * The TypeEvaluator will the automatically determined based on the type of values + * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so + * desired. This may be important in cases where either the type of the values supplied + * do not match the way that they should be interpolated between, or if the values + * are of a custom type or one not currently understood by the animation system. Currently, + * only values of type float and int (and their Object equivalents: Float + * and Integer) are correctly interpolated; all other types require setting a TypeEvaluator. + * @param evaluator + */ + public void setEvaluator(TypeEvaluator evaluator) { + mEvaluator = evaluator; + mKeyframeSet.setEvaluator(evaluator); + } + + /** + * Function used to calculate the value according to the evaluator set up for + * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue(). + * + * @param fraction The elapsed, interpolated fraction of the animation. + */ + void calculateValue(float fraction) { + mAnimatedValue = mKeyframeSet.getValue(fraction); + } + + /** + * Sets the name of the property that will be animated. This name is used to derive + * a setter function that will be called to set animated values. + * For example, a property name of <code>foo</code> will result + * in a call to the function <code>setFoo()</code> on the target object. If either + * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will + * also be derived and called. + * + * <p>Note that the setter function derived from this property name + * must take the same parameter type as the + * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to + * the setter function will fail.</p> + * + * @param propertyName The name of the property being animated. + */ + public void setPropertyName(String propertyName) { + mPropertyName = propertyName; + } + + /** + * Sets the property that will be animated. + * + * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property + * must exist on the target object specified in that ObjectAnimator.</p> + * + * @param property The property being animated. + */ + public void setProperty(Property property) { + mProperty = property; + } + + /** + * Gets the name of the property that will be animated. This name will be used to derive + * a setter function that will be called to set animated values. + * For example, a property name of <code>foo</code> will result + * in a call to the function <code>setFoo()</code> on the target object. If either + * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will + * also be derived and called. + */ + public String getPropertyName() { + return mPropertyName; + } + + /** + * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value + * most recently calculated in calculateValue(). + * @return + */ + Object getAnimatedValue() { + return mAnimatedValue; + } + + @Override + public String toString() { + return mPropertyName + ": " + mKeyframeSet.toString(); + } + + /** + * 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 here + return prefix; + } + char firstLetter = Character.toUpperCase(propertyName.charAt(0)); + String theRest = propertyName.substring(1); + return prefix + firstLetter + theRest; + } + + static class IntPropertyValuesHolder extends PropertyValuesHolder { + + // Cache JNI functions to avoid looking them up twice + //private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = + // new HashMap<Class, HashMap<String, Integer>>(); + //int mJniSetter; + private IntProperty mIntProperty; + + IntKeyframeSet mIntKeyframeSet; + int mIntAnimatedValue; + + public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) { + super(propertyName); + mValueType = int.class; + mKeyframeSet = keyframeSet; + mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; + } + + public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) { + super(property); + mValueType = int.class; + mKeyframeSet = keyframeSet; + mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; + if (property instanceof IntProperty) { + mIntProperty = (IntProperty) mProperty; + } + } + + public IntPropertyValuesHolder(String propertyName, int... values) { + super(propertyName); + setIntValues(values); + } + + public IntPropertyValuesHolder(Property property, int... values) { + super(property); + setIntValues(values); + if (property instanceof IntProperty) { + mIntProperty = (IntProperty) mProperty; + } + } + + @Override + public void setIntValues(int... values) { + super.setIntValues(values); + mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; + } + + @Override + void calculateValue(float fraction) { + mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction); + } + + @Override + Object getAnimatedValue() { + return mIntAnimatedValue; + } + + @Override + public IntPropertyValuesHolder clone() { + IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone(); + newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet; + return newPVH; + } + + /** + * 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 + */ + @Override + void setAnimatedValue(Object target) { + if (mIntProperty != null) { + mIntProperty.setValue(target, mIntAnimatedValue); + return; + } + if (mProperty != null) { + mProperty.set(target, mIntAnimatedValue); + return; + } + //if (mJniSetter != 0) { + // nCallIntMethod(target, mJniSetter, mIntAnimatedValue); + // return; + //} + if (mSetter != null) { + try { + mTmpValueArray[0] = mIntAnimatedValue; + mSetter.invoke(target, mTmpValueArray); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); + } + } + } + + @Override + void setupSetter(Class targetClass) { + if (mProperty != null) { + return; + } + // Check new static hashmap<propName, int> for setter method + //try { + // mPropertyMapLock.writeLock().lock(); + // HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass); + // if (propertyMap != null) { + // Integer mJniSetterInteger = propertyMap.get(mPropertyName); + // if (mJniSetterInteger != null) { + // mJniSetter = mJniSetterInteger; + // } + // } + // if (mJniSetter == 0) { + // String methodName = getMethodName("set", mPropertyName); + // mJniSetter = nGetIntMethod(targetClass, methodName); + // if (mJniSetter != 0) { + // if (propertyMap == null) { + // propertyMap = new HashMap<String, Integer>(); + // sJNISetterPropertyMap.put(targetClass, propertyMap); + // } + // propertyMap.put(mPropertyName, mJniSetter); + // } + // } + //} catch (NoSuchMethodError e) { + // Log.d("PropertyValuesHolder", + // "Can't find native method using JNI, use reflection" + e); + //} finally { + // mPropertyMapLock.writeLock().unlock(); + //} + //if (mJniSetter == 0) { + // Couldn't find method through fast JNI approach - just use reflection + super.setupSetter(targetClass); + //} + } + } + + static class FloatPropertyValuesHolder extends PropertyValuesHolder { + + // Cache JNI functions to avoid looking them up twice + //private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = + // new HashMap<Class, HashMap<String, Integer>>(); + //int mJniSetter; + private FloatProperty mFloatProperty; + + FloatKeyframeSet mFloatKeyframeSet; + float mFloatAnimatedValue; + + public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) { + super(propertyName); + mValueType = float.class; + mKeyframeSet = keyframeSet; + mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; + } + + public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) { + super(property); + mValueType = float.class; + mKeyframeSet = keyframeSet; + mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; + if (property instanceof FloatProperty) { + mFloatProperty = (FloatProperty) mProperty; + } + } + + public FloatPropertyValuesHolder(String propertyName, float... values) { + super(propertyName); + setFloatValues(values); + } + + public FloatPropertyValuesHolder(Property property, float... values) { + super(property); + setFloatValues(values); + if (property instanceof FloatProperty) { + mFloatProperty = (FloatProperty) mProperty; + } + } + + @Override + public void setFloatValues(float... values) { + super.setFloatValues(values); + mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; + } + + @Override + void calculateValue(float fraction) { + mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction); + } + + @Override + Object getAnimatedValue() { + return mFloatAnimatedValue; + } + + @Override + public FloatPropertyValuesHolder clone() { + FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone(); + newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet; + return newPVH; + } + + /** + * 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 + */ + @Override + void 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()); + } + } + } + + @Override + void setupSetter(Class targetClass) { + if (mProperty != null) { + return; + } + // Check new static hashmap<propName, int> for setter method + //try { + // mPropertyMapLock.writeLock().lock(); + // HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass); + // if (propertyMap != null) { + // Integer mJniSetterInteger = propertyMap.get(mPropertyName); + // if (mJniSetterInteger != null) { + // mJniSetter = mJniSetterInteger; + // } + // } + // if (mJniSetter == 0) { + // String methodName = getMethodName("set", mPropertyName); + // mJniSetter = nGetFloatMethod(targetClass, methodName); + // if (mJniSetter != 0) { + // if (propertyMap == null) { + // propertyMap = new HashMap<String, Integer>(); + // sJNISetterPropertyMap.put(targetClass, propertyMap); + // } + // propertyMap.put(mPropertyName, mJniSetter); + // } + // } + //} catch (NoSuchMethodError e) { + // Log.d("PropertyValuesHolder", + // "Can't find native method using JNI, use reflection" + e); + //} finally { + // mPropertyMapLock.writeLock().unlock(); + //} + //if (mJniSetter == 0) { + // Couldn't find method through fast JNI approach - just use reflection + super.setupSetter(targetClass); + //} + } + + } + + //native static private int nGetIntMethod(Class targetClass, String methodName); + //native static private int nGetFloatMethod(Class targetClass, String methodName); + //native static private void nCallIntMethod(Object target, int methodID, int arg); + //native static private void nCallFloatMethod(Object target, int methodID, float arg); +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/TimeAnimator.java @@ -0,0 +1,78 @@ +package com.nineoldandroids.animation; + +/** + * This class provides a simple callback mechanism to listeners that is synchronized with other + * animators in the system. There is no duration, interpolation, or object value-setting + * with this Animator. Instead, it is simply started and proceeds to send out events on every + * animation frame to its TimeListener (if set), with information about this animator, + * the total elapsed time, and the time since the last animation frame. + * + * @hide + */ +public class TimeAnimator extends ValueAnimator { + + private TimeListener mListener; + private long mPreviousTime = -1; + + @Override + boolean animationFrame(long currentTime) { + if (mPlayingState == STOPPED) { + mPlayingState = RUNNING; + if (mSeekTime < 0) { + mStartTime = currentTime; + } else { + mStartTime = currentTime - mSeekTime; + // Now that we're playing, reset the seek time + mSeekTime = -1; + } + } + if (mListener != null) { + long totalTime = currentTime - mStartTime; + long deltaTime = (mPreviousTime < 0) ? 0 : (currentTime - mPreviousTime); + mPreviousTime = currentTime; + mListener.onTimeUpdate(this, totalTime, deltaTime); + } + return false; + } + + /** + * Sets a listener that is sent update events throughout the life of + * an animation. + * + * @param listener the listener to be set. + */ + public void setTimeListener(TimeListener listener) { + mListener = listener; + } + + @Override + void animateValue(float fraction) { + // Noop + } + + @Override + void initAnimation() { + // noop + } + + /** + * Implementors of this interface can set themselves as update listeners + * to a <code>TimeAnimator</code> instance to receive callbacks on every animation + * frame to receive the total time since the animator started and the delta time + * since the last frame. The first time the listener is called, totalTime and + * deltaTime should both be zero. + * + * @hide + */ + public static interface TimeListener { + /** + * <p>Notifies listeners of the occurrence of another frame of the animation, + * along with information about the elapsed time.</p> + * + * @param animation The animator sending out the notification. + * @param totalTime The + */ + void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime); + + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/TypeEvaluator.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +/** + * Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators + * allow developers to create animations on arbitrary property types, by allowing them to supply + * custom evaulators for types that are not automatically understood and used by the animation + * system. + * + * @see ValueAnimator#setEvaluator(TypeEvaluator) + */ +public interface TypeEvaluator<T> { + + /** + * This function returns the result of linearly interpolating the start and end values, with + * <code>fraction</code> representing the proportion between the start and end values. The + * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>, + * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>, + * and <code>t</code> is <code>fraction</code>. + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start value. + * @param endValue The end value. + * @return A linear interpolation between the start and end values, given the + * <code>fraction</code> parameter. + */ + public T evaluate(float fraction, T startValue, T endValue); + +} \ No newline at end of file
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/animation/ValueAnimator.java @@ -0,0 +1,1264 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.animation; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.AndroidRuntimeException; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * This class provides a simple timing engine for running animations + * which calculate animated values and set them on target objects. + * + * <p>There is a single timing pulse that all animations use. It runs in a + * custom handler to ensure that property changes happen on the UI thread.</p> + * + * <p>By default, ValueAnimator uses non-linear time interpolation, via the + * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates + * out of an animation. This behavior can be changed by calling + * {@link ValueAnimator#setInterpolator(TimeInterpolator)}.</p> + */ +public class ValueAnimator extends Animator { + + /** + * Internal constants + */ + + /* + * The default amount of time in ms between animation frames + */ + private static final long DEFAULT_FRAME_DELAY = 10; + + /** + * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent + * by the handler to itself to process the next animation frame + */ + static final int ANIMATION_START = 0; + static final int ANIMATION_FRAME = 1; + + /** + * Values used with internal variable mPlayingState to indicate the current state of an + * animation. + */ + static final int STOPPED = 0; // Not yet playing + static final int RUNNING = 1; // Playing normally + static final int SEEKED = 2; // Seeked to some time value + + /** + * Internal variables + * NOTE: This object implements the clone() method, making a deep copy of any referenced + * objects. As other non-trivial fields are added to this class, make sure to add logic + * to clone() to make deep copies of them. + */ + + // The first time that the animation's animateFrame() method is called. This time is used to + // determine elapsed time (and therefore the elapsed fraction) in subsequent calls + // to animateFrame() + long mStartTime; + + /** + * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked + * to a value. + */ + long mSeekTime = -1; + + // TODO: We access the following ThreadLocal variables often, some of them on every update. + // If ThreadLocal access is significantly expensive, we may want to put all of these + // fields into a structure sot hat we just access ThreadLocal once to get the reference + // to that structure, then access the structure directly for each field. + + // The static sAnimationHandler processes the internal timing loop on which all animations + // are based + private static ThreadLocal<AnimationHandler> sAnimationHandler = + new ThreadLocal<AnimationHandler>(); + + // The per-thread list of all active animations + private static final ThreadLocal<ArrayList<ValueAnimator>> sAnimations = + new ThreadLocal<ArrayList<ValueAnimator>>() { + @Override + protected ArrayList<ValueAnimator> initialValue() { + return new ArrayList<ValueAnimator>(); + } + }; + + // The per-thread set of animations to be started on the next animation frame + private static final ThreadLocal<ArrayList<ValueAnimator>> sPendingAnimations = + new ThreadLocal<ArrayList<ValueAnimator>>() { + @Override + protected ArrayList<ValueAnimator> initialValue() { + return new ArrayList<ValueAnimator>(); + } + }; + + /** + * Internal per-thread collections used to avoid set collisions as animations start and end + * while being processed. + */ + private static final ThreadLocal<ArrayList<ValueAnimator>> sDelayedAnims = + new ThreadLocal<ArrayList<ValueAnimator>>() { + @Override + protected ArrayList<ValueAnimator> initialValue() { + return new ArrayList<ValueAnimator>(); + } + }; + + private static final ThreadLocal<ArrayList<ValueAnimator>> sEndingAnims = + new ThreadLocal<ArrayList<ValueAnimator>>() { + @Override + protected ArrayList<ValueAnimator> initialValue() { + return new ArrayList<ValueAnimator>(); + } + }; + + private static final ThreadLocal<ArrayList<ValueAnimator>> sReadyAnims = + new ThreadLocal<ArrayList<ValueAnimator>>() { + @Override + protected ArrayList<ValueAnimator> initialValue() { + return new ArrayList<ValueAnimator>(); + } + }; + + // The time interpolator to be used if none is set on the animation + private static final /*Time*/Interpolator sDefaultInterpolator = + new AccelerateDecelerateInterpolator(); + + // type evaluators for the primitive types handled by this implementation + private static final TypeEvaluator sIntEvaluator = new IntEvaluator(); + private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator(); + + /** + * Used to indicate whether the animation is currently playing in reverse. This causes the + * elapsed fraction to be inverted to calculate the appropriate values. + */ + private boolean mPlayingBackwards = false; + + /** + * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the + * repeatCount (if repeatCount!=INFINITE), the animation ends + */ + private int mCurrentIteration = 0; + + /** + * Tracks current elapsed/eased fraction, for querying in getAnimatedFraction(). + */ + private float mCurrentFraction = 0f; + + /** + * Tracks whether a startDelay'd animation has begun playing through the startDelay. + */ + private boolean mStartedDelay = false; + + /** + * Tracks the time at which the animation began playing through its startDelay. This is + * different from the mStartTime variable, which is used to track when the animation became + * active (which is when the startDelay expired and the animation was added to the active + * animations list). + */ + private long mDelayStartTime; + + /** + * Flag that represents the current state of the animation. Used to figure out when to start + * an animation (if state == STOPPED). Also used to end an animation that + * has been cancel()'d or end()'d since the last animation frame. Possible values are + * STOPPED, RUNNING, SEEKED. + */ + int mPlayingState = STOPPED; + + /** + * Additional playing state to indicate whether an animator has been start()'d. There is + * some lag between a call to start() and the first animation frame. We should still note + * that the animation has been started, even if it's first animation frame has not yet + * happened, and reflect that state in isRunning(). + * Note that delayed animations are different: they are not started until their first + * animation frame, which occurs after their delay elapses. + */ + private boolean mRunning = false; + + /** + * Additional playing state to indicate whether an animator has been start()'d, whether or + * not there is a nonzero startDelay. + */ + private boolean mStarted = false; + + /** + * Flag that denotes whether the animation is set up and ready to go. Used to + * set up animation that has not yet been started. + */ + boolean mInitialized = false; + + // + // Backing variables + // + + // How long the animation should last in ms + private long mDuration = 300; + + // The amount of time in ms to delay starting the animation after start() is called + private long mStartDelay = 0; + + // The number of milliseconds between animation frames + private static long sFrameDelay = DEFAULT_FRAME_DELAY; + + // The number of times the animation will repeat. The default is 0, which means the animation + // will play only once + private int mRepeatCount = 0; + + /** + * The type of repetition that will occur when repeatMode is nonzero. RESTART means the + * animation will start from the beginning on every new cycle. REVERSE means the animation + * will reverse directions on each iteration. + */ + private int mRepeatMode = RESTART; + + /** + * The time interpolator to be used. The elapsed fraction of the animation will be passed + * through this interpolator to calculate the interpolated fraction, which is then used to + * calculate the animated values. + */ + private /*Time*/Interpolator mInterpolator = sDefaultInterpolator; + + /** + * The set of listeners to be sent events through the life of an animation. + */ + private ArrayList<AnimatorUpdateListener> mUpdateListeners = null; + + /** + * The property/value sets being animated. + */ + PropertyValuesHolder[] mValues; + + /** + * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values + * by property name during calls to getAnimatedValue(String). + */ + HashMap<String, PropertyValuesHolder> mValuesMap; + + /** + * Public constants + */ + + /** + * When the animation reaches the end and <code>repeatCount</code> is INFINITE + * or a positive value, the animation restarts from the beginning. + */ + public static final int RESTART = 1; + /** + * When the animation reaches the end and <code>repeatCount</code> is INFINITE + * or a positive value, the animation reverses direction on every iteration. + */ + public static final int REVERSE = 2; + /** + * This value used used with the {@link #setRepeatCount(int)} property to repeat + * the animation indefinitely. + */ + public static final int INFINITE = -1; + + /** + * Creates a new ValueAnimator object. This default constructor is primarily for + * use internally; the factory methods which take parameters are more generally + * useful. + */ + public ValueAnimator() { + } + + /** + * Constructs and returns a ValueAnimator that animates between int values. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + * @param values A set of values that the animation will animate between over time. + * @return A ValueAnimator object that is set up to animate between the given values. + */ + public static ValueAnimator ofInt(int... values) { + ValueAnimator anim = new ValueAnimator(); + anim.setIntValues(values); + return anim; + } + + /** + * Constructs and returns a ValueAnimator that animates between float values. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + * @param values A set of values that the animation will animate between over time. + * @return A ValueAnimator object that is set up to animate between the given values. + */ + public static ValueAnimator ofFloat(float... values) { + ValueAnimator anim = new ValueAnimator(); + anim.setFloatValues(values); + return anim; + } + + /** + * Constructs and returns a ValueAnimator that animates between the values + * specified in the PropertyValuesHolder objects. + * + * @param values A set of PropertyValuesHolder objects whose values will be animated + * between over time. + * @return A ValueAnimator object that is set up to animate between the given values. + */ + public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) { + ValueAnimator anim = new ValueAnimator(); + anim.setValues(values); + return anim; + } + /** + * Constructs and returns a ValueAnimator that animates between Object values. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + * <p>Since ValueAnimator does not know how to animate between arbitrary Objects, this + * factory method also takes a TypeEvaluator object that the ValueAnimator will use + * to perform that interpolation. + * + * @param evaluator A TypeEvaluator that will be called on each animation frame to + * provide the ncessry interpolation between the Object values to derive the animated + * value. + * @param values A set of values that the animation will animate between over time. + * @return A ValueAnimator object that is set up to animate between the given values. + */ + public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) { + ValueAnimator anim = new ValueAnimator(); + anim.setObjectValues(values); + anim.setEvaluator(evaluator); + return anim; + } + + /** + * Sets int values that will be animated between. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + * <p>If there are already multiple sets of values defined for this ValueAnimator via more + * than one PropertyValuesHolder object, this method will set the values for the first + * of those objects.</p> + * + * @param values A set of values that the animation will animate between over time. + */ + public void setIntValues(int... values) { + if (values == null || values.length == 0) { + return; + } + if (mValues == null || mValues.length == 0) { + setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofInt("", values)}); + } else { + PropertyValuesHolder valuesHolder = mValues[0]; + valuesHolder.setIntValues(values); + } + // New property/values/target should cause re-initialization prior to starting + mInitialized = false; + } + + /** + * Sets float values that will be animated between. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + * <p>If there are already multiple sets of values defined for this ValueAnimator via more + * than one PropertyValuesHolder object, this method will set the values for the first + * of those objects.</p> + * + * @param values A set of values that the animation will animate between over time. + */ + public void setFloatValues(float... values) { + if (values == null || values.length == 0) { + return; + } + if (mValues == null || mValues.length == 0) { + setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofFloat("", values)}); + } else { + PropertyValuesHolder valuesHolder = mValues[0]; + valuesHolder.setFloatValues(values); + } + // New property/values/target should cause re-initialization prior to starting + mInitialized = false; + } + + /** + * Sets the values to animate between for this animation. A single + * value implies that that value is the one being animated to. However, this is not typically + * useful in a ValueAnimator object because there is no way for the object to determine the + * starting value for the animation (unlike ObjectAnimator, which can derive that value + * from the target object and property being animated). Therefore, there should typically + * be two or more values. + * + * <p>If there are already multiple sets of values defined for this ValueAnimator via more + * than one PropertyValuesHolder object, this method will set the values for the first + * of those objects.</p> + * + * <p>There should be a TypeEvaluator set on the ValueAnimator that knows how to interpolate + * between these value objects. ValueAnimator only knows how to interpolate between the + * primitive types specified in the other setValues() methods.</p> + * + * @param values The set of values to animate between. + */ + public void setObjectValues(Object... values) { + if (values == null || values.length == 0) { + return; + } + if (mValues == null || mValues.length == 0) { + setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofObject("", + (TypeEvaluator)null, values)}); + } else { + PropertyValuesHolder valuesHolder = mValues[0]; + valuesHolder.setObjectValues(values); + } + // New property/values/target should cause re-initialization prior to starting + mInitialized = false; + } + + /** + * Sets the values, per property, being animated between. This function is called internally + * by the constructors of ValueAnimator that take a list of values. But an ValueAnimator can + * be constructed without values and this method can be called to set the values manually + * instead. + * + * @param values The set of values, per property, being animated between. + */ + 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 = (PropertyValuesHolder) values[i]; + mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); + } + // New property/values/target should cause re-initialization prior to starting + mInitialized = false; + } + + /** + * Returns the values that this ValueAnimator animates between. These values are stored in + * PropertyValuesHolder objects, even if the ValueAnimator was created with a simple list + * of value objects instead. + * + * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the + * values, per property, that define the animation. + */ + public PropertyValuesHolder[] getValues() { + return mValues; + } + + /** + * This function is called immediately before processing the first animation + * frame of an animation. If there is a nonzero <code>startDelay</code>, the + * function is called after that delay ends. + * It takes care of the final initialization steps for the + * animation. + * + * <p>Overrides of this method should call the superclass method to ensure + * that internal mechanisms for the animation are set up correctly.</p> + */ + void initAnimation() { + if (!mInitialized) { + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].init(); + } + mInitialized = true; + } + } + + + /** + * Sets the length of the animation. The default duration is 300 milliseconds. + * + * @param duration The length of the animation, in milliseconds. This value cannot + * be negative. + * @return ValueAnimator The object called with setDuration(). This return + * value makes it easier to compose statements together that construct and then set the + * duration, as in <code>ValueAnimator.ofInt(0, 10).setDuration(500).start()</code>. + */ + public ValueAnimator setDuration(long duration) { + if (duration < 0) { + throw new IllegalArgumentException("Animators cannot have negative duration: " + + duration); + } + mDuration = duration; + return this; + } + + /** + * Gets the length of the animation. The default duration is 300 milliseconds. + * + * @return The length of the animation, in milliseconds. + */ + public long getDuration() { + return mDuration; + } + + /** + * Sets the position of the animation to the specified point in time. This time should + * be between 0 and the total duration of the animation, including any repetition. If + * the animation has not yet been started, then it will not advance forward after it is + * set to this time; it will simply set the time to this value and perform any appropriate + * actions based on that time. If the animation is already running, then setCurrentPlayTime() + * will set the current playing time to this value and continue playing from that point. + * + * @param playTime The time, in milliseconds, to which the animation is advanced or rewound. + */ + public void setCurrentPlayTime(long playTime) { + initAnimation(); + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + if (mPlayingState != RUNNING) { + mSeekTime = playTime; + mPlayingState = SEEKED; + } + mStartTime = currentTime - playTime; + animationFrame(currentTime); + } + + /** + * Gets the current position of the animation in time, which is equal to the current + * time minus the time that the animation started. An animation that is not yet started will + * return a value of zero. + * + * @return The current position in time of the animation. + */ + public long getCurrentPlayTime() { + if (!mInitialized || mPlayingState == STOPPED) { + return 0; + } + return AnimationUtils.currentAnimationTimeMillis() - mStartTime; + } + + /** + * This custom, static handler handles the timing pulse that is shared by + * all active animations. This approach ensures that the setting of animation + * values will happen on the UI thread and that all animations will share + * the same times for calculating their values, which makes synchronizing + * animations possible. + * + */ + private static class AnimationHandler extends Handler { + /** + * There are only two messages that we care about: ANIMATION_START and + * ANIMATION_FRAME. The START message is sent when an animation's start() + * method is called. It cannot start synchronously when start() is called + * because the call may be on the wrong thread, and it would also not be + * synchronized with other animations because it would not start on a common + * timing pulse. So each animation sends a START message to the handler, which + * causes the handler to place the animation on the active animations queue and + * start processing frames for that animation. + * The FRAME message is the one that is sent over and over while there are any + * active animations to process. + */ + @Override + public void handleMessage(Message msg) { + boolean callAgain = true; + ArrayList<ValueAnimator> animations = sAnimations.get(); + ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get(); + switch (msg.what) { + // TODO: should we avoid sending frame message when starting if we + // were already running? + case ANIMATION_START: + ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get(); + if (animations.size() > 0 || delayedAnims.size() > 0) { + callAgain = false; + } + // pendingAnims holds any animations that have requested to be started + // We're going to clear sPendingAnimations, but starting animation may + // cause more to be added to the pending list (for example, if one animation + // starting triggers another starting). So we loop until sPendingAnimations + // is empty. + while (pendingAnimations.size() > 0) { + ArrayList<ValueAnimator> pendingCopy = + (ArrayList<ValueAnimator>) pendingAnimations.clone(); + pendingAnimations.clear(); + int count = pendingCopy.size(); + for (int i = 0; i < count; ++i) { + ValueAnimator anim = pendingCopy.get(i); + // If the animation has a startDelay, place it on the delayed list + if (anim.mStartDelay == 0) { + anim.startAnimation(); + } else { + delayedAnims.add(anim); + } + } + } + // fall through to process first frame of new animations + case ANIMATION_FRAME: + // currentTime holds the common time for all animations processed + // during this frame + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + ArrayList<ValueAnimator> readyAnims = sReadyAnims.get(); + ArrayList<ValueAnimator> endingAnims = sEndingAnims.get(); + + // First, process animations currently sitting on the delayed queue, adding + // them to the active animations if they are ready + int numDelayedAnims = delayedAnims.size(); + for (int i = 0; i < numDelayedAnims; ++i) { + ValueAnimator anim = delayedAnims.get(i); + if (anim.delayedAnimationFrame(currentTime)) { + readyAnims.add(anim); + } + } + int numReadyAnims = readyAnims.size(); + if (numReadyAnims > 0) { + for (int i = 0; i < numReadyAnims; ++i) { + ValueAnimator anim = readyAnims.get(i); + anim.startAnimation(); + anim.mRunning = true; + delayedAnims.remove(anim); + } + readyAnims.clear(); + } + + // Now process all active animations. The return value from animationFrame() + // tells the handler whether it should now be ended + int numAnims = animations.size(); + int i = 0; + while (i < numAnims) { + ValueAnimator anim = animations.get(i); + if (anim.animationFrame(currentTime)) { + endingAnims.add(anim); + } + if (animations.size() == numAnims) { + ++i; + } else { + // An animation might be canceled or ended by client code + // during the animation frame. Check to see if this happened by + // seeing whether the current index is the same as it was before + // calling animationFrame(). Another approach would be to copy + // animations to a temporary list and process that list instead, + // but that entails garbage and processing overhead that would + // be nice to avoid. + --numAnims; + endingAnims.remove(anim); + } + } + if (endingAnims.size() > 0) { + for (i = 0; i < endingAnims.size(); ++i) { + endingAnims.get(i).endAnimation(); + } + endingAnims.clear(); + } + + // If there are still active or delayed animations, call the handler again + // after the frameDelay + if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) { + sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay - + (AnimationUtils.currentAnimationTimeMillis() - currentTime))); + } + break; + } + } + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + * + * @return the number of milliseconds to delay running the animation + */ + public long getStartDelay() { + return mStartDelay; + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + + * @param startDelay The amount of the delay, in milliseconds + */ + public void setStartDelay(long startDelay) { + this.mStartDelay = startDelay; + } + + /** + * The amount of time, in milliseconds, between each frame of the animation. This is a + * requested time that the animation will attempt to honor, but the actual delay between + * frames may be different, depending on system load and capabilities. This is a static + * function because the same delay will be applied to all animations, since they are all + * run off of a single timing loop. + * + * @return the requested time between frames, in milliseconds + */ + public static long getFrameDelay() { + return sFrameDelay; + } + + /** + * The amount of time, in milliseconds, between each frame of the animation. This is a + * requested time that the animation will attempt to honor, but the actual delay between + * frames may be different, depending on system load and capabilities. This is a static + * function because the same delay will be applied to all animations, since they are all + * run off of a single timing loop. + * + * @param frameDelay the requested time between frames, in milliseconds + */ + public static void setFrameDelay(long frameDelay) { + sFrameDelay = frameDelay; + } + + /** + * The most recent value calculated by this <code>ValueAnimator</code> when there is just one + * property being animated. This value is only sensible while the animation is running. The main + * purpose for this read-only property is to retrieve the value from the <code>ValueAnimator</code> + * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which + * is called during each animation frame, immediately after the value is calculated. + * + * @return animatedValue The value most recently calculated by this <code>ValueAnimator</code> for + * the single property being animated. If there are several properties being animated + * (specified by several PropertyValuesHolder objects in the constructor), this function + * returns the animated value for the first of those objects. + */ + public Object getAnimatedValue() { + if (mValues != null && mValues.length > 0) { + return mValues[0].getAnimatedValue(); + } + // Shouldn't get here; should always have values unless ValueAnimator was set up wrong + return null; + } + + /** + * The most recent value calculated by this <code>ValueAnimator</code> for <code>propertyName</code>. + * The main purpose for this read-only property is to retrieve the value from the + * <code>ValueAnimator</code> during a call to + * {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which + * is called during each animation frame, immediately after the value is calculated. + * + * @return animatedValue The value most recently calculated for the named property + * by this <code>ValueAnimator</code>. + */ + public Object getAnimatedValue(String propertyName) { + PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName); + if (valuesHolder != null) { + return valuesHolder.getAnimatedValue(); + } else { + // At least avoid crashing if called with bogus propertyName + return null; + } + } + + /** + * Sets how many times the animation should be repeated. If the repeat + * count is 0, the animation is never repeated. If the repeat count is + * greater than 0 or {@link #INFINITE}, the repeat mode will be taken + * into account. The repeat count is 0 by default. + * + * @param value the number of times the animation should be repeated + */ + public void setRepeatCount(int value) { + mRepeatCount = value; + } + /** + * Defines how many times the animation should repeat. The default value + * is 0. + * + * @return the number of times the animation should repeat, or {@link #INFINITE} + */ + public int getRepeatCount() { + return mRepeatCount; + } + + /** + * Defines what this animation should do when it reaches the end. This + * setting is applied only when the repeat count is either greater than + * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}. + * + * @param value {@link #RESTART} or {@link #REVERSE} + */ + public void setRepeatMode(int value) { + mRepeatMode = value; + } + + /** + * Defines what this animation should do when it reaches the end. + * + * @return either one of {@link #REVERSE} or {@link #RESTART} + */ + public int getRepeatMode() { + return mRepeatMode; + } + + /** + * Adds a listener to the set of listeners that are sent update events through the life of + * an animation. This method is called on all listeners for every frame of the animation, + * after the values for the animation have been calculated. + * + * @param listener the listener to be added to the current set of listeners for this animation. + */ + public void addUpdateListener(AnimatorUpdateListener listener) { + if (mUpdateListeners == null) { + mUpdateListeners = new ArrayList<AnimatorUpdateListener>(); + } + mUpdateListeners.add(listener); + } + + /** + * Removes all listeners from the set listening to frame updates for this animation. + */ + public void removeAllUpdateListeners() { + if (mUpdateListeners == null) { + return; + } + mUpdateListeners.clear(); + mUpdateListeners = null; + } + + /** + * Removes a listener from the set listening to frame updates for this animation. + * + * @param listener the listener to be removed from the current set of update listeners + * for this animation. + */ + public void removeUpdateListener(AnimatorUpdateListener listener) { + if (mUpdateListeners == null) { + return; + } + mUpdateListeners.remove(listener); + if (mUpdateListeners.size() == 0) { + mUpdateListeners = null; + } + } + + + /** + * The time interpolator used in calculating the elapsed fraction of this animation. The + * interpolator determines whether the animation runs with linear or non-linear motion, + * such as acceleration and deceleration. The default value is + * {@link android.view.animation.AccelerateDecelerateInterpolator} + * + * @param value the interpolator to be used by this animation. A value of <code>null</code> + * will result in linear interpolation. + */ + @Override + public void setInterpolator(/*Time*/Interpolator value) { + if (value != null) { + mInterpolator = value; + } else { + mInterpolator = new LinearInterpolator(); + } + } + + /** + * Returns the timing interpolator that this ValueAnimator uses. + * + * @return The timing interpolator for this ValueAnimator. + */ + public /*Time*/Interpolator getInterpolator() { + return mInterpolator; + } + + /** + * The type evaluator to be used when calculating the animated values of this animation. + * The system will automatically assign a float or int evaluator based on the type + * of <code>startValue</code> and <code>endValue</code> in the constructor. But if these values + * are not one of these primitive types, or if different evaluation is desired (such as is + * necessary with int values that represent colors), a custom evaluator needs to be assigned. + * For example, when running an animation on color values, the {@link ArgbEvaluator} + * should be used to get correct RGB color interpolation. + * + * <p>If this ValueAnimator has only one set of values being animated between, this evaluator + * will be used for that set. If there are several sets of values being animated, which is + * the case if PropertyValuesHOlder objects were set on the ValueAnimator, then the evaluator + * is assigned just to the first PropertyValuesHolder object.</p> + * + * @param value the evaluator to be used this animation + */ + public void setEvaluator(TypeEvaluator value) { + if (value != null && mValues != null && mValues.length > 0) { + mValues[0].setEvaluator(value); + } + } + + /** + * Start the animation playing. This version of start() takes a boolean flag that indicates + * whether the animation should play in reverse. The flag is usually false, but may be set + * to true if called from the reverse() method. + * + * <p>The animation started by calling this method will be run on the thread that called + * this method. This thread should have a Looper on it (a runtime exception will be thrown if + * this is not the case). Also, if the animation will animate + * properties of objects in the view hierarchy, then the calling thread should be the UI + * thread for that view hierarchy.</p> + * + * @param playBackwards Whether the ValueAnimator should start playing in reverse. + */ + private void start(boolean playBackwards) { + if (Looper.myLooper() == null) { + throw new AndroidRuntimeException("Animators may only be run on Looper threads"); + } + mPlayingBackwards = playBackwards; + mCurrentIteration = 0; + mPlayingState = STOPPED; + mStarted = true; + mStartedDelay = false; + sPendingAnimations.get().add(this); + if (mStartDelay == 0) { + // This sets the initial value of the animation, prior to actually starting it running + setCurrentPlayTime(getCurrentPlayTime()); + mPlayingState = STOPPED; + mRunning = true; + + if (mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationStart(this); + } + } + } + AnimationHandler animationHandler = sAnimationHandler.get(); + if (animationHandler == null) { + animationHandler = new AnimationHandler(); + sAnimationHandler.set(animationHandler); + } + animationHandler.sendEmptyMessage(ANIMATION_START); + } + + @Override + public void start() { + start(false); + } + + @Override + public void cancel() { + // Only cancel if the animation is actually running or has been started and is about + // to run + if (mPlayingState != STOPPED || sPendingAnimations.get().contains(this) || + sDelayedAnims.get().contains(this)) { + // Only notify listeners if the animator has actually started + if (mRunning && mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationCancel(this); + } + } + endAnimation(); + } + } + + @Override + public void end() { + if (!sAnimations.get().contains(this) && !sPendingAnimations.get().contains(this)) { + // Special case if the animation has not yet started; get it ready for ending + mStartedDelay = false; + startAnimation(); + } else if (!mInitialized) { + initAnimation(); + } + // The final value set on the target varies, depending on whether the animation + // was supposed to repeat an odd number of times + if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) { + animateValue(0f); + } else { + animateValue(1f); + } + endAnimation(); + } + + @Override + public boolean isRunning() { + return (mPlayingState == RUNNING || mRunning); + } + + @Override + public boolean isStarted() { + return mStarted; + } + + /** + * Plays the ValueAnimator in reverse. If the animation is already running, + * it will stop itself and play backwards from the point reached when reverse was called. + * If the animation is not currently running, then it will start from the end and + * play backwards. This behavior is only set for the current animation; future playing + * of the animation will use the default behavior of playing forward. + */ + public void reverse() { + mPlayingBackwards = !mPlayingBackwards; + if (mPlayingState == RUNNING) { + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + long currentPlayTime = currentTime - mStartTime; + long timeLeft = mDuration - currentPlayTime; + mStartTime = currentTime - timeLeft; + } else { + start(true); + } + } + + /** + * Called internally to end an animation by removing it from the animations list. Must be + * called on the UI thread. + */ + private void endAnimation() { + sAnimations.get().remove(this); + sPendingAnimations.get().remove(this); + sDelayedAnims.get().remove(this); + mPlayingState = STOPPED; + if (mRunning && mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationEnd(this); + } + } + mRunning = false; + mStarted = false; + } + + /** + * Called internally to start an animation by adding it to the active animations list. Must be + * called on the UI thread. + */ + private void startAnimation() { + initAnimation(); + sAnimations.get().add(this); + if (mStartDelay > 0 && mListeners != null) { + // Listeners were already notified in start() if startDelay is 0; this is + // just for delayed animations + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationStart(this); + } + } + } + + /** + * Internal function called to process an animation frame on an animation that is currently + * sleeping through its <code>startDelay</code> phase. The return value indicates whether it + * should be woken up and put on the active animations queue. + * + * @param currentTime The current animation time, used to calculate whether the animation + * has exceeded its <code>startDelay</code> and should be started. + * @return True if the animation's <code>startDelay</code> has been exceeded and the animation + * should be added to the set of active animations. + */ + private boolean delayedAnimationFrame(long currentTime) { + if (!mStartedDelay) { + mStartedDelay = true; + mDelayStartTime = currentTime; + } else { + long deltaTime = currentTime - mDelayStartTime; + if (deltaTime > mStartDelay) { + // startDelay ended - start the anim and record the + // mStartTime appropriately + mStartTime = currentTime - (deltaTime - mStartDelay); + mPlayingState = RUNNING; + return true; + } + } + return false; + } + + /** + * 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 animationFrame(long currentTime) { + boolean done = false; + + if (mPlayingState == STOPPED) { + mPlayingState = RUNNING; + if (mSeekTime < 0) { + mStartTime = currentTime; + } else { + mStartTime = currentTime - mSeekTime; + // Now that we're playing, reset the seek time + mSeekTime = -1; + } + } + switch (mPlayingState) { + case RUNNING: + case SEEKED: + float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f; + if (fraction >= 1f) { + if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) { + // Time to repeat + if (mListeners != null) { + int numListeners = mListeners.size(); + for (int i = 0; i < numListeners; ++i) { + mListeners.get(i).onAnimationRepeat(this); + } + } + if (mRepeatMode == REVERSE) { + mPlayingBackwards = mPlayingBackwards ? false : true; + } + mCurrentIteration += (int)fraction; + fraction = fraction % 1f; + mStartTime += mDuration; + } else { + done = true; + fraction = Math.min(fraction, 1.0f); + } + } + if (mPlayingBackwards) { + fraction = 1f - fraction; + } + animateValue(fraction); + break; + } + + return done; + } + + /** + * Returns the current animation fraction, which is the elapsed/interpolated fraction used in + * the most recent frame update on the animation. + * + * @return Elapsed/interpolated fraction of the animation. + */ + public float getAnimatedFraction() { + return mCurrentFraction; + } + + /** + * This method is called with the elapsed fraction of the animation during every + * animation frame. This function turns the elapsed fraction into an interpolated fraction + * and then into an animated value (from the evaluator. The function is called mostly during + * animation updates, but it is also called when the <code>end()</code> + * function is called, to set the final value on the property. + * + * <p>Overrides of this method must call the superclass to perform the calculation + * of the animated value.</p> + * + * @param fraction The elapsed fraction of the animation. + */ + void animateValue(float fraction) { + fraction = mInterpolator.getInterpolation(fraction); + mCurrentFraction = fraction; + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].calculateValue(fraction); + } + if (mUpdateListeners != null) { + int numListeners = mUpdateListeners.size(); + for (int i = 0; i < numListeners; ++i) { + mUpdateListeners.get(i).onAnimationUpdate(this); + } + } + } + + @Override + public ValueAnimator clone() { + final ValueAnimator anim = (ValueAnimator) super.clone(); + if (mUpdateListeners != null) { + ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners; + anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(); + int numListeners = oldListeners.size(); + for (int i = 0; i < numListeners; ++i) { + anim.mUpdateListeners.add(oldListeners.get(i)); + } + } + anim.mSeekTime = -1; + anim.mPlayingBackwards = false; + anim.mCurrentIteration = 0; + anim.mInitialized = false; + anim.mPlayingState = STOPPED; + anim.mStartedDelay = false; + PropertyValuesHolder[] oldValues = mValues; + if (oldValues != null) { + int numValues = oldValues.length; + anim.mValues = new PropertyValuesHolder[numValues]; + anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues); + for (int i = 0; i < numValues; ++i) { + PropertyValuesHolder newValuesHolder = oldValues[i].clone(); + anim.mValues[i] = newValuesHolder; + anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder); + } + } + return anim; + } + + /** + * Implementors of this interface can add themselves as update listeners + * to an <code>ValueAnimator</code> instance to receive callbacks on every animation + * frame, after the current frame's values have been calculated for that + * <code>ValueAnimator</code>. + */ + public static interface AnimatorUpdateListener { + /** + * <p>Notifies the occurrence of another frame of the animation.</p> + * + * @param animation The animation which was repeated. + */ + void onAnimationUpdate(ValueAnimator animation); + + } + + /** + * Return the number of animations currently running. + * + * Used by StrictMode internally to annotate violations. Only + * called on the main thread. + * + * @hide + */ + public static int getCurrentAnimationsCount() { + return sAnimations.get().size(); + } + + /** + * Clear all animations on this thread, without canceling or ending them. + * This should be used with caution. + * + * @hide + */ + public static void clearAllAnimations() { + sAnimations.get().clear(); + sPendingAnimations.get().clear(); + sDelayedAnims.get().clear(); + } + + @Override + public String toString() { + String returnVal = "ValueAnimator@" + Integer.toHexString(hashCode()); + if (mValues != null) { + for (int i = 0; i < mValues.length; ++i) { + returnVal += "\n " + mValues[i].toString(); + } + } + return returnVal; + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/util/FloatProperty.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.nineoldandroids.util; + +/** + * An implementation of {@link android.util.Property} to be used specifically with fields of type + * <code>float</code>. This type-specific subclass enables performance benefit by allowing + * calls to a {@link #set(Object, Float) set()} function that takes the primitive + * <code>float</code> type and avoids autoboxing and other overhead associated with the + * <code>Float</code> class. + * + * @param <T> The class on which the Property is declared. + * + * @hide + */ +public abstract class FloatProperty<T> extends Property<T, Float> { + + public FloatProperty(String name) { + super(Float.class, name); + } + + /** + * A type-specific override of the {@link #set(Object, Float)} that is faster when dealing + * with fields of type <code>float</code>. + */ + public abstract void setValue(T object, float value); + + @Override + final public void set(T object, Float value) { + setValue(object, value); + } + +} \ No newline at end of file
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/util/IntProperty.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.nineoldandroids.util; + +/** + * An implementation of {@link android.util.Property} to be used specifically with fields of type + * <code>int</code>. This type-specific subclass enables performance benefit by allowing + * calls to a {@link #set(Object, Integer) set()} function that takes the primitive + * <code>int</code> type and avoids autoboxing and other overhead associated with the + * <code>Integer</code> class. + * + * @param <T> The class on which the Property is declared. + * + * @hide + */ +public abstract class IntProperty<T> extends Property<T, Integer> { + + public IntProperty(String name) { + super(Integer.class, name); + } + + /** + * A type-specific override of the {@link #set(Object, Integer)} that is faster when dealing + * with fields of type <code>int</code>. + */ + public abstract void setValue(T object, int value); + + @Override + final public void set(T object, Integer value) { + set(object, value.intValue()); + } + +} \ No newline at end of file
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/util/NoSuchPropertyException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.nineoldandroids.util; + +/** + * Thrown when code requests a {@link Property} on a class that does + * not expose the appropriate method or field. + * + * @see Property#of(java.lang.Class, java.lang.Class, java.lang.String) + */ +public class NoSuchPropertyException extends RuntimeException { + + public NoSuchPropertyException(String s) { + super(s); + } + +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/util/Property.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.nineoldandroids.util; + + +/** + * A property is an abstraction that can be used to represent a <emb>mutable</em> value that is held + * in a <em>host</em> object. The Property's {@link #set(Object, Object)} or {@link #get(Object)} + * methods can be implemented in terms of the private fields of the host object, or via "setter" and + * "getter" methods or by some other mechanism, as appropriate. + * + * @param <T> The class on which the property is declared. + * @param <V> The type that this property represents. + */ +public abstract class Property<T, V> { + + private final String mName; + private final Class<V> mType; + + /** + * This factory method creates and returns a Property given the <code>class</code> and + * <code>name</code> parameters, where the <code>"name"</code> parameter represents either: + * <ul> + * <li>a public <code>getName()</code> method on the class which takes no arguments, plus an + * optional public <code>setName()</code> method which takes a value of the same type + * returned by <code>getName()</code> + * <li>a public <code>isName()</code> method on the class which takes no arguments, plus an + * optional public <code>setName()</code> method which takes a value of the same type + * returned by <code>isName()</code> + * <li>a public <code>name</code> field on the class + * </ul> + * + * <p>If either of the get/is method alternatives is found on the class, but an appropriate + * <code>setName()</code> method is not found, the <code>Property</code> will be + * {@link #isReadOnly() readOnly}. Calling the {@link #set(Object, Object)} method on such + * a property is allowed, but will have no effect.</p> + * + * <p>If neither the methods nor the field are found on the class a + * {@link NoSuchPropertyException} exception will be thrown.</p> + */ + public static <T, V> Property<T, V> of(Class<T> hostType, Class<V> valueType, String name) { + return new ReflectiveProperty<T, V>(hostType, valueType, name); + } + + /** + * A constructor that takes an identifying name and {@link #getType() type} for the property. + */ + public Property(Class<V> type, String name) { + mName = name; + mType = type; + } + + /** + * Returns true if the {@link #set(Object, Object)} method does not set the value on the target + * object (in which case the {@link #set(Object, Object) set()} method should throw a {@link + * NoSuchPropertyException} exception). This may happen if the Property wraps functionality that + * allows querying the underlying value but not setting it. For example, the {@link #of(Class, + * Class, String)} factory method may return a Property with name "foo" for an object that has + * only a <code>getFoo()</code> or <code>isFoo()</code> method, but no matching + * <code>setFoo()</code> method. + */ + public boolean isReadOnly() { + return false; + } + + /** + * Sets the value on <code>object</code> which this property represents. If the method is unable + * to set the value on the target object it will throw an {@link UnsupportedOperationException} + * exception. + */ + public void set(T object, V value) { + throw new UnsupportedOperationException("Property " + getName() +" is read-only"); + } + + /** + * Returns the current value that this property represents on the given <code>object</code>. + */ + public abstract V get(T object); + + /** + * Returns the name for this property. + */ + public String getName() { + return mName; + } + + /** + * Returns the type for this property. + */ + public Class<V> getType() { + return mType; + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/util/ReflectiveProperty.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.nineoldandroids.util; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Internal class to automatically generate a Property for a given class/name pair, given the + * specification of {@link Property#of(java.lang.Class, java.lang.Class, java.lang.String)} + */ +class ReflectiveProperty<T, V> extends Property<T, V> { + + private static final String PREFIX_GET = "get"; + private static final String PREFIX_IS = "is"; + private static final String PREFIX_SET = "set"; + private Method mSetter; + private Method mGetter; + private Field mField; + + /** + * For given property name 'name', look for getName/isName method or 'name' field. + * Also look for setName method (optional - could be readonly). Failing method getters and + * field results in throwing NoSuchPropertyException. + * + * @param propertyHolder The class on which the methods or field are found + * @param name The name of the property, where this name is capitalized and appended to + * "get" and "is to search for the appropriate methods. If the get/is methods are not found, + * the constructor will search for a field with that exact name. + */ + public ReflectiveProperty(Class<T> propertyHolder, Class<V> valueType, String name) { + // TODO: cache reflection info for each new class/name pair + super(valueType, name); + char firstLetter = Character.toUpperCase(name.charAt(0)); + String theRest = name.substring(1); + String capitalizedName = firstLetter + theRest; + String getterName = PREFIX_GET + capitalizedName; + try { + mGetter = propertyHolder.getMethod(getterName, (Class<?>[]) null); + } catch (NoSuchMethodException e) { + try { + /* The native implementation uses JNI to do reflection, which allows access to private methods. + * getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback. + */ + mGetter = propertyHolder.getDeclaredMethod(getterName, (Class<?>[]) null); + mGetter.setAccessible(true); + } catch (NoSuchMethodException e2) { + // getName() not available - try isName() instead + getterName = PREFIX_IS + capitalizedName; + try { + mGetter = propertyHolder.getMethod(getterName, (Class<?>[]) null); + } catch (NoSuchMethodException e3) { + try { + /* The native implementation uses JNI to do reflection, which allows access to private methods. + * getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback. + */ + mGetter = propertyHolder.getDeclaredMethod(getterName, (Class<?>[]) null); + mGetter.setAccessible(true); + } catch (NoSuchMethodException e4) { + // Try public field instead + try { + mField = propertyHolder.getField(name); + Class fieldType = mField.getType(); + if (!typesMatch(valueType, fieldType)) { + throw new NoSuchPropertyException("Underlying type (" + fieldType + ") " + + "does not match Property type (" + valueType + ")"); + } + return; + } catch (NoSuchFieldException e5) { + // no way to access property - throw appropriate exception + throw new NoSuchPropertyException("No accessor method or field found for" + + " property with name " + name); + } + } + } + } + } + Class getterType = mGetter.getReturnType(); + // Check to make sure our getter type matches our valueType + if (!typesMatch(valueType, getterType)) { + throw new NoSuchPropertyException("Underlying type (" + getterType + ") " + + "does not match Property type (" + valueType + ")"); + } + String setterName = PREFIX_SET + capitalizedName; + try { + // mSetter = propertyHolder.getMethod(setterName, getterType); + // The native implementation uses JNI to do reflection, which allows access to private methods. + mSetter = propertyHolder.getDeclaredMethod(setterName, getterType); + mSetter.setAccessible(true); + } catch (NoSuchMethodException ignored) { + // Okay to not have a setter - just a readonly property + } + } + + /** + * Utility method to check whether the type of the underlying field/method on the target + * object matches the type of the Property. The extra checks for primitive types are because + * generics will force the Property type to be a class, whereas the type of the underlying + * method/field will probably be a primitive type instead. Accept float as matching Float, + * etc. + */ + private boolean typesMatch(Class<V> valueType, Class getterType) { + if (getterType != valueType) { + if (getterType.isPrimitive()) { + return (getterType == float.class && valueType == Float.class) || + (getterType == int.class && valueType == Integer.class) || + (getterType == boolean.class && valueType == Boolean.class) || + (getterType == long.class && valueType == Long.class) || + (getterType == double.class && valueType == Double.class) || + (getterType == short.class && valueType == Short.class) || + (getterType == byte.class && valueType == Byte.class) || + (getterType == char.class && valueType == Character.class); + } + return false; + } + return true; + } + + @Override + public void set(T object, V value) { + if (mSetter != null) { + try { + mSetter.invoke(object, value); + } catch (IllegalAccessException e) { + throw new AssertionError(); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } + } else if (mField != null) { + try { + mField.set(object, value); + } catch (IllegalAccessException e) { + throw new AssertionError(); + } + } else { + throw new UnsupportedOperationException("Property " + getName() +" is read-only"); + } + } + + @Override + public V get(T object) { + if (mGetter != null) { + try { + return (V) mGetter.invoke(object, (Object[])null); + } catch (IllegalAccessException e) { + throw new AssertionError(); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } + } else if (mField != null) { + try { + return (V) mField.get(object); + } catch (IllegalAccessException e) { + throw new AssertionError(); + } + } + // Should not get here: there should always be a non-null getter or field + throw new AssertionError(); + } + + /** + * Returns false if there is no setter or public field underlying this Property. + */ + @Override + public boolean isReadOnly() { + return (mSetter == null && mField == null); + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/view/ViewHelper.java @@ -0,0 +1,292 @@ +package com.nineoldandroids.view; + +import android.view.View; + +import static com.nineoldandroids.view.animation.AnimatorProxy.NEEDS_PROXY; +import static com.nineoldandroids.view.animation.AnimatorProxy.wrap; + +public final class ViewHelper { + private ViewHelper() {} + + public static float getAlpha(View view) { + return NEEDS_PROXY ? wrap(view).getAlpha() : Honeycomb.getAlpha(view); + } + + public static void setAlpha(View view, float alpha) { + if (NEEDS_PROXY) { + wrap(view).setAlpha(alpha); + } else { + Honeycomb.setAlpha(view, alpha); + } + } + + public static float getPivotX(View view) { + return NEEDS_PROXY ? wrap(view).getPivotX() : Honeycomb.getPivotX(view); + } + + public static void setPivotX(View view, float pivotX) { + if (NEEDS_PROXY) { + wrap(view).setPivotX(pivotX); + } else { + Honeycomb.setPivotX(view, pivotX); + } + } + + public static float getPivotY(View view) { + return NEEDS_PROXY ? wrap(view).getPivotY() : Honeycomb.getPivotY(view); + } + + public static void setPivotY(View view, float pivotY) { + if (NEEDS_PROXY) { + wrap(view).setPivotY(pivotY); + } else { + Honeycomb.setPivotY(view, pivotY); + } + } + + public static float getRotation(View view) { + return NEEDS_PROXY ? wrap(view).getRotation() : Honeycomb.getRotation(view); + } + + public static void setRotation(View view, float rotation) { + if (NEEDS_PROXY) { + wrap(view).setRotation(rotation); + } else { + Honeycomb.setRotation(view, rotation); + } + } + + public static float getRotationX(View view) { + return NEEDS_PROXY ? wrap(view).getRotationX() : Honeycomb.getRotationX(view); + } + + public static void setRotationX(View view, float rotationX) { + if (NEEDS_PROXY) { + wrap(view).setRotationX(rotationX); + } else { + Honeycomb.setRotationX(view, rotationX); + } + } + + public static float getRotationY(View view) { + return NEEDS_PROXY ? wrap(view).getRotationY() : Honeycomb.getRotationY(view); + } + + public static void setRotationY(View view, float rotationY) { + if (NEEDS_PROXY) { + wrap(view).setRotationY(rotationY); + } else { + Honeycomb.setRotationY(view, rotationY); + } + } + + public static float getScaleX(View view) { + return NEEDS_PROXY ? wrap(view).getScaleX() : Honeycomb.getScaleX(view); + } + + public static void setScaleX(View view, float scaleX) { + if (NEEDS_PROXY) { + wrap(view).setScaleX(scaleX); + } else { + Honeycomb.setScaleX(view, scaleX); + } + } + + public static float getScaleY(View view) { + return NEEDS_PROXY ? wrap(view).getScaleY() : Honeycomb.getScaleY(view); + } + + public static void setScaleY(View view, float scaleY) { + if (NEEDS_PROXY) { + wrap(view).setScaleY(scaleY); + } else { + Honeycomb.setScaleY(view, scaleY); + } + } + + public static float getScrollX(View view) { + return NEEDS_PROXY ? wrap(view).getScrollX() : Honeycomb.getScrollX(view); + } + + public static void setScrollX(View view, int scrollX) { + if (NEEDS_PROXY) { + wrap(view).setScrollX(scrollX); + } else { + Honeycomb.setScrollX(view, scrollX); + } + } + + public static float getScrollY(View view) { + return NEEDS_PROXY ? wrap(view).getScrollY() : Honeycomb.getScrollY(view); + } + + public static void setScrollY(View view, int scrollY) { + if (NEEDS_PROXY) { + wrap(view).setScrollY(scrollY); + } else { + Honeycomb.setScrollY(view, scrollY); + } + } + + public static float getTranslationX(View view) { + return NEEDS_PROXY ? wrap(view).getTranslationX() : Honeycomb.getTranslationX(view); + } + + public static void setTranslationX(View view, float translationX) { + if (NEEDS_PROXY) { + wrap(view).setTranslationX(translationX); + } else { + Honeycomb.setTranslationX(view, translationX); + } + } + + public static float getTranslationY(View view) { + return NEEDS_PROXY ? wrap(view).getTranslationY() : Honeycomb.getTranslationY(view); + } + + public static void setTranslationY(View view, float translationY) { + if (NEEDS_PROXY) { + wrap(view).setTranslationY(translationY); + } else { + Honeycomb.setTranslationY(view, translationY); + } + } + + public static float getX(View view) { + return NEEDS_PROXY ? wrap(view).getX() : Honeycomb.getX(view); + } + + public static void setX(View view, float x) { + if (NEEDS_PROXY) { + wrap(view).setX(x); + } else { + Honeycomb.setX(view, x); + } + } + + public static float getY(View view) { + return NEEDS_PROXY ? wrap(view).getY() : Honeycomb.getY(view); + } + + public static void setY(View view, float y) { + if (NEEDS_PROXY) { + wrap(view).setY(y); + } else { + Honeycomb.setY(view, y); + } + } + + private static final class Honeycomb { + static float getAlpha(View view) { + return view.getAlpha(); + } + + static void setAlpha(View view, float alpha) { + view.setAlpha(alpha); + } + + static float getPivotX(View view) { + return view.getPivotX(); + } + + static void setPivotX(View view, float pivotX) { + view.setPivotX(pivotX); + } + + static float getPivotY(View view) { + return view.getPivotY(); + } + + static void setPivotY(View view, float pivotY) { + view.setPivotY(pivotY); + } + + static float getRotation(View view) { + return view.getRotation(); + } + + static void setRotation(View view, float rotation) { + view.setRotation(rotation); + } + + static float getRotationX(View view) { + return view.getRotationX(); + } + + static void setRotationX(View view, float rotationX) { + view.setRotationX(rotationX); + } + + static float getRotationY(View view) { + return view.getRotationY(); + } + + static void setRotationY(View view, float rotationY) { + view.setRotationY(rotationY); + } + + static float getScaleX(View view) { + return view.getScaleX(); + } + + static void setScaleX(View view, float scaleX) { + view.setScaleX(scaleX); + } + + static float getScaleY(View view) { + return view.getScaleY(); + } + + static void setScaleY(View view, float scaleY) { + view.setScaleY(scaleY); + } + + static float getScrollX(View view) { + return view.getScrollX(); + } + + static void setScrollX(View view, int scrollX) { + view.setScrollX(scrollX); + } + + static float getScrollY(View view) { + return view.getScrollY(); + } + + static void setScrollY(View view, int scrollY) { + view.setScrollY(scrollY); + } + + static float getTranslationX(View view) { + return view.getTranslationX(); + } + + static void setTranslationX(View view, float translationX) { + view.setTranslationX(translationX); + } + + static float getTranslationY(View view) { + return view.getTranslationY(); + } + + static void setTranslationY(View view, float translationY) { + view.setTranslationY(translationY); + } + + static float getX(View view) { + return view.getX(); + } + + static void setX(View view, float x) { + view.setX(x); + } + + static float getY(View view) { + return view.getY(); + } + + static void setY(View view, float y) { + view.setY(y); + } + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimator.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.view; + +import java.util.WeakHashMap; +import android.os.Build; +import android.view.View; +import android.view.animation.Interpolator; +import com.nineoldandroids.animation.Animator; + +/** + * This class enables automatic and optimized animation of select properties on View objects. + * If only one or two properties on a View object are being animated, then using an + * {@link android.animation.ObjectAnimator} is fine; the property setters called by ObjectAnimator + * are well equipped to do the right thing to set the property and invalidate the view + * appropriately. But if several properties are animated simultaneously, or if you just want a + * more convenient syntax to animate a specific property, then ViewPropertyAnimator might be + * more well-suited to the task. + * + * <p>This class may provide better performance for several simultaneous animations, because + * it will optimize invalidate calls to take place only once for several properties instead of each + * animated property independently causing its own invalidation. Also, the syntax of using this + * class could be easier to use because the caller need only tell the View object which + * property to animate, and the value to animate either to or by, and this class handles the + * details of configuring the underlying Animator class and starting it.</p> + * + * <p>This class is not constructed by the caller, but rather by the View whose properties + * it will animate. Calls to {@link android.view.View#animate()} will return a reference + * to the appropriate ViewPropertyAnimator object for that View.</p> + * + */ +public abstract class ViewPropertyAnimator { + private static final WeakHashMap<View, ViewPropertyAnimator> ANIMATORS = + new WeakHashMap<View, ViewPropertyAnimator>(0); + + /** + * This method returns a ViewPropertyAnimator object, which can be used to animate specific + * properties on this View. + * + * @param view View to animate. + * @return The ViewPropertyAnimator associated with this View. + */ + public static ViewPropertyAnimator animate(View view) { + ViewPropertyAnimator animator = ANIMATORS.get(view); + if (animator == null) { + final int version = Integer.valueOf(Build.VERSION.SDK); + if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + animator = new ViewPropertyAnimatorICS(view); + } else if (version >= Build.VERSION_CODES.HONEYCOMB) { + animator = new ViewPropertyAnimatorHC(view); + } else { + animator = new ViewPropertyAnimatorPreHC(view); + } + ANIMATORS.put(view, animator); + } + return animator; + } + + + /** + * Sets the duration for the underlying animator that animates the requested properties. + * By default, the animator uses the default value for ValueAnimator. Calling this method + * will cause the declared value to be used instead. + * @param duration The length of ensuing property animations, in milliseconds. The value + * cannot be negative. + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator setDuration(long duration); + + /** + * Returns the current duration of property animations. If the duration was set on this + * object, that value is returned. Otherwise, the default value of the underlying Animator + * is returned. + * + * @see #setDuration(long) + * @return The duration of animations, in milliseconds. + */ + public abstract long getDuration(); + + /** + * Returns the current startDelay of property animations. If the startDelay was set on this + * object, that value is returned. Otherwise, the default value of the underlying Animator + * is returned. + * + * @see #setStartDelay(long) + * @return The startDelay of animations, in milliseconds. + */ + public abstract long getStartDelay(); + + /** + * Sets the startDelay for the underlying animator that animates the requested properties. + * By default, the animator uses the default value for ValueAnimator. Calling this method + * will cause the declared value to be used instead. + * @param startDelay The delay of ensuing property animations, in milliseconds. The value + * cannot be negative. + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator setStartDelay(long startDelay); + + /** + * Sets the interpolator for the underlying animator that animates the requested properties. + * By default, the animator uses the default interpolator for ValueAnimator. Calling this method + * will cause the declared object to be used instead. + * + * @param interpolator The TimeInterpolator to be used for ensuing property animations. + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator setInterpolator(/*Time*/Interpolator interpolator); + + /** + * Sets a listener for events in the underlying Animators that run the property + * animations. + * + * @param listener The listener to be called with AnimatorListener events. + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator setListener(Animator.AnimatorListener listener); + + /** + * Starts the currently pending property animations immediately. Calling <code>start()</code> + * is optional because all animations start automatically at the next opportunity. However, + * if the animations are needed to start immediately and synchronously (not at the time when + * the next event is processed by the hierarchy, which is when the animations would begin + * otherwise), then this method can be used. + */ + public abstract void start(); + + /** + * Cancels all property animations that are currently running or pending. + */ + public abstract void cancel(); + + /** + * This method will cause the View's <code>x</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setX(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator x(float value); + + /** + * This method will cause the View's <code>x</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setX(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator xBy(float value); + + /** + * This method will cause the View's <code>y</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setY(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator y(float value); + + /** + * This method will cause the View's <code>y</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setY(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator yBy(float value); + + /** + * This method will cause the View's <code>rotation</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setRotation(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator rotation(float value); + + /** + * This method will cause the View's <code>rotation</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setRotation(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator rotationBy(float value); + + /** + * This method will cause the View's <code>rotationX</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setRotationX(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator rotationX(float value); + + /** + * This method will cause the View's <code>rotationX</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setRotationX(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator rotationXBy(float value); + + /** + * This method will cause the View's <code>rotationY</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setRotationY(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator rotationY(float value); + + /** + * This method will cause the View's <code>rotationY</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setRotationY(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator rotationYBy(float value); + + /** + * This method will cause the View's <code>translationX</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setTranslationX(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator translationX(float value); + + /** + * This method will cause the View's <code>translationX</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setTranslationX(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator translationXBy(float value); + + /** + * This method will cause the View's <code>translationY</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setTranslationY(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator translationY(float value); + + /** + * This method will cause the View's <code>translationY</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setTranslationY(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator translationYBy(float value); + + /** + * This method will cause the View's <code>scaleX</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setScaleX(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator scaleX(float value); + + /** + * This method will cause the View's <code>scaleX</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setScaleX(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator scaleXBy(float value); + + /** + * This method will cause the View's <code>scaleY</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setScaleY(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator scaleY(float value); + + /** + * This method will cause the View's <code>scaleY</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setScaleY(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator scaleYBy(float value); + + /** + * This method will cause the View's <code>alpha</code> property to be animated to the + * specified value. Animations already running on the property will be canceled. + * + * @param value The value to be animated to. + * @see View#setAlpha(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator alpha(float value); + + /** + * This method will cause the View's <code>alpha</code> property to be animated by the + * specified value. Animations already running on the property will be canceled. + * + * @param value The amount to be animated by, as an offset from the current value. + * @see View#setAlpha(float) + * @return This object, allowing calls to methods in this class to be chained. + */ + public abstract ViewPropertyAnimator alphaBy(float value); +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorHC.java @@ -0,0 +1,723 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.view; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Set; +import android.view.View; +import android.view.animation.Interpolator; +import com.nineoldandroids.animation.Animator; +import com.nineoldandroids.animation.ValueAnimator; + +class ViewPropertyAnimatorHC extends ViewPropertyAnimator { + + /** + * A WeakReference holding the View whose properties are being animated by this class. + * This is set at construction time. + */ + private final WeakReference<View> mView; + + /** + * The duration of the underlying Animator object. By default, we don't set the duration + * on the Animator and just use its default duration. If the duration is ever set on this + * Animator, then we use the duration that it was set to. + */ + private long mDuration; + + /** + * A flag indicating whether the duration has been set on this object. If not, we don't set + * the duration on the underlying Animator, but instead just use its default duration. + */ + private boolean mDurationSet = false; + + /** + * The startDelay of the underlying Animator object. By default, we don't set the startDelay + * on the Animator and just use its default startDelay. If the startDelay is ever set on this + * Animator, then we use the startDelay that it was set to. + */ + private long mStartDelay = 0; + + /** + * A flag indicating whether the startDelay has been set on this object. If not, we don't set + * the startDelay on the underlying Animator, but instead just use its default startDelay. + */ + private boolean mStartDelaySet = false; + + /** + * The interpolator of the underlying Animator object. By default, we don't set the interpolator + * on the Animator and just use its default interpolator. If the interpolator is ever set on + * this Animator, then we use the interpolator that it was set to. + */ + private /*Time*/Interpolator mInterpolator; + + /** + * A flag indicating whether the interpolator has been set on this object. If not, we don't set + * the interpolator on the underlying Animator, but instead just use its default interpolator. + */ + private boolean mInterpolatorSet = false; + + /** + * Listener for the lifecycle events of the underlying + */ + private Animator.AnimatorListener mListener = null; + + /** + * This listener is the mechanism by which the underlying Animator causes changes to the + * properties currently being animated, as well as the cleanup after an animation is + * complete. + */ + private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener(); + + /** + * This list holds the properties that have been asked to animate. We allow the caller to + * request several animations prior to actually starting the underlying animator. This + * enables us to run one single animator to handle several properties in parallel. Each + * property is tossed onto the pending list until the animation actually starts (which is + * done by posting it onto mView), at which time the pending list is cleared and the properties + * on that list are added to the list of properties associated with that animator. + */ + ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>(); + + /** + * Constants used to associate a property being requested and the mechanism used to set + * the property (this class calls directly into View to set the properties in question). + */ + private static final int NONE = 0x0000; + private static final int TRANSLATION_X = 0x0001; + private static final int TRANSLATION_Y = 0x0002; + private static final int SCALE_X = 0x0004; + private static final int SCALE_Y = 0x0008; + private static final int ROTATION = 0x0010; + private static final int ROTATION_X = 0x0020; + private static final int ROTATION_Y = 0x0040; + private static final int X = 0x0080; + private static final int Y = 0x0100; + private static final int ALPHA = 0x0200; + + private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | SCALE_X | SCALE_Y | + ROTATION | ROTATION_X | ROTATION_Y | X | Y; + + /** + * The mechanism by which the user can request several properties that are then animated + * together works by posting this Runnable to start the underlying Animator. Every time + * a property animation is requested, we cancel any previous postings of the Runnable + * and re-post it. This means that we will only ever run the Runnable (and thus start the + * underlying animator) after the caller is done setting the properties that should be + * animated together. + */ + private Runnable mAnimationStarter = new Runnable() { + @Override + public void run() { + startAnimation(); + } + }; + + /** + * This class holds information about the overall animation being run on the set of + * properties. The mask describes which properties are being animated and the + * values holder is the list of all property/value objects. + */ + private static class PropertyBundle { + int mPropertyMask; + ArrayList<NameValuesHolder> mNameValuesHolder; + + PropertyBundle(int propertyMask, ArrayList<NameValuesHolder> nameValuesHolder) { + mPropertyMask = propertyMask; + mNameValuesHolder = nameValuesHolder; + } + + /** + * Removes the given property from being animated as a part of this + * PropertyBundle. If the property was a part of this bundle, it returns + * true to indicate that it was, in fact, canceled. This is an indication + * to the caller that a cancellation actually occurred. + * + * @param propertyConstant The property whose cancellation is requested. + * @return true if the given property is a part of this bundle and if it + * has therefore been canceled. + */ + boolean cancel(int propertyConstant) { + if ((mPropertyMask & propertyConstant) != 0 && mNameValuesHolder != null) { + int count = mNameValuesHolder.size(); + for (int i = 0; i < count; ++i) { + NameValuesHolder nameValuesHolder = mNameValuesHolder.get(i); + if (nameValuesHolder.mNameConstant == propertyConstant) { + mNameValuesHolder.remove(i); + mPropertyMask &= ~propertyConstant; + return true; + } + } + } + return false; + } + } + + /** + * This list tracks the list of properties being animated by any particular animator. + * In most situations, there would only ever be one animator running at a time. But it is + * possible to request some properties to animate together, then while those properties + * are animating, to request some other properties to animate together. The way that + * works is by having this map associate the group of properties being animated with the + * animator handling the animation. On every update event for an Animator, we ask the + * map for the associated properties and set them accordingly. + */ + private HashMap<Animator, PropertyBundle> mAnimatorMap = + new HashMap<Animator, PropertyBundle>(); + + /** + * This is the information we need to set each property during the animation. + * mNameConstant is used to set the appropriate field in View, and the from/delta + * values are used to calculate the animated value for a given animation fraction + * during the animation. + */ + private static class NameValuesHolder { + int mNameConstant; + float mFromValue; + float mDeltaValue; + NameValuesHolder(int nameConstant, float fromValue, float deltaValue) { + mNameConstant = nameConstant; + mFromValue = fromValue; + mDeltaValue = deltaValue; + } + } + + /** + * Constructor, called by View. This is private by design, as the user should only + * get a ViewPropertyAnimator by calling View.animate(). + * + * @param view The View associated with this ViewPropertyAnimator + */ + ViewPropertyAnimatorHC(View view) { + mView = new WeakReference<View>(view); + } + + /** + * Sets the duration for the underlying animator that animates the requested properties. + * By default, the animator uses the default value for ValueAnimator. Calling this method + * will cause the declared value to be used instead. + * @param duration The length of ensuing property animations, in milliseconds. The value + * cannot be negative. + * @return This object, allowing calls to methods in this class to be chained. + */ + public ViewPropertyAnimator setDuration(long duration) { + if (duration < 0) { + throw new IllegalArgumentException("Animators cannot have negative duration: " + + duration); + } + mDurationSet = true; + mDuration = duration; + return this; + } + + /** + * Returns the current duration of property animations. If the duration was set on this + * object, that value is returned. Otherwise, the default value of the underlying Animator + * is returned. + * + * @see #setDuration(long) + * @return The duration of animations, in milliseconds. + */ + public long getDuration() { + if (mDurationSet) { + return mDuration; + } else { + // Just return the default from ValueAnimator, since that's what we'd get if + // the value has not been set otherwise + return new ValueAnimator().getDuration(); + } + } + + @Override + public long getStartDelay() { + if (mStartDelaySet) { + return mStartDelay; + } else { + // Just return the default from ValueAnimator (0), since that's what we'd get if + // the value has not been set otherwise + return 0; + } + } + + @Override + public ViewPropertyAnimator setStartDelay(long startDelay) { + if (startDelay < 0) { + throw new IllegalArgumentException("Animators cannot have negative duration: " + + startDelay); + } + mStartDelaySet = true; + mStartDelay = startDelay; + return this; + } + + @Override + public ViewPropertyAnimator setInterpolator(/*Time*/Interpolator interpolator) { + mInterpolatorSet = true; + mInterpolator = interpolator; + return this; + } + + @Override + public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) { + mListener = listener; + return this; + } + + @Override + public void start() { + startAnimation(); + } + + @Override + public void cancel() { + if (mAnimatorMap.size() > 0) { + HashMap<Animator, PropertyBundle> mAnimatorMapCopy = + (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone(); + Set<Animator> animatorSet = mAnimatorMapCopy.keySet(); + for (Animator runningAnim : animatorSet) { + runningAnim.cancel(); + } + } + mPendingAnimations.clear(); + View v = mView.get(); + if (v != null) { + v.removeCallbacks(mAnimationStarter); + } + } + + @Override + public ViewPropertyAnimator x(float value) { + animateProperty(X, value); + return this; + } + + @Override + public ViewPropertyAnimator xBy(float value) { + animatePropertyBy(X, value); + return this; + } + + @Override + public ViewPropertyAnimator y(float value) { + animateProperty(Y, value); + return this; + } + + @Override + public ViewPropertyAnimator yBy(float value) { + animatePropertyBy(Y, value); + return this; + } + + @Override + public ViewPropertyAnimator rotation(float value) { + animateProperty(ROTATION, value); + return this; + } + + @Override + public ViewPropertyAnimator rotationBy(float value) { + animatePropertyBy(ROTATION, value); + return this; + } + + @Override + public ViewPropertyAnimator rotationX(float value) { + animateProperty(ROTATION_X, value); + return this; + } + + @Override + public ViewPropertyAnimator rotationXBy(float value) { + animatePropertyBy(ROTATION_X, value); + return this; + } + + @Override + public ViewPropertyAnimator rotationY(float value) { + animateProperty(ROTATION_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator rotationYBy(float value) { + animatePropertyBy(ROTATION_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator translationX(float value) { + animateProperty(TRANSLATION_X, value); + return this; + } + + @Override + public ViewPropertyAnimator translationXBy(float value) { + animatePropertyBy(TRANSLATION_X, value); + return this; + } + + @Override + public ViewPropertyAnimator translationY(float value) { + animateProperty(TRANSLATION_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator translationYBy(float value) { + animatePropertyBy(TRANSLATION_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator scaleX(float value) { + animateProperty(SCALE_X, value); + return this; + } + + @Override + public ViewPropertyAnimator scaleXBy(float value) { + animatePropertyBy(SCALE_X, value); + return this; + } + + @Override + public ViewPropertyAnimator scaleY(float value) { + animateProperty(SCALE_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator scaleYBy(float value) { + animatePropertyBy(SCALE_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator alpha(float value) { + animateProperty(ALPHA, value); + return this; + } + + @Override + public ViewPropertyAnimator alphaBy(float value) { + animatePropertyBy(ALPHA, value); + return this; + } + + /** + * Starts the underlying Animator for a set of properties. We use a single animator that + * simply runs from 0 to 1, and then use that fractional value to set each property + * value accordingly. + */ + private void startAnimation() { + ValueAnimator animator = ValueAnimator.ofFloat(1.0f); + ArrayList<NameValuesHolder> nameValueList = + (ArrayList<NameValuesHolder>) mPendingAnimations.clone(); + mPendingAnimations.clear(); + int propertyMask = 0; + int propertyCount = nameValueList.size(); + for (int i = 0; i < propertyCount; ++i) { + NameValuesHolder nameValuesHolder = nameValueList.get(i); + propertyMask |= nameValuesHolder.mNameConstant; + } + mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList)); + animator.addUpdateListener(mAnimatorEventListener); + animator.addListener(mAnimatorEventListener); + if (mStartDelaySet) { + animator.setStartDelay(mStartDelay); + } + if (mDurationSet) { + animator.setDuration(mDuration); + } + if (mInterpolatorSet) { + animator.setInterpolator(mInterpolator); + } + animator.start(); + } + + /** + * Utility function, called by the various x(), y(), etc. methods. This stores the + * constant name for the property along with the from/delta values that will be used to + * calculate and set the property during the animation. This structure is added to the + * pending animations, awaiting the eventual start() of the underlying animator. A + * Runnable is posted to start the animation, and any pending such Runnable is canceled + * (which enables us to end up starting just one animator for all of the properties + * specified at one time). + * + * @param constantName The specifier for the property being animated + * @param toValue The value to which the property will animate + */ + private void animateProperty(int constantName, float toValue) { + float fromValue = getValue(constantName); + float deltaValue = toValue - fromValue; + animatePropertyBy(constantName, fromValue, deltaValue); + } + + /** + * Utility function, called by the various xBy(), yBy(), etc. methods. This method is + * just like animateProperty(), except the value is an offset from the property's + * current value, instead of an absolute "to" value. + * + * @param constantName The specifier for the property being animated + * @param byValue The amount by which the property will change + */ + private void animatePropertyBy(int constantName, float byValue) { + float fromValue = getValue(constantName); + animatePropertyBy(constantName, fromValue, byValue); + } + + /** + * Utility function, called by animateProperty() and animatePropertyBy(), which handles the + * details of adding a pending animation and posting the request to start the animation. + * + * @param constantName The specifier for the property being animated + * @param startValue The starting value of the property + * @param byValue The amount by which the property will change + */ + private void animatePropertyBy(int constantName, float startValue, float byValue) { + // First, cancel any existing animations on this property + if (mAnimatorMap.size() > 0) { + Animator animatorToCancel = null; + Set<Animator> animatorSet = mAnimatorMap.keySet(); + for (Animator runningAnim : animatorSet) { + PropertyBundle bundle = mAnimatorMap.get(runningAnim); + if (bundle.cancel(constantName)) { + // property was canceled - cancel the animation if it's now empty + // Note that it's safe to break out here because every new animation + // on a property will cancel a previous animation on that property, so + // there can only ever be one such animation running. + if (bundle.mPropertyMask == NONE) { + // the animation is no longer changing anything - cancel it + animatorToCancel = runningAnim; + break; + } + } + } + if (animatorToCancel != null) { + animatorToCancel.cancel(); + } + } + + NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue); + mPendingAnimations.add(nameValuePair); + View v = mView.get(); + if (v != null) { + v.removeCallbacks(mAnimationStarter); + v.post(mAnimationStarter); + } + } + + /** + * This method handles setting the property values directly in the View object's fields. + * propertyConstant tells it which property should be set, value is the value to set + * the property to. + * + * @param propertyConstant The property to be set + * @param value The value to set the property to + */ + private void setValue(int propertyConstant, float value) { + //final View.TransformationInfo info = mView.mTransformationInfo; + View v = mView.get(); + if (v != null) { + switch (propertyConstant) { + case TRANSLATION_X: + //info.mTranslationX = value; + v.setTranslationX(value); + break; + case TRANSLATION_Y: + //info.mTranslationY = value; + v.setTranslationY(value); + break; + case ROTATION: + //info.mRotation = value; + v.setRotation(value); + break; + case ROTATION_X: + //info.mRotationX = value; + v.setRotationX(value); + break; + case ROTATION_Y: + //info.mRotationY = value; + v.setRotationY(value); + break; + case SCALE_X: + //info.mScaleX = value; + v.setScaleX(value); + break; + case SCALE_Y: + //info.mScaleY = value; + v.setScaleY(value); + break; + case X: + //info.mTranslationX = value - v.mLeft; + v.setX(value); + break; + case Y: + //info.mTranslationY = value - v.mTop; + v.setY(value); + break; + case ALPHA: + //info.mAlpha = value; + v.setAlpha(value); + break; + } + } + } + + /** + * This method gets the value of the named property from the View object. + * + * @param propertyConstant The property whose value should be returned + * @return float The value of the named property + */ + private float getValue(int propertyConstant) { + //final View.TransformationInfo info = mView.mTransformationInfo; + View v = mView.get(); + if (v != null) { + switch (propertyConstant) { + case TRANSLATION_X: + //return info.mTranslationX; + return v.getTranslationX(); + case TRANSLATION_Y: + //return info.mTranslationY; + return v.getTranslationY(); + case ROTATION: + //return info.mRotation; + return v.getRotation(); + case ROTATION_X: + //return info.mRotationX; + return v.getRotationX(); + case ROTATION_Y: + //return info.mRotationY; + return v.getRotationY(); + case SCALE_X: + //return info.mScaleX; + return v.getScaleX(); + case SCALE_Y: + //return info.mScaleY; + return v.getScaleY(); + case X: + //return v.mLeft + info.mTranslationX; + return v.getX(); + case Y: + //return v.mTop + info.mTranslationY; + return v.getY(); + case ALPHA: + //return info.mAlpha; + return v.getAlpha(); + } + } + return 0; + } + + /** + * Utility class that handles the various Animator events. The only ones we care + * about are the end event (which we use to clean up the animator map when an animator + * finishes) and the update event (which we use to calculate the current value of each + * property and then set it on the view object). + */ + private class AnimatorEventListener + implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { + @Override + public void onAnimationStart(Animator animation) { + if (mListener != null) { + mListener.onAnimationStart(animation); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (mListener != null) { + mListener.onAnimationCancel(animation); + } + } + + @Override + public void onAnimationRepeat(Animator animation) { + if (mListener != null) { + mListener.onAnimationRepeat(animation); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mListener != null) { + mListener.onAnimationEnd(animation); + } + mAnimatorMap.remove(animation); + // If the map is empty, it means all animation are done or canceled, so the listener + // isn't needed anymore. Not nulling it would cause it to leak any objects used in + // its implementation + if (mAnimatorMap.isEmpty()) { + mListener = null; + } + } + + /** + * Calculate the current value for each property and set it on the view. Invalidate + * the view object appropriately, depending on which properties are being animated. + * + * @param animation The animator associated with the properties that need to be + * set. This animator holds the animation fraction which we will use to calculate + * the current value of each property. + */ + @Override + public void onAnimationUpdate(ValueAnimator animation) { + // alpha requires slightly different treatment than the other (transform) properties. + // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation + // logic is dependent on how the view handles an internal call to onSetAlpha(). + // We track what kinds of properties are set, and how alpha is handled when it is + // set, and perform the invalidation steps appropriately. + //boolean alphaHandled = false; + //mView.invalidateParentCaches(); + float fraction = animation.getAnimatedFraction(); + PropertyBundle propertyBundle = mAnimatorMap.get(animation); + int propertyMask = propertyBundle.mPropertyMask; + if ((propertyMask & TRANSFORM_MASK) != 0) { + View v = mView.get(); + if (v != null) { + v.invalidate(/*false*/); + } + } + ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder; + if (valueList != null) { + int count = valueList.size(); + for (int i = 0; i < count; ++i) { + NameValuesHolder values = valueList.get(i); + float value = values.mFromValue + fraction * values.mDeltaValue; + //if (values.mNameConstant == ALPHA) { + // alphaHandled = mView.setAlphaNoInvalidation(value); + //} else { + setValue(values.mNameConstant, value); + //} + } + } + /*if ((propertyMask & TRANSFORM_MASK) != 0) { + mView.mTransformationInfo.mMatrixDirty = true; + mView.mPrivateFlags |= View.DRAWN; // force another invalidation + }*/ + // invalidate(false) in all cases except if alphaHandled gets set to true + // via the call to setAlphaNoInvalidation(), above + View v = mView.get(); + if (v != null) { + v.invalidate(/*alphaHandled*/); + } + } + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorICS.java @@ -0,0 +1,298 @@ +package com.nineoldandroids.view; + +import java.lang.ref.WeakReference; + +import android.view.View; +import android.view.animation.Interpolator; +import com.nineoldandroids.animation.Animator.AnimatorListener; + +class ViewPropertyAnimatorICS extends ViewPropertyAnimator { + /** + * A value to be returned when the WeakReference holding the native implementation + * returns <code>null</code> + */ + private final static long RETURN_WHEN_NULL = -1L; + + /** + * A WeakReference holding the native implementation of ViewPropertyAnimator + */ + private final WeakReference<android.view.ViewPropertyAnimator> mNative; + + ViewPropertyAnimatorICS(View view) { + mNative = new WeakReference<android.view.ViewPropertyAnimator>(view.animate()); + } + + @Override + public ViewPropertyAnimator setDuration(long duration) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.setDuration(duration); + } + return this; + } + + @Override + public long getDuration() { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + return n.getDuration(); + } + return RETURN_WHEN_NULL; + } + + @Override + public ViewPropertyAnimator setStartDelay(long startDelay) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.setStartDelay(startDelay); + } + return this; + } + + @Override + public long getStartDelay() { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + return n.getStartDelay(); + } + return RETURN_WHEN_NULL; + } + + @Override + public ViewPropertyAnimator setInterpolator(Interpolator interpolator) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.setInterpolator(interpolator); + } + return this; + } + + @Override + public ViewPropertyAnimator setListener(final AnimatorListener listener) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + if (listener == null) { + n.setListener(null); + } else { + n.setListener(new android.animation.Animator.AnimatorListener() { + @Override + public void onAnimationStart(android.animation.Animator animation) { + listener.onAnimationStart(null); + } + + @Override + public void onAnimationRepeat(android.animation.Animator animation) { + listener.onAnimationRepeat(null); + } + + @Override + public void onAnimationEnd(android.animation.Animator animation) { + listener.onAnimationEnd(null); + } + + @Override + public void onAnimationCancel(android.animation.Animator animation) { + listener.onAnimationCancel(null); + } + }); + } + } + return this; + } + + @Override + public void start() { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.start(); + } + } + + @Override + public void cancel() { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.cancel(); + } + } + + @Override + public ViewPropertyAnimator x(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.x(value); + } + return this; + } + + @Override + public ViewPropertyAnimator xBy(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.xBy(value); + } + return this; + } + + @Override + public ViewPropertyAnimator y(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.y(value); + } + return this; + } + + @Override + public ViewPropertyAnimator yBy(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.yBy(value); + } + return this; + } + + @Override + public ViewPropertyAnimator rotation(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.rotation(value); + } + return this; + } + + @Override + public ViewPropertyAnimator rotationBy(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.rotationBy(value); + } + return this; + } + + @Override + public ViewPropertyAnimator rotationX(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.rotationX(value); + } + return this; + } + + @Override + public ViewPropertyAnimator rotationXBy(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.rotationXBy(value); + } + return this; + } + + @Override + public ViewPropertyAnimator rotationY(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.rotationY(value); + } + return this; + } + + @Override + public ViewPropertyAnimator rotationYBy(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.rotationYBy(value); + } + return this; + } + + @Override + public ViewPropertyAnimator translationX(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.translationX(value); + } + return this; + } + + @Override + public ViewPropertyAnimator translationXBy(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.translationXBy(value); + } + return this; + } + + @Override + public ViewPropertyAnimator translationY(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.translationY(value); + } + return this; + } + + @Override + public ViewPropertyAnimator translationYBy(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.translationYBy(value); + } + return this; + } + + @Override + public ViewPropertyAnimator scaleX(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.scaleX(value); + } + return this; + } + + @Override + public ViewPropertyAnimator scaleXBy(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.scaleXBy(value); + } + return this; + } + + @Override + public ViewPropertyAnimator scaleY(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.scaleY(value); + } + return this; + } + + @Override + public ViewPropertyAnimator scaleYBy(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.scaleYBy(value); + } + return this; + } + + @Override + public ViewPropertyAnimator alpha(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.alpha(value); + } + return this; + } + + @Override + public ViewPropertyAnimator alphaBy(float value) { + android.view.ViewPropertyAnimator n = mNative.get(); + if (n != null) { + n.alphaBy(value); + } + return this; + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorPreHC.java @@ -0,0 +1,724 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nineoldandroids.view; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Set; +import android.view.View; +import android.view.animation.Interpolator; +import com.nineoldandroids.animation.Animator; +import com.nineoldandroids.animation.ValueAnimator; +import com.nineoldandroids.view.animation.AnimatorProxy; + +class ViewPropertyAnimatorPreHC extends ViewPropertyAnimator { + /** + * Proxy animation class which will allow us access to post-Honeycomb properties that were not + * otherwise available. + */ + private final AnimatorProxy mProxy; + + /** + * A WeakReference holding the View whose properties are being animated by this class. This is + * set at construction time. + */ + private final WeakReference<View> mView; + + /** + * The duration of the underlying Animator object. By default, we don't set the duration + * on the Animator and just use its default duration. If the duration is ever set on this + * Animator, then we use the duration that it was set to. + */ + private long mDuration; + + /** + * A flag indicating whether the duration has been set on this object. If not, we don't set + * the duration on the underlying Animator, but instead just use its default duration. + */ + private boolean mDurationSet = false; + + /** + * The startDelay of the underlying Animator object. By default, we don't set the startDelay + * on the Animator and just use its default startDelay. If the startDelay is ever set on this + * Animator, then we use the startDelay that it was set to. + */ + private long mStartDelay = 0; + + /** + * A flag indicating whether the startDelay has been set on this object. If not, we don't set + * the startDelay on the underlying Animator, but instead just use its default startDelay. + */ + private boolean mStartDelaySet = false; + + /** + * The interpolator of the underlying Animator object. By default, we don't set the interpolator + * on the Animator and just use its default interpolator. If the interpolator is ever set on + * this Animator, then we use the interpolator that it was set to. + */ + private /*Time*/Interpolator mInterpolator; + + /** + * A flag indicating whether the interpolator has been set on this object. If not, we don't set + * the interpolator on the underlying Animator, but instead just use its default interpolator. + */ + private boolean mInterpolatorSet = false; + + /** + * Listener for the lifecycle events of the underlying + */ + private Animator.AnimatorListener mListener = null; + + /** + * This listener is the mechanism by which the underlying Animator causes changes to the + * properties currently being animated, as well as the cleanup after an animation is + * complete. + */ + private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener(); + + /** + * This list holds the properties that have been asked to animate. We allow the caller to + * request several animations prior to actually starting the underlying animator. This + * enables us to run one single animator to handle several properties in parallel. Each + * property is tossed onto the pending list until the animation actually starts (which is + * done by posting it onto mView), at which time the pending list is cleared and the properties + * on that list are added to the list of properties associated with that animator. + */ + ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>(); + + /** + * Constants used to associate a property being requested and the mechanism used to set + * the property (this class calls directly into View to set the properties in question). + */ + private static final int NONE = 0x0000; + private static final int TRANSLATION_X = 0x0001; + private static final int TRANSLATION_Y = 0x0002; + private static final int SCALE_X = 0x0004; + private static final int SCALE_Y = 0x0008; + private static final int ROTATION = 0x0010; + private static final int ROTATION_X = 0x0020; + private static final int ROTATION_Y = 0x0040; + private static final int X = 0x0080; + private static final int Y = 0x0100; + private static final int ALPHA = 0x0200; + + private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | SCALE_X | SCALE_Y | + ROTATION | ROTATION_X | ROTATION_Y | X | Y; + + /** + * The mechanism by which the user can request several properties that are then animated + * together works by posting this Runnable to start the underlying Animator. Every time + * a property animation is requested, we cancel any previous postings of the Runnable + * and re-post it. This means that we will only ever run the Runnable (and thus start the + * underlying animator) after the caller is done setting the properties that should be + * animated together. + */ + private Runnable mAnimationStarter = new Runnable() { + @Override + public void run() { + startAnimation(); + } + }; + + /** + * This class holds information about the overall animation being run on the set of + * properties. The mask describes which properties are being animated and the + * values holder is the list of all property/value objects. + */ + private static class PropertyBundle { + int mPropertyMask; + ArrayList<NameValuesHolder> mNameValuesHolder; + + PropertyBundle(int propertyMask, ArrayList<NameValuesHolder> nameValuesHolder) { + mPropertyMask = propertyMask; + mNameValuesHolder = nameValuesHolder; + } + + /** + * Removes the given property from being animated as a part of this + * PropertyBundle. If the property was a part of this bundle, it returns + * true to indicate that it was, in fact, canceled. This is an indication + * to the caller that a cancellation actually occurred. + * + * @param propertyConstant The property whose cancellation is requested. + * @return true if the given property is a part of this bundle and if it + * has therefore been canceled. + */ + boolean cancel(int propertyConstant) { + if ((mPropertyMask & propertyConstant) != 0 && mNameValuesHolder != null) { + int count = mNameValuesHolder.size(); + for (int i = 0; i < count; ++i) { + NameValuesHolder nameValuesHolder = mNameValuesHolder.get(i); + if (nameValuesHolder.mNameConstant == propertyConstant) { + mNameValuesHolder.remove(i); + mPropertyMask &= ~propertyConstant; + return true; + } + } + } + return false; + } + } + + /** + * This list tracks the list of properties being animated by any particular animator. + * In most situations, there would only ever be one animator running at a time. But it is + * possible to request some properties to animate together, then while those properties + * are animating, to request some other properties to animate together. The way that + * works is by having this map associate the group of properties being animated with the + * animator handling the animation. On every update event for an Animator, we ask the + * map for the associated properties and set them accordingly. + */ + private HashMap<Animator, PropertyBundle> mAnimatorMap = + new HashMap<Animator, PropertyBundle>(); + + /** + * This is the information we need to set each property during the animation. + * mNameConstant is used to set the appropriate field in View, and the from/delta + * values are used to calculate the animated value for a given animation fraction + * during the animation. + */ + private static class NameValuesHolder { + int mNameConstant; + float mFromValue; + float mDeltaValue; + NameValuesHolder(int nameConstant, float fromValue, float deltaValue) { + mNameConstant = nameConstant; + mFromValue = fromValue; + mDeltaValue = deltaValue; + } + } + + /** + * Constructor, called by View. This is private by design, as the user should only + * get a ViewPropertyAnimator by calling View.animate(). + * + * @param view The View associated with this ViewPropertyAnimator + */ + ViewPropertyAnimatorPreHC(View view) { + mView = new WeakReference<View>(view); + mProxy = AnimatorProxy.wrap(view); + } + + /** + * Sets the duration for the underlying animator that animates the requested properties. + * By default, the animator uses the default value for ValueAnimator. Calling this method + * will cause the declared value to be used instead. + * @param duration The length of ensuing property animations, in milliseconds. The value + * cannot be negative. + * @return This object, allowing calls to methods in this class to be chained. + */ + public ViewPropertyAnimator setDuration(long duration) { + if (duration < 0) { + throw new IllegalArgumentException("Animators cannot have negative duration: " + + duration); + } + mDurationSet = true; + mDuration = duration; + return this; + } + + /** + * Returns the current duration of property animations. If the duration was set on this + * object, that value is returned. Otherwise, the default value of the underlying Animator + * is returned. + * + * @see #setDuration(long) + * @return The duration of animations, in milliseconds. + */ + public long getDuration() { + if (mDurationSet) { + return mDuration; + } else { + // Just return the default from ValueAnimator, since that's what we'd get if + // the value has not been set otherwise + return new ValueAnimator().getDuration(); + } + } + + @Override + public long getStartDelay() { + if (mStartDelaySet) { + return mStartDelay; + } else { + // Just return the default from ValueAnimator (0), since that's what we'd get if + // the value has not been set otherwise + return 0; + } + } + + @Override + public ViewPropertyAnimator setStartDelay(long startDelay) { + if (startDelay < 0) { + throw new IllegalArgumentException("Animators cannot have negative duration: " + + startDelay); + } + mStartDelaySet = true; + mStartDelay = startDelay; + return this; + } + + @Override + public ViewPropertyAnimator setInterpolator(/*Time*/Interpolator interpolator) { + mInterpolatorSet = true; + mInterpolator = interpolator; + return this; + } + + @Override + public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) { + mListener = listener; + return this; + } + + @Override + public void start() { + startAnimation(); + } + + @Override + public void cancel() { + if (mAnimatorMap.size() > 0) { + HashMap<Animator, PropertyBundle> mAnimatorMapCopy = + (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone(); + Set<Animator> animatorSet = mAnimatorMapCopy.keySet(); + for (Animator runningAnim : animatorSet) { + runningAnim.cancel(); + } + } + mPendingAnimations.clear(); + View v = mView.get(); + if (v != null) { + v.removeCallbacks(mAnimationStarter); + } + } + + @Override + public ViewPropertyAnimator x(float value) { + animateProperty(X, value); + return this; + } + + @Override + public ViewPropertyAnimator xBy(float value) { + animatePropertyBy(X, value); + return this; + } + + @Override + public ViewPropertyAnimator y(float value) { + animateProperty(Y, value); + return this; + } + + @Override + public ViewPropertyAnimator yBy(float value) { + animatePropertyBy(Y, value); + return this; + } + + @Override + public ViewPropertyAnimator rotation(float value) { + animateProperty(ROTATION, value); + return this; + } + + @Override + public ViewPropertyAnimator rotationBy(float value) { + animatePropertyBy(ROTATION, value); + return this; + } + + @Override + public ViewPropertyAnimator rotationX(float value) { + animateProperty(ROTATION_X, value); + return this; + } + + @Override + public ViewPropertyAnimator rotationXBy(float value) { + animatePropertyBy(ROTATION_X, value); + return this; + } + + @Override + public ViewPropertyAnimator rotationY(float value) { + animateProperty(ROTATION_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator rotationYBy(float value) { + animatePropertyBy(ROTATION_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator translationX(float value) { + animateProperty(TRANSLATION_X, value); + return this; + } + + @Override + public ViewPropertyAnimator translationXBy(float value) { + animatePropertyBy(TRANSLATION_X, value); + return this; + } + + @Override + public ViewPropertyAnimator translationY(float value) { + animateProperty(TRANSLATION_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator translationYBy(float value) { + animatePropertyBy(TRANSLATION_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator scaleX(float value) { + animateProperty(SCALE_X, value); + return this; + } + + @Override + public ViewPropertyAnimator scaleXBy(float value) { + animatePropertyBy(SCALE_X, value); + return this; + } + + @Override + public ViewPropertyAnimator scaleY(float value) { + animateProperty(SCALE_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator scaleYBy(float value) { + animatePropertyBy(SCALE_Y, value); + return this; + } + + @Override + public ViewPropertyAnimator alpha(float value) { + animateProperty(ALPHA, value); + return this; + } + + @Override + public ViewPropertyAnimator alphaBy(float value) { + animatePropertyBy(ALPHA, value); + return this; + } + + /** + * Starts the underlying Animator for a set of properties. We use a single animator that + * simply runs from 0 to 1, and then use that fractional value to set each property + * value accordingly. + */ + private void startAnimation() { + ValueAnimator animator = ValueAnimator.ofFloat(1.0f); + ArrayList<NameValuesHolder> nameValueList = + (ArrayList<NameValuesHolder>) mPendingAnimations.clone(); + mPendingAnimations.clear(); + int propertyMask = 0; + int propertyCount = nameValueList.size(); + for (int i = 0; i < propertyCount; ++i) { + NameValuesHolder nameValuesHolder = nameValueList.get(i); + propertyMask |= nameValuesHolder.mNameConstant; + } + mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList)); + animator.addUpdateListener(mAnimatorEventListener); + animator.addListener(mAnimatorEventListener); + if (mStartDelaySet) { + animator.setStartDelay(mStartDelay); + } + if (mDurationSet) { + animator.setDuration(mDuration); + } + if (mInterpolatorSet) { + animator.setInterpolator(mInterpolator); + } + animator.start(); + } + + /** + * Utility function, called by the various x(), y(), etc. methods. This stores the + * constant name for the property along with the from/delta values that will be used to + * calculate and set the property during the animation. This structure is added to the + * pending animations, awaiting the eventual start() of the underlying animator. A + * Runnable is posted to start the animation, and any pending such Runnable is canceled + * (which enables us to end up starting just one animator for all of the properties + * specified at one time). + * + * @param constantName The specifier for the property being animated + * @param toValue The value to which the property will animate + */ + private void animateProperty(int constantName, float toValue) { + float fromValue = getValue(constantName); + float deltaValue = toValue - fromValue; + animatePropertyBy(constantName, fromValue, deltaValue); + } + + /** + * Utility function, called by the various xBy(), yBy(), etc. methods. This method is + * just like animateProperty(), except the value is an offset from the property's + * current value, instead of an absolute "to" value. + * + * @param constantName The specifier for the property being animated + * @param byValue The amount by which the property will change + */ + private void animatePropertyBy(int constantName, float byValue) { + float fromValue = getValue(constantName); + animatePropertyBy(constantName, fromValue, byValue); + } + + /** + * Utility function, called by animateProperty() and animatePropertyBy(), which handles the + * details of adding a pending animation and posting the request to start the animation. + * + * @param constantName The specifier for the property being animated + * @param startValue The starting value of the property + * @param byValue The amount by which the property will change + */ + private void animatePropertyBy(int constantName, float startValue, float byValue) { + // First, cancel any existing animations on this property + if (mAnimatorMap.size() > 0) { + Animator animatorToCancel = null; + Set<Animator> animatorSet = mAnimatorMap.keySet(); + for (Animator runningAnim : animatorSet) { + PropertyBundle bundle = mAnimatorMap.get(runningAnim); + if (bundle.cancel(constantName)) { + // property was canceled - cancel the animation if it's now empty + // Note that it's safe to break out here because every new animation + // on a property will cancel a previous animation on that property, so + // there can only ever be one such animation running. + if (bundle.mPropertyMask == NONE) { + // the animation is no longer changing anything - cancel it + animatorToCancel = runningAnim; + break; + } + } + } + if (animatorToCancel != null) { + animatorToCancel.cancel(); + } + } + + NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue); + mPendingAnimations.add(nameValuePair); + View v = mView.get(); + if (v != null) { + v.removeCallbacks(mAnimationStarter); + v.post(mAnimationStarter); + } + } + + /** + * This method handles setting the property values directly in the View object's fields. + * propertyConstant tells it which property should be set, value is the value to set + * the property to. + * + * @param propertyConstant The property to be set + * @param value The value to set the property to + */ + private void setValue(int propertyConstant, float value) { + //final View.TransformationInfo info = mView.mTransformationInfo; + switch (propertyConstant) { + case TRANSLATION_X: + //info.mTranslationX = value; + mProxy.setTranslationX(value); + break; + case TRANSLATION_Y: + //info.mTranslationY = value; + mProxy.setTranslationY(value); + break; + case ROTATION: + //info.mRotation = value; + mProxy.setRotation(value); + break; + case ROTATION_X: + //info.mRotationX = value; + mProxy.setRotationX(value); + break; + case ROTATION_Y: + //info.mRotationY = value; + mProxy.setRotationY(value); + break; + case SCALE_X: + //info.mScaleX = value; + mProxy.setScaleX(value); + break; + case SCALE_Y: + //info.mScaleY = value; + mProxy.setScaleY(value); + break; + case X: + //info.mTranslationX = value - mView.mLeft; + mProxy.setX(value); + break; + case Y: + //info.mTranslationY = value - mView.mTop; + mProxy.setY(value); + break; + case ALPHA: + //info.mAlpha = value; + mProxy.setAlpha(value); + break; + } + } + + /** + * This method gets the value of the named property from the View object. + * + * @param propertyConstant The property whose value should be returned + * @return float The value of the named property + */ + private float getValue(int propertyConstant) { + //final View.TransformationInfo info = mView.mTransformationInfo; + switch (propertyConstant) { + case TRANSLATION_X: + //return info.mTranslationX; + return mProxy.getTranslationX(); + case TRANSLATION_Y: + //return info.mTranslationY; + return mProxy.getTranslationY(); + case ROTATION: + //return info.mRotation; + return mProxy.getRotation(); + case ROTATION_X: + //return info.mRotationX; + return mProxy.getRotationX(); + case ROTATION_Y: + //return info.mRotationY; + return mProxy.getRotationY(); + case SCALE_X: + //return info.mScaleX; + return mProxy.getScaleX(); + case SCALE_Y: + //return info.mScaleY; + return mProxy.getScaleY(); + case X: + //return mView.mLeft + info.mTranslationX; + return mProxy.getX(); + case Y: + //return mView.mTop + info.mTranslationY; + return mProxy.getY(); + case ALPHA: + //return info.mAlpha; + return mProxy.getAlpha(); + } + return 0; + } + + /** + * Utility class that handles the various Animator events. The only ones we care + * about are the end event (which we use to clean up the animator map when an animator + * finishes) and the update event (which we use to calculate the current value of each + * property and then set it on the view object). + */ + private class AnimatorEventListener + implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { + @Override + public void onAnimationStart(Animator animation) { + if (mListener != null) { + mListener.onAnimationStart(animation); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (mListener != null) { + mListener.onAnimationCancel(animation); + } + } + + @Override + public void onAnimationRepeat(Animator animation) { + if (mListener != null) { + mListener.onAnimationRepeat(animation); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mListener != null) { + mListener.onAnimationEnd(animation); + } + mAnimatorMap.remove(animation); + // If the map is empty, it means all animation are done or canceled, so the listener + // isn't needed anymore. Not nulling it would cause it to leak any objects used in + // its implementation + if (mAnimatorMap.isEmpty()) { + mListener = null; + } + } + + /** + * Calculate the current value for each property and set it on the view. Invalidate + * the view object appropriately, depending on which properties are being animated. + * + * @param animation The animator associated with the properties that need to be + * set. This animator holds the animation fraction which we will use to calculate + * the current value of each property. + */ + @Override + public void onAnimationUpdate(ValueAnimator animation) { + // alpha requires slightly different treatment than the other (transform) properties. + // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation + // logic is dependent on how the view handles an internal call to onSetAlpha(). + // We track what kinds of properties are set, and how alpha is handled when it is + // set, and perform the invalidation steps appropriately. + //boolean alphaHandled = false; + //mView.invalidateParentCaches(); + float fraction = animation.getAnimatedFraction(); + PropertyBundle propertyBundle = mAnimatorMap.get(animation); + int propertyMask = propertyBundle.mPropertyMask; + if ((propertyMask & TRANSFORM_MASK) != 0) { + View v = mView.get(); + if (v != null) { + v.invalidate(/*false*/); + } + } + ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder; + if (valueList != null) { + int count = valueList.size(); + for (int i = 0; i < count; ++i) { + NameValuesHolder values = valueList.get(i); + float value = values.mFromValue + fraction * values.mDeltaValue; + //if (values.mNameConstant == ALPHA) { + // alphaHandled = mView.setAlphaNoInvalidation(value); + //} else { + setValue(values.mNameConstant, value); + //} + } + } + /*if ((propertyMask & TRANSFORM_MASK) != 0) { + mView.mTransformationInfo.mMatrixDirty = true; + mView.mPrivateFlags |= View.DRAWN; // force another invalidation + }*/ + // invalidate(false) in all cases except if alphaHandled gets set to true + // via the call to setAlphaNoInvalidation(), above + View v = mView.get(); + if (v != null) { + v.invalidate(/*alphaHandled*/); + } + } + } +}
new file mode 100755 --- /dev/null +++ b/mobile/android/thirdparty/com/nineoldandroids/view/animation/AnimatorProxy.java @@ -0,0 +1,322 @@ +package com.nineoldandroids.view.animation; + +import android.graphics.Camera; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.os.Build; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +import java.lang.ref.WeakReference; +import java.util.WeakHashMap; + +/** + * A proxy class to allow for modifying post-3.0 view properties on all pre-3.0 + * platforms. <strong>DO NOT</strong> wrap your views with this class if you + * are using {@code ObjectAnimator} as it will handle that itself. + */ +public final class AnimatorProxy extends Animation { + /** Whether or not the current running platform needs to be proxied. */ + public static final boolean NEEDS_PROXY = Integer.valueOf(Build.VERSION.SDK).intValue() < Build.VERSION_CODES.HONEYCOMB; + + private static final WeakHashMap<View, AnimatorProxy> PROXIES = + new WeakHashMap<View, AnimatorProxy>(); + + /** + * Create a proxy to allow for modifying post-3.0 view properties on all + * pre-3.0 platforms. <strong>DO NOT</strong> wrap your views if you are + * using {@code ObjectAnimator} as it will handle that itself. + * + * @param view View to wrap. + * @return Proxy to post-3.0 properties. + */ + public static AnimatorProxy wrap(View view) { + AnimatorProxy proxy = PROXIES.get(view); + // This checks if the proxy already exists and whether it still is the animation of the given view + if (proxy == null || proxy != view.getAnimation()) { + proxy = new AnimatorProxy(view); + PROXIES.put(view, proxy); + } + return proxy; + } + + private final WeakReference<View> mView; + private final Camera mCamera = new Camera(); + private boolean mHasPivot; + + private float mAlpha = 1; + private float mPivotX; + private float mPivotY; + private float mRotationX; + private float mRotationY; + private float mRotationZ; + private float mScaleX = 1; + private float mScaleY = 1; + private float mTranslationX; + private float mTranslationY; + + private final RectF mBefore = new RectF(); + private final RectF mAfter = new RectF(); + private final Matrix mTempMatrix = new Matrix(); + + private AnimatorProxy(View view) { + setDuration(0); //perform transformation immediately + setFillAfter(true); //persist transformation beyond duration + view.setAnimation(this); + mView = new WeakReference<View>(view); + } + + public float getAlpha() { + return mAlpha; + } + public void setAlpha(float alpha) { + if (mAlpha != alpha) { + mAlpha = alpha; + View view = mView.get(); + if (view != null) { + view.invalidate(); + } + } + } + public float getPivotX() { + return mPivotX; + } + public void setPivotX(float pivotX) { + if (!mHasPivot || mPivotX != pivotX) { + prepareForUpdate(); + mHasPivot = true; + mPivotX = pivotX; + invalidateAfterUpdate(); + } + } + public float getPivotY() { + return mPivotY; + } + public void setPivotY(float pivotY) { + if (!mHasPivot || mPivotY != pivotY) { + prepareForUpdate(); + mHasPivot = true; + mPivotY = pivotY; + invalidateAfterUpdate(); + } + } + public float getRotation() { + return mRotationZ; + } + public void setRotation(float rotation) { + if (mRotationZ != rotation) { + prepareForUpdate(); + mRotationZ = rotation; + invalidateAfterUpdate(); + } + } + public float getRotationX() { + return mRotationX; + } + public void setRotationX(float rotationX) { + if (mRotationX != rotationX) { + prepareForUpdate(); + mRotationX = rotationX; + invalidateAfterUpdate(); + } + } + public float getRotationY() { + return mRotationY; + } + + public void setRotationY(float rotationY) { + if (mRotationY != rotationY) { + prepareForUpdate(); + mRotationY = rotationY; + invalidateAfterUpdate(); + } + } + public float getScaleX() { + return mScaleX; + } + public void setScaleX(float scaleX) { + if (mScaleX != scaleX) {