diff options
author | Yigit Boyar <yboyar@google.com> | 2014-09-25 12:23:35 -0700 |
---|---|---|
committer | Yigit Boyar <yboyar@google.com> | 2014-10-23 11:46:01 -0700 |
commit | d422dc358f0100106dc07d7b903201eb9b043b11 (patch) | |
tree | 9db96b09fc54dcd7129e17f64ce1ad2902d89fdf /core/java/android/animation | |
parent | d0aae5a2226c620c547690039fc06dfdf20ea35d (diff) | |
download | frameworks_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.java | 115 | ||||
-rw-r--r-- | core/java/android/animation/AnimatorInflater.java | 90 | ||||
-rw-r--r-- | core/java/android/animation/AnimatorSet.java | 78 | ||||
-rw-r--r-- | core/java/android/animation/FloatKeyframeSet.java | 5 | ||||
-rw-r--r-- | core/java/android/animation/IntKeyframeSet.java | 3 | ||||
-rw-r--r-- | core/java/android/animation/KeyframeSet.java | 18 | ||||
-rw-r--r-- | core/java/android/animation/Keyframes.java | 3 | ||||
-rw-r--r-- | core/java/android/animation/PropertyValuesHolder.java | 9 | ||||
-rw-r--r-- | core/java/android/animation/StateListAnimator.java | 148 | ||||
-rw-r--r-- | core/java/android/animation/ValueAnimator.java | 8 |
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; |