From 49afa5bc100e5d4c069fea980dd6b09501f56397 Mon Sep 17 00:00:00 2001 From: Chet Haase Date: Mon, 23 Aug 2010 11:39:53 -0700 Subject: [PATCH] Add facilities for layout transitions Change-Id: I5c73ce6c6ba3bc9e3b57fcfbbcab37d511db6132 --- api/current.xml | 51 ++++++---- core/java/android/animation/Animatable.java | 16 +++- core/java/android/animation/Animator.java | 39 +++++++- .../android/animation/PropertyAnimator.java | 6 ++ core/java/android/animation/Sequencer.java | 93 ++++++++++++++++++- core/java/android/app/Dialog.java | 2 +- core/java/android/util/TimingLogger.java | 32 ++++--- core/java/android/view/View.java | 2 +- .../android/view/animation/Animation.java | 2 +- 9 files changed, 200 insertions(+), 43 deletions(-) diff --git a/api/current.xml b/api/current.xml index bba70f977ce41..9327b84d68855 100644 --- a/api/current.xml +++ b/api/current.xml @@ -19779,6 +19779,8 @@ deprecated="not deprecated" visibility="public" > + + + + + + - + - + - + - + @@ -269677,7 +269692,7 @@ deprecated="not deprecated" visibility="public" > - + - + - + - + - + - + - + - + - + - + - + - + - + - + AnimatableListeners added to them. */ -public abstract class Animatable { +public abstract class Animatable implements Cloneable { /** @@ -107,6 +107,20 @@ public abstract class Animatable { } } + @Override + public Animatable clone() throws CloneNotSupportedException { + final Animatable anim = (Animatable) super.clone(); + if (mListeners != null) { + ArrayList oldListeners = mListeners; + anim.mListeners = new ArrayList(); + int numListeners = oldListeners.size(); + for (int i = 0; i < numListeners; ++i) { + anim.mListeners.add(oldListeners.get(i)); + } + } + return anim; + } + /** *

An animation listener receives notifications from an animation. * Notifications indicate animation related events, such as the end or the diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index 385e75deef18f..eca5f0d29601a 100755 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -80,9 +80,11 @@ public class Animator extends Animatable { /** * 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() @@ -402,7 +404,7 @@ public class Animator extends Animatable { * @return The current position in time of the animation. */ public long getCurrentPlayTime() { - if (!mInitialized) { + if (!mInitialized || mPlayingState == STOPPED) { return 0; } return AnimationUtils.currentAnimationTimeMillis() - mStartTime; @@ -954,6 +956,39 @@ public class Animator extends Animatable { } } + @Override + public Animator clone() throws CloneNotSupportedException { + final Animator anim = (Animator) super.clone(); + if (mUpdateListeners != null) { + ArrayList oldListeners = mUpdateListeners; + anim.mUpdateListeners = new ArrayList(); + 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]; + for (int i = 0; i < numValues; ++i) { + anim.mValues[i] = oldValues[i]; + } + anim.mValuesMap = new HashMap(numValues); + for (int i = 0; i < numValues; ++i) { + PropertyValuesHolder valuesHolder = mValues[i]; + anim.mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); + } + } + return anim; + } + /** * Implementors of this interface can add themselves as update listeners * to an Animator instance to receive callbacks on every animation diff --git a/core/java/android/animation/PropertyAnimator.java b/core/java/android/animation/PropertyAnimator.java index 1a010a982920c..022ca7ddc999c 100644 --- a/core/java/android/animation/PropertyAnimator.java +++ b/core/java/android/animation/PropertyAnimator.java @@ -228,4 +228,10 @@ public final class PropertyAnimator extends Animator { mValues[i].setAnimatedValue(mTarget); } } + + @Override + public PropertyAnimator clone() throws CloneNotSupportedException { + final PropertyAnimator anim = (PropertyAnimator) super.clone(); + return anim; + } } diff --git a/core/java/android/animation/Sequencer.java b/core/java/android/animation/Sequencer.java index 2406d8af57220..77494290f9e4b 100644 --- a/core/java/android/animation/Sequencer.java +++ b/core/java/android/animation/Sequencer.java @@ -45,11 +45,18 @@ import java.util.HashMap; */ public final class Sequencer extends Animatable { + /** + * 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 Sequencer */ - private final ArrayList mPlayingSet = new ArrayList(); + private ArrayList mPlayingSet = new ArrayList(); /** * Contains all nodes, mapped to their respective Animatables. When new @@ -57,21 +64,21 @@ public final class Sequencer extends Animatable { * to a single node representing that Animatable, not create a new Node * if one already exists. */ - private final HashMap mNodeMap = new HashMap(); + private HashMap mNodeMap = new HashMap(); /** * Set of all nodes created for this Sequencer. This list is used upon * starting the sequencer, and the nodes are placed in sorted order into the * sortedNodes collection. */ - private final ArrayList mNodes = new ArrayList(); + private ArrayList mNodes = new ArrayList(); /** * 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 final ArrayList mSortedNodes = new ArrayList(); + private ArrayList mSortedNodes = new ArrayList(); /** * Flag indicating whether the nodes should be sorted prior to playing. This @@ -283,6 +290,75 @@ public final class Sequencer extends Animatable { } } + @Override + public Sequencer clone() throws CloneNotSupportedException { + final Sequencer anim = (Sequencer) super.clone(); + /* + * The basic clone() operation copies all items. This doesn't work very well for + * Sequencer, 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.mCanceled = false; + anim.mPlayingSet = new ArrayList(); + anim.mNodeMap = new HashMap(); + anim.mNodes = new ArrayList(); + anim.mSortedNodes = new ArrayList(); + + // 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 sequencer. + // We need to track the old/new nodes in order to reconstruct the dependencies in the clone. + HashMap nodeCloneMap = new HashMap(); // + 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 sequencer; these will + // be set up when the clone's nodes are sorted + ArrayList cloneListeners = nodeClone.animation.getListeners(); + if (cloneListeners != null) { + ArrayList listenersToRemove = null; + for (AnimatableListener listener : cloneListeners) { + if (listener instanceof SequencerAnimatableListener) { + if (listenersToRemove == null) { + listenersToRemove = new ArrayList(); + } + listenersToRemove.add(listener); + } + } + if (listenersToRemove != null) { + for (AnimatableListener 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 @@ -514,7 +590,7 @@ public final class Sequencer extends Animatable { * 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 { + private static class Node implements Cloneable { public Animatable animation; /** @@ -587,6 +663,13 @@ public final class Sequencer extends Animatable { } dependencyNode.nodeDependents.add(this); } + + @Override + public Node clone() throws CloneNotSupportedException { + Node node = (Node) super.clone(); + node.animation = (Animatable) animation.clone(); + return node; + } } /** diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 274a2664a2e88..a0be0cde1f5d0 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -330,7 +330,7 @@ public class Dialog implements DialogInterface, Window.Callback, } /** - * Similar to {@link Activity#onCreate}, you should initialized your dialog + * Similar to {@link Activity#onCreate}, you should initialize your dialog * in this method, including calling {@link #setContentView}. * @param savedInstanceState If this dialog is being reinitalized after a * the hosting activity was previously shut down, holds the result from diff --git a/core/java/android/util/TimingLogger.java b/core/java/android/util/TimingLogger.java index 0f39c974a4d98..be442dac630af 100644 --- a/core/java/android/util/TimingLogger.java +++ b/core/java/android/util/TimingLogger.java @@ -24,22 +24,26 @@ import android.os.SystemClock; * A utility class to help log timings splits throughout a method call. * Typical usage is: * - * TimingLogger timings = new TimingLogger(TAG, "methodA"); - * ... do some work A ... - * timings.addSplit("work A"); - * ... do some work B ... - * timings.addSplit("work B"); - * ... do some work C ... - * timings.addSplit("work C"); - * timings.dumpToLog(); + *

+ *     TimingLogger timings = new TimingLogger(TAG, "methodA");
+ *     // ... do some work A ...
+ *     timings.addSplit("work A");
+ *     // ... do some work B ...
+ *     timings.addSplit("work B");
+ *     // ... do some work C ...
+ *     timings.addSplit("work C");
+ *     timings.dumpToLog();
+ * 
* - * The dumpToLog call would add the following to the log: + *

The dumpToLog call would add the following to the log:

* - * D/TAG ( 3459): methodA: begin - * D/TAG ( 3459): methodA: 9 ms, work A - * D/TAG ( 3459): methodA: 1 ms, work B - * D/TAG ( 3459): methodA: 6 ms, work C - * D/TAG ( 3459): methodA: end, 16 ms + *
+ *     D/TAG     ( 3459): methodA: begin
+ *     D/TAG     ( 3459): methodA:      9 ms, work A
+ *     D/TAG     ( 3459): methodA:      1 ms, work B
+ *     D/TAG     ( 3459): methodA:      6 ms, work C
+ *     D/TAG     ( 3459): methodA: end, 16 ms
+ * 
*/ public class TimingLogger { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c13bb8c70fde7..b565fc61f9e55 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3438,7 +3438,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** - * Sets the pressed that for this view. + * Sets the pressed state for this view. * * @see #isClickable() * @see #setClickable(boolean) diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index f3392d9617c00..9a1e41cb2bb93 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -307,7 +307,7 @@ public abstract class Animation implements Cloneable { * animated as well as the objects parents. (This is to support animation * sizes being specifed relative to these dimensions.) * - *

Objects that interpret a Animations should call this method when + *

Objects that interpret Animations should call this method when * the sizes of the object being animated and its parent are known, and * before calling {@link #getTransformation}. *