summaryrefslogtreecommitdiffstats
path: root/core/java/android/animation
diff options
context:
space:
mode:
authorYigit Boyar <yboyar@google.com>2014-09-25 12:23:35 -0700
committerYigit Boyar <yboyar@google.com>2014-10-23 11:46:01 -0700
commitd422dc358f0100106dc07d7b903201eb9b043b11 (patch)
tree9db96b09fc54dcd7129e17f64ce1ad2902d89fdf /core/java/android/animation
parentd0aae5a2226c620c547690039fc06dfdf20ea35d (diff)
downloadframeworks_base-d422dc358f0100106dc07d7b903201eb9b043b11.zip
frameworks_base-d422dc358f0100106dc07d7b903201eb9b043b11.tar.gz
frameworks_base-d422dc358f0100106dc07d7b903201eb9b043b11.tar.bz2
Added a generic configuration and theme based cache
For now, only animators use it but we can consider migrating drawable cache to it as well. Bug: 17456416 Change-Id: I571b96856805edb171f0fc52e6bff5a365f46b70
Diffstat (limited to 'core/java/android/animation')
-rw-r--r--core/java/android/animation/Animator.java115
-rw-r--r--core/java/android/animation/AnimatorInflater.java90
-rw-r--r--core/java/android/animation/AnimatorSet.java78
-rw-r--r--core/java/android/animation/FloatKeyframeSet.java5
-rw-r--r--core/java/android/animation/IntKeyframeSet.java3
-rw-r--r--core/java/android/animation/KeyframeSet.java18
-rw-r--r--core/java/android/animation/Keyframes.java3
-rw-r--r--core/java/android/animation/PropertyValuesHolder.java9
-rw-r--r--core/java/android/animation/StateListAnimator.java148
-rw-r--r--core/java/android/animation/ValueAnimator.java8
10 files changed, 388 insertions, 89 deletions
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 3720c81..da48709 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -16,6 +16,8 @@
package android.animation;
+import android.content.res.ConstantState;
+
import java.util.ArrayList;
/**
@@ -41,6 +43,18 @@ public abstract class Animator implements Cloneable {
boolean mPaused = false;
/**
+ * A set of flags which identify the type of configuration changes that can affect this
+ * Animator. Used by the Animator cache.
+ */
+ int mChangingConfigurations = 0;
+
+ /**
+ * If this animator is inflated from a constant state, keep a reference to it so that
+ * ConstantState will not be garbage collected until this animator is collected
+ */
+ private AnimatorConstantState mConstantState;
+
+ /**
* 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
@@ -295,25 +309,71 @@ public abstract class Animator implements Cloneable {
}
}
+ /**
+ * Return a mask of the configuration parameters for which this animator may change, requiring
+ * that it should be re-created from Resources. The default implementation returns whatever
+ * value was provided through setChangingConfigurations(int) or 0 by default.
+ *
+ * @return Returns a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}.
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Set a mask of the configuration parameters for which this animator may change, requiring
+ * that it be re-created from resource.
+ *
+ * @param configs A mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public void setChangingConfigurations(int configs) {
+ mChangingConfigurations = configs;
+ }
+
+ /**
+ * Sets the changing configurations value to the union of the current changing configurations
+ * and the provided configs.
+ * This method is called while loading the animator.
+ * @hide
+ */
+ public void appendChangingConfigurations(int configs) {
+ mChangingConfigurations |= configs;
+ }
+
+ /**
+ * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
+ * this Animator.
+ * <p>
+ * This constant state is used to create new instances of this animator when needed, instead
+ * of re-loading it from resources. Default implementation creates a new
+ * {@link AnimatorConstantState}. You can override this method to provide your custom logic or
+ * return null if you don't want this animator to be cached.
+ *
+ * @return The ConfigurationBoundResourceCache.BaseConstantState associated to this Animator.
+ * @see android.content.res.ConstantState
+ * @see #clone()
+ * @hide
+ */
+ public ConstantState<Animator> createConstantState() {
+ return new AnimatorConstantState(this);
+ }
+
@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));
- }
+ anim.mListeners = new ArrayList<AnimatorListener>(mListeners);
}
if (mPauseListeners != null) {
- ArrayList<AnimatorPauseListener> oldListeners = mPauseListeners;
- anim.mPauseListeners = new ArrayList<AnimatorPauseListener>();
- int numListeners = oldListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- anim.mPauseListeners.add(oldListeners.get(i));
- }
+ anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners);
}
return anim;
} catch (CloneNotSupportedException e) {
@@ -469,4 +529,35 @@ public abstract class Animator implements Cloneable {
public void setAllowRunningAsynchronously(boolean mayRunAsync) {
// It is up to subclasses to support this, if they can.
}
+
+ /**
+ * Creates a {@link ConstantState} which holds changing configurations information associated
+ * with the given Animator.
+ * <p>
+ * When {@link #newInstance()} is called, default implementation clones the Animator.
+ */
+ private static class AnimatorConstantState extends ConstantState<Animator> {
+
+ final Animator mAnimator;
+ int mChangingConf;
+
+ public AnimatorConstantState(Animator animator) {
+ mAnimator = animator;
+ // ensure a reference back to here so that constante state is not gc'ed.
+ mAnimator.mConstantState = this;
+ mChangingConf = mAnimator.getChangingConfigurations();
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public Animator newInstance() {
+ final Animator clone = mAnimator.clone();
+ clone.mConstantState = this;
+ return clone;
+ }
+ }
}
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 25417ed..688d7e4 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -16,6 +16,8 @@
package android.animation;
import android.content.Context;
+import android.content.res.ConfigurationBoundResourceCache;
+import android.content.res.ConstantState;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
@@ -30,6 +32,8 @@ import android.util.TypedValue;
import android.util.Xml;
import android.view.InflateException;
import android.view.animation.AnimationUtils;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.Interpolator;
import com.android.internal.R;
@@ -67,6 +71,9 @@ public class AnimatorInflater {
private static final boolean DBG_ANIMATOR_INFLATER = false;
+ // used to calculate changing configs for resource references
+ private static final TypedValue sTmpTypedValue = new TypedValue();
+
/**
* Loads an {@link Animator} object from a resource
*
@@ -98,11 +105,34 @@ public class AnimatorInflater {
/** @hide */
public static Animator loadAnimator(Resources resources, Theme theme, int id,
float pathErrorScale) throws NotFoundException {
-
+ final ConfigurationBoundResourceCache<Animator> animatorCache = resources
+ .getAnimatorCache();
+ Animator animator = animatorCache.get(id, theme);
+ if (animator != null) {
+ if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id));
+ }
+ return animator;
+ } else if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "cache miss for animator " + resources.getResourceName(id));
+ }
XmlResourceParser parser = null;
try {
parser = resources.getAnimation(id);
- return createAnimatorFromXml(resources, theme, parser, pathErrorScale);
+ animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
+ if (animator != null) {
+ animator.appendChangingConfigurations(getChangingConfigs(resources, id));
+ final ConstantState<Animator> constantState = animator.createConstantState();
+ if (constantState != null) {
+ if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "caching animator for res " + resources.getResourceName(id));
+ }
+ animatorCache.put(id, theme, constantState);
+ // create a new animator so that cached version is never used by the user
+ animator = constantState.newInstance(resources, theme);
+ }
+ }
+ return animator;
} catch (XmlPullParserException ex) {
Resources.NotFoundException rnf =
new Resources.NotFoundException("Can't load animation resource ID #0x" +
@@ -122,10 +152,29 @@ public class AnimatorInflater {
public static StateListAnimator loadStateListAnimator(Context context, int id)
throws NotFoundException {
+ final Resources resources = context.getResources();
+ final ConfigurationBoundResourceCache<StateListAnimator> cache = resources
+ .getStateListAnimatorCache();
+ final Theme theme = context.getTheme();
+ StateListAnimator animator = cache.get(id, theme);
+ if (animator != null) {
+ return animator;
+ }
XmlResourceParser parser = null;
try {
- parser = context.getResources().getAnimation(id);
- return createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
+ parser = resources.getAnimation(id);
+ animator = createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
+ if (animator != null) {
+ animator.appendChangingConfigurations(getChangingConfigs(resources, id));
+ final ConstantState<StateListAnimator> constantState = animator
+ .createConstantState();
+ if (constantState != null) {
+ cache.put(id, theme, constantState);
+ // return a clone so that the animator in constant state is never used.
+ animator = constantState.newInstance(resources, theme);
+ }
+ }
+ return animator;
} catch (XmlPullParserException ex) {
Resources.NotFoundException rnf =
new Resources.NotFoundException(
@@ -172,14 +221,13 @@ public class AnimatorInflater {
for (int i = 0; i < attributeCount; i++) {
int attrName = attributeSet.getAttributeNameResource(i);
if (attrName == R.attr.animation) {
- animator = loadAnimator(context,
- attributeSet.getAttributeResourceValue(i, 0));
+ final int animId = attributeSet.getAttributeResourceValue(i, 0);
+ animator = loadAnimator(context, animId);
} else {
states[stateIndex++] =
attributeSet.getAttributeBooleanValue(i, false) ?
attrName : -attrName;
}
-
}
if (animator == null) {
animator = createAnimatorFromXml(context.getResources(),
@@ -192,7 +240,6 @@ public class AnimatorInflater {
}
stateListAnimator
.addState(StateSet.trimStateSet(states, stateIndex), animator);
-
}
break;
}
@@ -508,7 +555,6 @@ public class AnimatorInflater {
private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize)
throws XmlPullParserException, IOException {
-
Animator anim = null;
ArrayList<Animator> childAnims = null;
@@ -537,8 +583,8 @@ public class AnimatorInflater {
} else {
a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
}
- int ordering = a.getInt(R.styleable.AnimatorSet_ordering,
- TOGETHER);
+ anim.appendChangingConfigurations(a.getChangingConfigurations());
+ int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER);
createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
pixelSize);
a.recycle();
@@ -565,7 +611,6 @@ public class AnimatorInflater {
parent.playSequentially(animsArray);
}
}
-
return anim;
}
@@ -591,7 +636,6 @@ public class AnimatorInflater {
private static ValueAnimator loadAnimator(Resources res, Theme theme,
AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
throws NotFoundException {
-
TypedArray arrayAnimator = null;
TypedArray arrayObjectAnimator = null;
@@ -609,25 +653,37 @@ public class AnimatorInflater {
} else {
arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
}
+ anim.appendChangingConfigurations(arrayObjectAnimator.getChangingConfigurations());
}
if (anim == null) {
anim = new ValueAnimator();
}
+ anim.appendChangingConfigurations(arrayAnimator.getChangingConfigurations());
parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator, pathErrorScale);
- final int resID =
- arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
+ final int resID = arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
if (resID > 0) {
- anim.setInterpolator(AnimationUtils.loadInterpolator(res, theme, resID));
+ final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
+ if (interpolator instanceof BaseInterpolator) {
+ anim.appendChangingConfigurations(
+ ((BaseInterpolator) interpolator).getChangingConfiguration());
+ }
+ anim.setInterpolator(interpolator);
}
arrayAnimator.recycle();
if (arrayObjectAnimator != null) {
arrayObjectAnimator.recycle();
}
-
return anim;
}
+
+ private static int getChangingConfigs(Resources resources, int id) {
+ synchronized (sTmpTypedValue) {
+ resources.getValue(id, sTmpTypedValue, true);
+ return sTmpTypedValue.changingConfigurations;
+ }
+ }
}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 0aa8fdd..92762c3 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -241,6 +241,19 @@ public final class AnimatorSet extends Animator {
}
/**
+ * @hide
+ */
+ @Override
+ public int getChangingConfigurations() {
+ int conf = super.getChangingConfigurations();
+ final int nodeCount = mNodes.size();
+ for (int i = 0; i < nodeCount; i ++) {
+ conf |= mNodes.get(i).animation.getChangingConfigurations();
+ }
+ return conf;
+ }
+
+ /**
* Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations}
* of this AnimatorSet. The default value is null, which means that no interpolator
* is set on this AnimatorSet. Setting the interpolator to any non-null value
@@ -628,23 +641,25 @@ public final class AnimatorSet extends Animator {
* 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.
*/
+ final int nodeCount = mNodes.size();
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>();
+ anim.mNodes = new ArrayList<Node>(nodeCount);
+ anim.mSortedNodes = new ArrayList<Node>(nodeCount);
anim.mReversible = mReversible;
anim.mSetListener = null;
// 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) {
+
+ for (int n = 0; n < nodeCount; n++) {
+ final Node node = mNodes.get(n);
Node nodeClone = node.clone();
- nodeCloneMap.put(node, nodeClone);
+ node.mTmpClone = 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
@@ -652,40 +667,50 @@ public final class AnimatorSet extends Animator {
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();
+ final ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners();
if (cloneListeners != null) {
- ArrayList<AnimatorListener> listenersToRemove = null;
- for (AnimatorListener listener : cloneListeners) {
+ for (int i = cloneListeners.size() - 1; i >= 0; i--) {
+ final AnimatorListener listener = cloneListeners.get(i);
if (listener instanceof AnimatorSetListener) {
- if (listenersToRemove == null) {
- listenersToRemove = new ArrayList<AnimatorListener>();
- }
- listenersToRemove.add(listener);
- }
- }
- if (listenersToRemove != null) {
- for (AnimatorListener listener : listenersToRemove) {
- cloneListeners.remove(listener);
+ cloneListeners.remove(i);
}
}
}
}
// 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);
+ for (int n = 0; n < nodeCount; n++) {
+ final Node node = mNodes.get(n);
+ final Node clone = node.mTmpClone;
if (node.dependencies != null) {
- for (Dependency dependency : node.dependencies) {
- Node clonedDependencyNode = nodeCloneMap.get(dependency.node);
- Dependency cloneDependency = new Dependency(clonedDependencyNode,
+ clone.dependencies = new ArrayList<Dependency>(node.dependencies.size());
+ final int depSize = node.dependencies.size();
+ for (int i = 0; i < depSize; i ++) {
+ final Dependency dependency = node.dependencies.get(i);
+ Dependency cloneDependency = new Dependency(dependency.node.mTmpClone,
dependency.rule);
- nodeClone.addDependency(cloneDependency);
+ clone.dependencies.add(cloneDependency);
+ }
+ }
+ if (node.nodeDependents != null) {
+ clone.nodeDependents = new ArrayList<Node>(node.nodeDependents.size());
+ for (Node dep : node.nodeDependents) {
+ clone.nodeDependents.add(dep.mTmpClone);
+ }
+ }
+ if (node.nodeDependencies != null) {
+ clone.nodeDependencies = new ArrayList<Node>(node.nodeDependencies.size());
+ for (Node dep : node.nodeDependencies) {
+ clone.nodeDependencies.add(dep.mTmpClone);
}
}
}
-
+ for (int n = 0; n < nodeCount; n++) {
+ mNodes.get(n).mTmpClone = null;
+ }
return anim;
}
@@ -1017,6 +1042,11 @@ public final class AnimatorSet extends Animator {
public boolean done = false;
/**
+ * Temporary field to hold the clone in AnimatorSet#clone. Cleaned after clone is complete
+ */
+ private Node mTmpClone = null;
+
+ /**
* Constructs the Node with the animation that it encapsulates. A Node has no
* dependencies by default; dependencies are added via the addDependency()
* method.
diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java
index 12e5862..abac246 100644
--- a/core/java/android/animation/FloatKeyframeSet.java
+++ b/core/java/android/animation/FloatKeyframeSet.java
@@ -19,6 +19,7 @@ package android.animation;
import android.animation.Keyframe.FloatKeyframe;
import java.util.ArrayList;
+import java.util.List;
/**
* This class holds a collection of FloatKeyframe objects and is called by ValueAnimator to calculate
@@ -47,8 +48,8 @@ class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes {
@Override
public FloatKeyframeSet clone() {
- ArrayList<Keyframe> keyframes = mKeyframes;
- int numKeyframes = mKeyframes.size();
+ final List<Keyframe> keyframes = mKeyframes;
+ final int numKeyframes = mKeyframes.size();
FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone();
diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java
index 7a5b0ec..0ec5138 100644
--- a/core/java/android/animation/IntKeyframeSet.java
+++ b/core/java/android/animation/IntKeyframeSet.java
@@ -19,6 +19,7 @@ package android.animation;
import android.animation.Keyframe.IntKeyframe;
import java.util.ArrayList;
+import java.util.List;
/**
* This class holds a collection of IntKeyframe objects and is called by ValueAnimator to calculate
@@ -47,7 +48,7 @@ class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes {
@Override
public IntKeyframeSet clone() {
- ArrayList<Keyframe> keyframes = mKeyframes;
+ List<Keyframe> keyframes = mKeyframes;
int numKeyframes = mKeyframes.size();
IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index 8d15db2..0e99bff 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -18,6 +18,8 @@ package android.animation;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+
import android.animation.Keyframe.IntKeyframe;
import android.animation.Keyframe.FloatKeyframe;
import android.animation.Keyframe.ObjectKeyframe;
@@ -36,16 +38,16 @@ class KeyframeSet implements Keyframes {
Keyframe mFirstKeyframe;
Keyframe mLastKeyframe;
TimeInterpolator mInterpolator; // only used in the 2-keyframe case
- ArrayList<Keyframe> mKeyframes; // only used when there are not 2 keyframes
+ List<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);
+ // immutable list
+ mKeyframes = Arrays.asList(keyframes);
+ mFirstKeyframe = keyframes[0];
+ mLastKeyframe = keyframes[mNumKeyframes - 1];
mInterpolator = mLastKeyframe.getInterpolator();
}
@@ -57,7 +59,7 @@ class KeyframeSet implements Keyframes {
public void invalidateCache() {
}
- public ArrayList<Keyframe> getKeyframes() {
+ public List<Keyframe> getKeyframes() {
return mKeyframes;
}
@@ -177,9 +179,9 @@ class KeyframeSet implements Keyframes {
@Override
public KeyframeSet clone() {
- ArrayList<Keyframe> keyframes = mKeyframes;
+ List<Keyframe> keyframes = mKeyframes;
int numKeyframes = mKeyframes.size();
- Keyframe[] newKeyframes = new Keyframe[numKeyframes];
+ final Keyframe[] newKeyframes = new Keyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
newKeyframes[i] = keyframes.get(i).clone();
}
diff --git a/core/java/android/animation/Keyframes.java b/core/java/android/animation/Keyframes.java
index 6611c6c..c921466 100644
--- a/core/java/android/animation/Keyframes.java
+++ b/core/java/android/animation/Keyframes.java
@@ -16,6 +16,7 @@
package android.animation;
import java.util.ArrayList;
+import java.util.List;
/**
* This interface abstracts a collection of Keyframe objects and is called by
@@ -62,7 +63,7 @@ interface Keyframes extends Cloneable {
* @return A list of all Keyframes contained by this. This may return null if this is
* not made up of Keyframes.
*/
- ArrayList<Keyframe> getKeyframes();
+ List<Keyframe> getKeyframes();
Keyframes clone();
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index d372933..97426c3 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -27,6 +27,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
@@ -791,7 +792,7 @@ public class PropertyValuesHolder implements Cloneable {
// check to make sure that mProperty is on the class of target
try {
Object testValue = null;
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
@@ -814,7 +815,7 @@ public class PropertyValuesHolder implements Cloneable {
if (mSetter == null) {
setupSetter(targetClass);
}
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
@@ -890,7 +891,7 @@ public class PropertyValuesHolder implements Cloneable {
* @param target The object which holds the start values that should be set.
*/
void setupStartValue(Object target) {
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
if (!keyframes.isEmpty()) {
setupValue(target, keyframes.get(0));
}
@@ -905,7 +906,7 @@ public class PropertyValuesHolder implements Cloneable {
* @param target The object which holds the start values that should be set.
*/
void setupEndValue(Object target) {
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
if (!keyframes.isEmpty()) {
setupValue(target, keyframes.get(keyframes.size() - 1));
}
diff --git a/core/java/android/animation/StateListAnimator.java b/core/java/android/animation/StateListAnimator.java
index 7256a06..d49e914 100644
--- a/core/java/android/animation/StateListAnimator.java
+++ b/core/java/android/animation/StateListAnimator.java
@@ -16,6 +16,7 @@
package android.animation;
+import android.content.res.ConstantState;
import android.util.StateSet;
import android.view.View;
@@ -44,25 +45,31 @@ import java.util.ArrayList;
* @attr ref android.R.styleable#DrawableStates_state_pressed
* @attr ref android.R.styleable#StateListAnimatorItem_animation
*/
-public class StateListAnimator {
-
- private final ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
+public class StateListAnimator implements Cloneable {
+ private ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
private Tuple mLastMatch = null;
-
private Animator mRunningAnimator = null;
-
private WeakReference<View> mViewRef;
+ private StateListAnimatorConstantState mConstantState;
+ private AnimatorListenerAdapter mAnimatorListener;
+ private int mChangingConfigurations;
- private AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- animation.setTarget(null);
- if (mRunningAnimator == animation) {
- mRunningAnimator = null;
+ public StateListAnimator() {
+ initAnimatorListener();
+ }
+
+ private void initAnimatorListener() {
+ mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animation.setTarget(null);
+ if (mRunningAnimator == animation) {
+ mRunningAnimator = null;
+ }
}
- }
- };
+ };
+ }
/**
* Associates the given animator with the provided drawable state specs so that it will be run
@@ -75,6 +82,7 @@ public class StateListAnimator {
Tuple tuple = new Tuple(specs, animator);
tuple.mAnimator.addListener(mAnimatorListener);
mTuples.add(tuple);
+ mChangingConfigurations |= animator.getChangingConfigurations();
}
/**
@@ -118,12 +126,35 @@ public class StateListAnimator {
for (int i = 0; i < size; i++) {
mTuples.get(i).mAnimator.setTarget(null);
}
-
mViewRef = null;
mLastMatch = null;
mRunningAnimator = null;
}
+ @Override
+ public StateListAnimator clone() {
+ try {
+ StateListAnimator clone = (StateListAnimator) super.clone();
+ clone.mTuples = new ArrayList<Tuple>(mTuples.size());
+ clone.mLastMatch = null;
+ clone.mRunningAnimator = null;
+ clone.mViewRef = null;
+ clone.mAnimatorListener = null;
+ clone.initAnimatorListener();
+ final int tupleSize = mTuples.size();
+ for (int i = 0; i < tupleSize; i++) {
+ final Tuple tuple = mTuples.get(i);
+ final Animator animatorClone = tuple.mAnimator.clone();
+ animatorClone.removeListener(mAnimatorListener);
+ clone.addState(tuple.mSpecs, animatorClone);
+ }
+ clone.setChangingConfigurations(getChangingConfigurations());
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError("cannot clone state list animator", e);
+ }
+ }
+
/**
* Called by View
* @hide
@@ -182,6 +213,63 @@ public class StateListAnimator {
}
/**
+ * Return a mask of the configuration parameters for which this animator may change, requiring
+ * that it be re-created. The default implementation returns whatever was provided through
+ * {@link #setChangingConfigurations(int)} or 0 by default.
+ *
+ * @return Returns a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Set a mask of the configuration parameters for which this animator may change, requiring
+ * that it should be recreated from resources instead of being cloned.
+ *
+ * @param configs A mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public void setChangingConfigurations(int configs) {
+ mChangingConfigurations = configs;
+ }
+
+ /**
+ * Sets the changing configurations value to the union of the current changing configurations
+ * and the provided configs.
+ * This method is called while loading the animator.
+ * @hide
+ */
+ public void appendChangingConfigurations(int configs) {
+ mChangingConfigurations |= configs;
+ }
+
+ /**
+ * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
+ * this Animator.
+ * <p>
+ * This constant state is used to create new instances of this animator when needed. Default
+ * implementation creates a new {@link StateListAnimatorConstantState}. You can override this
+ * method to provide your custom logic or return null if you don't want this animator to be
+ * cached.
+ *
+ * @return The {@link android.content.res.ConstantState} associated to this Animator.
+ * @see android.content.res.ConstantState
+ * @see #clone()
+ * @hide
+ */
+ public ConstantState<StateListAnimator> createConstantState() {
+ return new StateListAnimatorConstantState(this);
+ }
+
+ /**
* @hide
*/
public static class Tuple {
@@ -209,4 +297,36 @@ public class StateListAnimator {
return mAnimator;
}
}
+
+ /**
+ * Creates a constant state which holds changing configurations information associated with the
+ * given Animator.
+ * <p>
+ * When new instance is called, default implementation clones the Animator.
+ */
+ private static class StateListAnimatorConstantState
+ extends ConstantState<StateListAnimator> {
+
+ final StateListAnimator mAnimator;
+
+ int mChangingConf;
+
+ public StateListAnimatorConstantState(StateListAnimator animator) {
+ mAnimator = animator;
+ mAnimator.mConstantState = this;
+ mChangingConf = mAnimator.getChangingConfigurations();
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public StateListAnimator newInstance() {
+ final StateListAnimator clone = mAnimator.clone();
+ clone.mConstantState = this;
+ return clone;
+ }
+ }
}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 0d17d67..07f79b8 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -16,6 +16,7 @@
package android.animation;
+import android.content.res.ConfigurationBoundResourceCache;
import android.os.Looper;
import android.os.Trace;
import android.util.AndroidRuntimeException;
@@ -1289,12 +1290,7 @@ public class ValueAnimator extends Animator {
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.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(mUpdateListeners);
}
anim.mSeekTime = -1;
anim.mPlayingBackwards = false;