diff options
| author | Chet Haase <chet@google.com> | 2010-09-03 12:31:17 -0700 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2010-09-03 12:31:17 -0700 |
| commit | 52d33aa41288d862e999919f03502686393863c3 (patch) | |
| tree | 872d576b5f11df1b95b5d83b29cacb8a50c915b0 | |
| parent | 462bac9222428c227b3704c1be0744b00c3bad73 (diff) | |
| parent | 21cd1389d2ef218b20994b617c57af120841a57f (diff) | |
| download | frameworks_base-52d33aa41288d862e999919f03502686393863c3.zip frameworks_base-52d33aa41288d862e999919f03502686393863c3.tar.gz frameworks_base-52d33aa41288d862e999919f03502686393863c3.tar.bz2 | |
Merge "Add transition effects for layout changes on ViewGroups"
| -rw-r--r-- | api/current.xml | 702 | ||||
| -rw-r--r-- | core/java/android/animation/Animatable.java | 99 | ||||
| -rw-r--r-- | core/java/android/animation/AnimatableListenerAdapter.java | 8 | ||||
| -rwxr-xr-x | core/java/android/animation/Animator.java | 49 | ||||
| -rw-r--r-- | core/java/android/animation/Keyframe.java | 59 | ||||
| -rw-r--r-- | core/java/android/animation/LayoutTransition.java | 778 | ||||
| -rw-r--r-- | core/java/android/animation/PropertyAnimator.java | 33 | ||||
| -rw-r--r-- | core/java/android/animation/PropertyValuesHolder.java | 72 | ||||
| -rw-r--r-- | core/java/android/animation/Sequencer.java | 116 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 229 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 96 |
11 files changed, 2172 insertions, 69 deletions
diff --git a/api/current.xml b/api/current.xml index 384ca18..9e86f01 100644 --- a/api/current.xml +++ b/api/current.xml @@ -19851,8 +19851,6 @@ deprecated="not deprecated" visibility="public" > -<exception name="CloneNotSupportedException" type="java.lang.CloneNotSupportedException"> -</exception> </method> <method name="end" return="void" @@ -19865,6 +19863,17 @@ visibility="public" > </method> +<method name="getDuration" + return="long" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getListeners" return="java.util.ArrayList<android.animation.Animatable.AnimatableListener>" abstract="false" @@ -19876,6 +19885,17 @@ visibility="public" > </method> +<method name="getStartDelay" + return="long" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="isRunning" return="boolean" abstract="true" @@ -19911,6 +19931,80 @@ <parameter name="listener" type="android.animation.Animatable.AnimatableListener"> </parameter> </method> +<method name="setDuration" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="duration" type="long"> +</parameter> +</method> +<method name="setInterpolator" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="value" type="android.view.animation.Interpolator"> +</parameter> +</method> +<method name="setStartDelay" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="startDelay" type="long"> +</parameter> +</method> +<method name="setTarget" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="target" type="java.lang.Object"> +</parameter> +</method> +<method name="setupEndValues" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="setupStartValues" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="start" return="void" abstract="false" @@ -20230,6 +20324,17 @@ visibility="public" > </method> +<method name="getValues" + return="android.animation.PropertyValuesHolder[]" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="isRunning" return="boolean" abstract="false" @@ -20566,6 +20671,8 @@ deprecated="not deprecated" visibility="public" > +<implements name="java.lang.Cloneable"> +</implements> <constructor name="Keyframe" type="android.animation.Keyframe" static="false" @@ -20587,6 +20694,42 @@ > <parameter name="fraction" type="float"> </parameter> +<parameter name="value" type="java.lang.Float"> +</parameter> +</constructor> +<constructor name="Keyframe" + type="android.animation.Keyframe" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="fraction" type="float"> +</parameter> +<parameter name="value" type="java.lang.Integer"> +</parameter> +</constructor> +<constructor name="Keyframe" + type="android.animation.Keyframe" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="fraction" type="float"> +</parameter> +<parameter name="value" type="java.lang.Double"> +</parameter> +</constructor> +<constructor name="Keyframe" + type="android.animation.Keyframe" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="fraction" type="float"> +</parameter> <parameter name="value" type="int"> </parameter> </constructor> @@ -20614,6 +20757,17 @@ <parameter name="value" type="double"> </parameter> </constructor> +<method name="clone" + return="android.animation.Keyframe" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getFraction" return="float" abstract="false" @@ -20698,6 +20852,333 @@ </parameter> </method> </class> +<class name="LayoutTransition" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="LayoutTransition" + type="android.animation.LayoutTransition" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<method name="addTransitionListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.animation.LayoutTransition.TransitionListener"> +</parameter> +</method> +<method name="childAdd" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="parent" type="android.view.ViewGroup"> +</parameter> +<parameter name="child" type="android.view.View"> +</parameter> +</method> +<method name="childRemove" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="parent" type="android.view.ViewGroup"> +</parameter> +<parameter name="child" type="android.view.View"> +</parameter> +</method> +<method name="getAnimatable" + return="android.animation.Animatable" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transitionType" type="int"> +</parameter> +</method> +<method name="getDuration" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transitionType" type="int"> +</parameter> +</method> +<method name="getInterpolator" + return="android.view.animation.Interpolator" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transitionType" type="int"> +</parameter> +</method> +<method name="getStagger" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transitionType" type="int"> +</parameter> +</method> +<method name="getStartDelay" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transitionType" type="int"> +</parameter> +</method> +<method name="getTransitionListeners" + return="java.util.List<android.animation.LayoutTransition.TransitionListener>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="removeTransitionListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.animation.LayoutTransition.TransitionListener"> +</parameter> +</method> +<method name="setAnimatable" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transitionType" type="int"> +</parameter> +<parameter name="animatable" type="android.animation.Animatable"> +</parameter> +</method> +<method name="setDuration" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="duration" type="long"> +</parameter> +</method> +<method name="setDuration" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transitionType" type="int"> +</parameter> +<parameter name="duration" type="long"> +</parameter> +</method> +<method name="setInterpolator" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transitionType" type="int"> +</parameter> +<parameter name="interpolator" type="android.view.animation.Interpolator"> +</parameter> +</method> +<method name="setStagger" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transitionType" type="int"> +</parameter> +<parameter name="duration" type="long"> +</parameter> +</method> +<method name="setStartDelay" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transitionType" type="int"> +</parameter> +<parameter name="delay" type="long"> +</parameter> +</method> +<field name="APPEARING" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="CHANGE_APPEARING" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="CHANGE_DISAPPEARING" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DISAPPEARING" + type="int" + transient="false" + volatile="false" + value="3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<interface name="LayoutTransition.TransitionListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="endTransition" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transition" type="android.animation.LayoutTransition"> +</parameter> +<parameter name="container" type="android.view.ViewGroup"> +</parameter> +<parameter name="view" type="android.view.View"> +</parameter> +<parameter name="transitionType" type="int"> +</parameter> +</method> +<method name="startTransition" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transition" type="android.animation.LayoutTransition"> +</parameter> +<parameter name="container" type="android.view.ViewGroup"> +</parameter> +<parameter name="view" type="android.view.View"> +</parameter> +<parameter name="transitionType" type="int"> +</parameter> +</method> +</interface> <class name="PropertyAnimator" extends="android.animation.Animator" abstract="false" @@ -20779,19 +21260,6 @@ <parameter name="propertyName" type="java.lang.String"> </parameter> </method> -<method name="setTarget" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="target" type="java.lang.Object"> -</parameter> -</method> </class> <class name="PropertyValuesHolder" extends="java.lang.Object" @@ -20801,6 +21269,8 @@ deprecated="not deprecated" visibility="public" > +<implements name="java.lang.Cloneable"> +</implements> <constructor name="PropertyValuesHolder" type="android.animation.PropertyValuesHolder" static="false" @@ -20823,6 +21293,17 @@ <parameter name="values" type="T..."> </parameter> </constructor> +<method name="clone" + return="android.animation.PropertyValuesHolder" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getGetter" return="java.lang.reflect.Method" abstract="false" @@ -20985,6 +21466,28 @@ visibility="public" > </method> +<method name="getDuration" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getStartDelay" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="isRunning" return="boolean" abstract="false" @@ -21035,7 +21538,7 @@ <parameter name="sequenceItems" type="android.animation.Animatable..."> </parameter> </method> -<method name="setTarget" +<method name="setDuration" return="void" abstract="false" native="false" @@ -21045,7 +21548,33 @@ deprecated="not deprecated" visibility="public" > -<parameter name="target" type="java.lang.Object"> +<parameter name="duration" type="long"> +</parameter> +</method> +<method name="setInterpolator" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="interpolator" type="android.view.animation.Interpolator"> +</parameter> +</method> +<method name="setStartDelay" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="startDelay" type="long"> </parameter> </method> </class> @@ -194495,6 +195024,19 @@ <parameter name="focusableMode" type="int"> </parameter> </method> +<method name="addOnLayoutChangeListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.view.View.OnLayoutChangeListener"> +</parameter> +</method> <method name="addTouchables" return="void" abstract="false" @@ -195584,6 +196126,17 @@ visibility="public" > </method> +<method name="getOnLayoutChangeListeners" + return="java.util.List<android.view.View.OnLayoutChangeListener>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getPaddingBottom" return="int" abstract="false" @@ -197188,6 +197741,19 @@ <parameter name="action" type="java.lang.Runnable"> </parameter> </method> +<method name="removeOnLayoutChangeListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.view.View.OnLayoutChangeListener"> +</parameter> +</method> <method name="requestFocus" return="boolean" abstract="false" @@ -197456,6 +198022,19 @@ <parameter name="resid" type="int"> </parameter> </method> +<method name="setBottom" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="bottom" type="int"> +</parameter> +</method> <method name="setClickable" return="void" abstract="false" @@ -197664,6 +198243,19 @@ <parameter name="params" type="android.view.ViewGroup.LayoutParams"> </parameter> </method> +<method name="setLeft" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="left" type="int"> +</parameter> +</method> <method name="setLongClickable" return="void" abstract="false" @@ -197906,6 +198498,19 @@ <parameter name="pressed" type="boolean"> </parameter> </method> +<method name="setRight" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="right" type="int"> +</parameter> +</method> <method name="setRotation" return="void" abstract="false" @@ -198090,6 +198695,19 @@ <parameter name="tag" type="java.lang.Object"> </parameter> </method> +<method name="setTop" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="top" type="int"> +</parameter> +</method> <method name="setTouchDelegate" return="void" abstract="false" @@ -199152,6 +199770,43 @@ </parameter> </method> </interface> +<interface name="View.OnLayoutChangeListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onLayoutChange" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="v" type="android.view.View"> +</parameter> +<parameter name="left" type="int"> +</parameter> +<parameter name="top" type="int"> +</parameter> +<parameter name="right" type="int"> +</parameter> +<parameter name="bottom" type="int"> +</parameter> +<parameter name="oldLeft" type="int"> +</parameter> +<parameter name="oldTop" type="int"> +</parameter> +<parameter name="oldRight" type="int"> +</parameter> +<parameter name="oldBottom" type="int"> +</parameter> +</method> +</interface> <interface name="View.OnLongClickListener" abstract="true" static="true" @@ -200980,6 +201635,19 @@ <parameter name="animationListener" type="android.view.animation.Animation.AnimationListener"> </parameter> </method> +<method name="setLayoutTransition" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="transition" type="android.animation.LayoutTransition"> +</parameter> +</method> <method name="setMotionEventSplittingEnabled" return="void" abstract="false" diff --git a/core/java/android/animation/Animatable.java b/core/java/android/animation/Animatable.java index d6cf7c0..3fdf200 100644 --- a/core/java/android/animation/Animatable.java +++ b/core/java/android/animation/Animatable.java @@ -16,6 +16,8 @@ package android.animation; +import android.view.animation.Interpolator; + import java.util.ArrayList; /** @@ -56,6 +58,46 @@ public abstract class Animatable implements Cloneable { 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 void 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(Interpolator value); /** * Returns whether this Animatable is currently running (having been started and not yet ended). @@ -115,17 +157,56 @@ public abstract class Animatable implements Cloneable { } @Override - public Animatable clone() throws CloneNotSupportedException { - final Animatable anim = (Animatable) super.clone(); - if (mListeners != null) { - ArrayList<AnimatableListener> oldListeners = mListeners; - anim.mListeners = new ArrayList<AnimatableListener>(); - int numListeners = oldListeners.size(); - for (int i = 0; i < numListeners; ++i) { - anim.mListeners.add(oldListeners.get(i)); + public Animatable clone() { + try { + final Animatable anim = (Animatable) super.clone(); + if (mListeners != null) { + ArrayList<AnimatableListener> oldListeners = mListeners; + anim.mListeners = new ArrayList<AnimatableListener>(); + 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(); } - return anim; + } + + /** + * This method tells the object to use appropriate information to extract + * starting values for the animation. For example, a Sequencer object will pass + * this call to its child objects to tell them to set up the values. A + * PropertyAnimator object will use the information it has about its target object + * and PropertyValuesHolder objects to get the start values for its properties. + * An Animator 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 Sequencer object will pass + * this call to its child objects to tell them to set up the values. A + * PropertyAnimator object will use the information it has about its target object + * and PropertyValuesHolder objects to get the start values for its properties. + * An Animator 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 android.animation.Animator}, 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) { } /** diff --git a/core/java/android/animation/AnimatableListenerAdapter.java b/core/java/android/animation/AnimatableListenerAdapter.java index 25a842b..c169b28 100644 --- a/core/java/android/animation/AnimatableListenerAdapter.java +++ b/core/java/android/animation/AnimatableListenerAdapter.java @@ -26,28 +26,28 @@ import android.animation.Animatable.AnimatableListener; public abstract class AnimatableListenerAdapter implements AnimatableListener { /** - * @{inheritdoc} + * {@inheritdoc} */ @Override public void onAnimationCancel(Animatable animation) { } /** - * @{inheritdoc} + * {@inheritdoc} */ @Override public void onAnimationEnd(Animatable animation) { } /** - * @{inheritdoc} + * {@inheritdoc} */ @Override public void onAnimationRepeat(Animatable animation) { } /** - * @{inheritdoc} + * {@inheritdoc} */ @Override public void onAnimationStart(Animatable animation) { diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index 8b74658..8e947ec 100755 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -145,10 +145,10 @@ public class Animator<T> extends Animatable { private static final ArrayList<Animator> sReadyAnims = new ArrayList<Animator>(); /** - * Flag that denotes whether the animation is set up and ready to go. Used by seek() to + * Flag that denotes whether the animation is set up and ready to go. Used to * set up animation that has not yet been started. */ - private boolean mInitialized = false; + boolean mInitialized = false; // // Backing variables @@ -243,6 +243,14 @@ public class Animator<T> extends Animatable { } } + /** + * Sets the values, per property, being animated between. This function is called internally + * by the constructors of Animator that take a list of values. But an Animator 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; @@ -254,6 +262,18 @@ public class Animator<T> extends Animatable { } /** + * Returns the values that this Animator animates between. These values are stored in + * PropertyValuesHolder objects, even if the Animator 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; + } + + /** * Sets the values to animate between for this animation. If <code>values</code> is * a set of PropertyValuesHolder objects, these objects will become the set of properties * animated and the values that those properties are animated between. Otherwise, this method @@ -286,12 +306,14 @@ public class Animator<T> extends Animatable { * that internal mechanisms for the animation are set up correctly.</p> */ void initAnimation() { - int numValues = mValues.length; - for (int i = 0; i < numValues; ++i) { - mValues[i].init(); + if (!mInitialized) { + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].init(); + } + mCurrentIteration = 0; + mInitialized = true; } - mCurrentIteration = 0; - mInitialized = true; } @@ -324,9 +346,7 @@ public class Animator<T> extends Animatable { * @param playTime The time, in milliseconds, to which the animation is advanced or rewound. */ public void setCurrentPlayTime(long playTime) { - if (!mInitialized) { - initAnimation(); - } + initAnimation(); long currentTime = AnimationUtils.currentAnimationTimeMillis(); if (mPlayingState != RUNNING) { mSeekTime = playTime; @@ -619,6 +639,7 @@ public class Animator<T> extends Animatable { * * @param value the interpolator to be used by this animation */ + @Override public void setInterpolator(Interpolator value) { if (value != null) { mInterpolator = value; @@ -783,6 +804,10 @@ public class Animator<T> extends Animatable { * should be added to the set of active animations. */ private boolean delayedAnimationFrame(long currentTime) { + if (mPlayingState == CANCELED || mPlayingState == ENDED) { + // end the delay, process an animation frame to actually cancel it + return true; + } if (!mStartedDelay) { mStartedDelay = true; mDelayStartTime = currentTime; @@ -898,7 +923,7 @@ public class Animator<T> extends Animatable { } @Override - public Animator clone() throws CloneNotSupportedException { + public Animator clone() { final Animator anim = (Animator) super.clone(); if (mUpdateListeners != null) { ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners; @@ -919,7 +944,7 @@ public class Animator<T> extends Animatable { int numValues = oldValues.length; anim.mValues = new PropertyValuesHolder[numValues]; for (int i = 0; i < numValues; ++i) { - anim.mValues[i] = oldValues[i]; + anim.mValues[i] = oldValues[i].clone(); } anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues); for (int i = 0; i < numValues; ++i) { diff --git a/core/java/android/animation/Keyframe.java b/core/java/android/animation/Keyframe.java index e2800b3..7d4d104 100644 --- a/core/java/android/animation/Keyframe.java +++ b/core/java/android/animation/Keyframe.java @@ -26,7 +26,7 @@ import android.view.animation.Interpolator; * next keyframe. Each keyframe also holds an option {@link android.view.animation.Interpolator} * object, which defines the time interpolation over the intervalue preceding the keyframe. */ -public class Keyframe { +public class Keyframe implements Cloneable { /** * The time at which mValue will hold true. */ @@ -81,7 +81,55 @@ public class Keyframe { * this keyframe. */ public Keyframe(float fraction, Object value) { - this(fraction, value, Object.class); + this(fraction, value, (value != null) ? value.getClass() : Object.class); + } + + /** + * Constructs a Keyframe object with the given time and float 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 Keyframe(float fraction, Float value) { + this(fraction, value, Float.class); + } + + /** + * Constructs a Keyframe object with the given time and integer 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 Keyframe(float fraction, Integer value) { + this(fraction, value, Integer.class); + } + + /** + * Constructs a Keyframe object with the given time and double 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 Keyframe(float fraction, Double value) { + this(fraction, value, Double.class); } /** @@ -200,4 +248,11 @@ public class Keyframe { public Class getType() { return mValueType; } + + @Override + public Keyframe clone() { + Keyframe kfClone = new Keyframe(mFraction, mValue, mValueType); + kfClone.setInterpolator(mInterpolator); + return kfClone; + } } diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java new file mode 100644 index 0000000..5dfdfbd --- /dev/null +++ b/core/java/android/animation/LayoutTransition.java @@ -0,0 +1,778 @@ +/* + * 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 android.animation; + +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * This class enables automatic animations on layout changes in ViewGroup objects. To enable + * transitions for a layout container, create a LayoutTransition object and set it on any + * ViewGroup by calling {@link ViewGroup#setLayoutTransition(LayoutTransition)}. This will cause + * default animations to run whenever items are added to or removed from that container. To specify + * custom animations, use the {@link LayoutTransition#setAnimatable(int, Animatable) + * setAnimatable()} method. + * + * <p>One of the core concepts of these transition animations is that there are two core + * changes that cause the transition and four different animations that run because of + * those changes. The changes that trigger the transition are items being added to a container + * (referred to as an "appearing" transition) or removed from a container (also known as + * "disappearing"). The animations that run due to those events are one that animates + * items being added, one that animates items being removed, and two that animate the other + * items in the container that change due to the add/remove occurrence. Users of + * the transition may want different animations for the changing items depending on whether + * they are changing due to anappearing or disappearing event, so there is one animation for + * each of these variations of the changing event. Most of the API of this class is concerned + * with setting up the basic properties of the animations used in these four situations, + * or with setting up custom animations for any or all of the four.</p> + * + * <p>The animations specified for the transition, both the defaults and any custom animations + * set on the transition object, are templates only. That is, these animations exist to hold the + * basic animation properties, such as the duration, start delay, and properties being animated. + * But the actual target object, as well as the start and end values for those properties, are + * set automatically in the process of setting up the transition each time it runs. Each of the + * animations is cloned from the original copy and the clone is then populated with the dynamic + * values of the target being animated (such as one of the items in a layout container that is + * moving as a result of the layout event) as well as the values that are changing (such as the + * position and size of that object). The actual values that are pushed to each animation + * depends on what properties are specified for the animation. For example, the default + * CHANGE_APPEARING animation animates <code>left</code>, <code>top</code>, <code>right</code>, + * and <code>bottom</code>. Values for these properties are updated with the pre- and post-layout + * values when the transition begins. Custom animations will be similarly populated with + * the target and values being animated, assuming they use PropertyAnimator objects with + * property names that are known on the target object.</p> + */ +public class LayoutTransition { + + /** + * A flag indicating the animation that runs on those items that are changing + * due to a new item appearing in the container. + */ + public static final int CHANGE_APPEARING = 0; + + /** + * A flag indicating the animation that runs on those items that are changing + * due to a new item disappearing from the container. + */ + public static final int CHANGE_DISAPPEARING = 1; + + /** + * A flag indicating the animation that runs on those items that are changing + * due to a new item appearing in the container. + */ + public static final int APPEARING = 2; + + /** + * A flag indicating the animation that runs on those items that are changing + * due to a new item appearing in the container. + */ + public static final int DISAPPEARING = 3; + + /** + * These variables hold the animations that are currently used to run the transition effects. + * These animations are set to defaults, but can be changed to custom animations by + * calls to setAnimatable(). + */ + private Animatable mDisappearingAnim = null; + private Animatable mAppearingAnim = null; + private Animatable mChangingAppearingAnim = null; + private Animatable mChangingDisappearingAnim = null; + + /** + * These are the default animations, defined in the constructor, that will be used + * unless the user specifies custom animations. + */ + private static PropertyAnimator defaultChangeIn; + private static PropertyAnimator defaultChangeOut; + private static PropertyAnimator defaultFadeIn; + private static PropertyAnimator defaultFadeOut; + + /** + * The default duration used by all animations. + */ + private static long DEFAULT_DURATION = 300; + + /** + * The durations of the four different animations + */ + private long mChangingAppearingDuration = DEFAULT_DURATION; + private long mChangingDisappearingDuration = DEFAULT_DURATION; + private long mAppearingDuration = DEFAULT_DURATION; + private long mDisappearingDuration = DEFAULT_DURATION; + + /** + * The start delays of the four different animations. Note that the default behavior of + * the appearing item is the default duration, since it should wait for the items to move + * before fading it. Same for the changing animation when disappearing; it waits for the item + * to fade out before moving the other items. + */ + private long mAppearingDelay = DEFAULT_DURATION; + private long mDisappearingDelay = 0; + private long mChangingAppearingDelay = 0; + private long mChangingDisappearingDelay = DEFAULT_DURATION; + + /** + * The inter-animation delays used on the two changing animations + */ + private long mChangingAppearingStagger = 0; + private long mChangingDisappearingStagger = 0; + + /** + * The default interpolators used for the animations + */ + private Interpolator mAppearingInterpolator = new AccelerateDecelerateInterpolator(); + private Interpolator mDisappearingInterpolator = new AccelerateDecelerateInterpolator(); + private Interpolator mChangingAppearingInterpolator = new DecelerateInterpolator(); + private Interpolator mChangingDisappearingInterpolator = new DecelerateInterpolator(); + + /** + * This hashmap is used to store the animations that are currently running as part of + * the transition. The reason for this is that a further layout event should cause + * existing animations to stop where they are prior to starting new animations. So + * we cache all of the current animations in this map for possible cancellation on + * another layout event. + */ + private HashMap<View, Animatable> currentAnimations = new HashMap<View, Animatable>(); + + /** + * This hashmap is used to track the listeners that have been added to the children of + * a container. When a layout change occurs, an animation is created for each View, so that + * the pre-layout values can be cached in that animation. Then a listener is added to the + * view to see whether the layout changes the bounds of that view. If so, the animation + * is set with the final values and then run. If not, the animation is not started. When + * the process of setting up and running all appropriate animations is done, we need to + * remove these listeners and clear out the map. + */ + private HashMap<View, View.OnLayoutChangeListener> layoutChangeListenerMap = + new HashMap<View, View.OnLayoutChangeListener>(); + + /** + * Used to track the current delay being assigned to successive animations as they are + * started. This value is incremented for each new animation, then zeroed before the next + * transition begins. + */ + private long staggerDelay; + + /** + * The set of listeners that should be notified when APPEARING/DISAPPEARING transitions + * start and end. + */ + private ArrayList<TransitionListener> mListeners; + + + /** + * Constructs a LayoutTransition object. By default, the object will listen to layout + * events on any ViewGroup that it is set on and will run default animations for each + * type of layout event. + */ + public LayoutTransition() { + if (defaultChangeIn == null) { + // "left" is just a placeholder; we'll put real properties/values in when needed + PropertyValuesHolder<Integer> pvhLeft = new PropertyValuesHolder<Integer>("left", 0, 1); + PropertyValuesHolder<Integer> pvhTop = new PropertyValuesHolder<Integer>("top", 0, 1); + PropertyValuesHolder<Integer> pvhRight = new PropertyValuesHolder<Integer>("right", 0, 1); + PropertyValuesHolder<Integer> pvhBottom = new PropertyValuesHolder<Integer>("bottom", 0, 1); + defaultChangeIn = new PropertyAnimator<PropertyValuesHolder>(DEFAULT_DURATION, this, + pvhLeft, pvhTop, pvhRight, pvhBottom); + defaultChangeIn.setStartDelay(mChangingAppearingDelay); + defaultChangeIn.setInterpolator(mChangingAppearingInterpolator); + defaultChangeOut = defaultChangeIn.clone(); + defaultChangeOut.setStartDelay(mChangingDisappearingDelay); + defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator); + defaultFadeIn = + new PropertyAnimator<Float>(DEFAULT_DURATION, this, "alpha", 0f, 1f); + defaultFadeIn.setStartDelay(mAppearingDelay); + defaultFadeIn.setInterpolator(mAppearingInterpolator); + defaultFadeOut = + new PropertyAnimator<Float>(DEFAULT_DURATION, this, "alpha", 1f, 0f); + defaultFadeOut.setStartDelay(mDisappearingDelay); + defaultFadeOut.setInterpolator(mDisappearingInterpolator); + } + mChangingAppearingAnim = defaultChangeIn; + mChangingDisappearingAnim = defaultChangeOut; + mAppearingAnim = defaultFadeIn; + mDisappearingAnim = defaultFadeOut; + } + + /** + * Sets the duration to be used by all animations of this transition object. If you want to + * set the duration of just one of the animations in particular, use the + * {@link #setDuration(int, long)} method. + * + * @param duration The length of time, in milliseconds, that the transition animations + * should last. + */ + public void setDuration(long duration) { + mChangingAppearingDuration = duration; + mChangingDisappearingDuration = duration; + mAppearingDuration = duration; + mDisappearingDuration = duration; + } + + /** + * Sets the start delay on one of the animation objects used by this transition. The + * <code>transitionType</code> parameter determines the animation whose start delay + * is being set. + * + * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, + * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start + * delay is being set. + * @param delay The length of time, in milliseconds, to delay before starting the animation. + * @see android.animation.Animatable#setStartDelay(long) + */ + public void setStartDelay(int transitionType, long delay) { + switch (transitionType) { + case CHANGE_APPEARING: + mChangingAppearingDelay = delay; + break; + case CHANGE_DISAPPEARING: + mChangingDisappearingDelay = delay; + break; + case APPEARING: + mAppearingDelay = delay; + break; + case DISAPPEARING: + mDisappearingDelay = delay; + break; + } + } + + /** + * Gets the start delay on one of the animation objects used by this transition. The + * <code>transitionType</code> parameter determines the animation whose start delay + * is returned. + * + * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, + * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start + * delay is returned. + * @return long The start delay of the specified animation. + * @see android.animation.Animatable#getStartDelay() + */ + public long getStartDelay(int transitionType) { + switch (transitionType) { + case CHANGE_APPEARING: + return mChangingAppearingDuration; + case CHANGE_DISAPPEARING: + return mChangingDisappearingDuration; + case APPEARING: + return mAppearingDuration; + case DISAPPEARING: + return mDisappearingDuration; + } + // shouldn't reach here + return 0; + } + + /** + * Sets the duration on one of the animation objects used by this transition. The + * <code>transitionType</code> parameter determines the animation whose duration + * is being set. + * + * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, + * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose + * duration is being set. + * @param duration The length of time, in milliseconds, that the specified animation should run. + * @see android.animation.Animatable#setDuration(long) + */ + public void setDuration(int transitionType, long duration) { + switch (transitionType) { + case CHANGE_APPEARING: + mChangingAppearingDuration = duration; + break; + case CHANGE_DISAPPEARING: + mChangingDisappearingDuration = duration; + break; + case APPEARING: + mAppearingDuration = duration; + break; + case DISAPPEARING: + mDisappearingDuration = duration; + break; + } + } + + /** + * Gets the duration on one of the animation objects used by this transition. The + * <code>transitionType</code> parameter determines the animation whose duration + * is returned. + * + * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, + * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose + * duration is returned. + * @return long The duration of the specified animation. + * @see android.animation.Animatable#getDuration() + */ + public long getDuration(int transitionType) { + switch (transitionType) { + case CHANGE_APPEARING: + return mChangingAppearingDuration; + case CHANGE_DISAPPEARING: + return mChangingDisappearingDuration; + case APPEARING: + return mAppearingDuration; + case DISAPPEARING: + return mDisappearingDuration; + } + // shouldn't reach here + return 0; + } + + /** + * Sets the length of time to delay between starting each animation during one of the + * CHANGE animations. + * + * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}. + * @param duration The length of time, in milliseconds, to delay before launching the next + * animation in the sequence. + */ + public void setStagger(int transitionType, long duration) { + switch (transitionType) { + case CHANGE_APPEARING: + mChangingAppearingStagger = duration; + break; + case CHANGE_DISAPPEARING: + mChangingDisappearingStagger = duration; + break; + // noop other cases + } + } + + /** + * Tets the length of time to delay between starting each animation during one of the + * CHANGE animations. + * + * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}. + * @return long The length of time, in milliseconds, to delay before launching the next + * animation in the sequence. + */ + public long getStagger(int transitionType) { + switch (transitionType) { + case CHANGE_APPEARING: + return mChangingAppearingStagger; + case CHANGE_DISAPPEARING: + return mChangingDisappearingStagger; + } + // shouldn't reach here + return 0; + } + + /** + * Sets the interpolator on one of the animation objects used by this transition. The + * <code>transitionType</code> parameter determines the animation whose interpolator + * is being set. + * + * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, + * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose + * duration is being set. + * @param interpolator The interpolator that the specified animation should use. + * @see android.animation.Animatable#setInterpolator(android.view.animation.Interpolator) + */ + public void setInterpolator(int transitionType, Interpolator interpolator) { + switch (transitionType) { + case CHANGE_APPEARING: + mChangingAppearingInterpolator = interpolator; + break; + case CHANGE_DISAPPEARING: + mChangingDisappearingInterpolator = interpolator; + break; + case APPEARING: + mAppearingInterpolator = interpolator; + break; + case DISAPPEARING: + mDisappearingInterpolator = interpolator; + break; + } + } + + /** + * Gets the interpolator on one of the animation objects used by this transition. The + * <code>transitionType</code> parameter determines the animation whose interpolator + * is returned. + * + * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, + * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose + * duration is being set. + * @return Interpolator The interpolator that the specified animation uses. + * @see android.animation.Animatable#setInterpolator(android.view.animation.Interpolator) + */ + public Interpolator getInterpolator(int transitionType) { + switch (transitionType) { + case CHANGE_APPEARING: + return mChangingAppearingInterpolator; + case CHANGE_DISAPPEARING: + return mChangingDisappearingInterpolator; + case APPEARING: + return mAppearingInterpolator; + case DISAPPEARING: + return mDisappearingInterpolator; + } + // shouldn't reach here + return null; + } + + /** + * Sets the animation used during one of the transition types that may run. Any + * Animatable object can be used, but to be most useful in the context of layout + * transitions, the animation should either be a PropertyAnimator or a Sequencer + * of animations including PropertyAnimators. Also, these PropertyAnimator objects + * should be able to get and set values on their target objects automatically. For + * example, a PropertyAnimator that animates the property "left" is able to set and get the + * <code>left</code> property from the View objects being animated by the layout + * transition. The transition works by setting target objects and properties + * dynamically, according to the pre- and post-layoout values of those objects, so + * having animations that can handle those properties appropriately will work best + * for custom animation. The dynamic setting of values is only the case for the + * CHANGE animations; the APPEARING and DISAPPEARING animations are simply run with + * the values they have. + * + * <p>It is also worth noting that any and all animations (and their underlying + * PropertyValuesHolder objects) will have their start and end values set according + * to the pre- and post-layout values. So, for example, a custom animation on "alpha" + * as the CHANGE_APPEARING animation will inherit the real value of alpha on the target + * object (presumably 1) as its starting and ending value when the animation begins. + * Animations which need to use values at the beginning and end that may not match the + * values queried when the transition begins may need to use a different mechanism + * than a standard PropertyAnimator object.</p> + * + * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, + * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose + * duration is being set. + * @param animatable The animation being assigned. + */ + public void setAnimatable(int transitionType, Animatable animatable) { + switch (transitionType) { + case CHANGE_APPEARING: + mChangingAppearingAnim = (animatable != null) ? animatable : defaultChangeIn; + break; + case CHANGE_DISAPPEARING: + mChangingDisappearingAnim = (animatable != null) ? animatable : defaultChangeOut; + break; + case APPEARING: + mAppearingAnim = (animatable != null) ? animatable : defaultFadeIn; + break; + case DISAPPEARING: + mDisappearingAnim = (animatable != null) ? animatable : defaultFadeOut; + break; + } + } + + /** + * Gets the animation used during one of the transition types that may run. + * + * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, + * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose + * duration is being set. + * @return Animatable The animation being used for the given transition type. + * @see #setAnimatable(int, Animatable) + */ + public Animatable getAnimatable(int transitionType) { + switch (transitionType) { + case CHANGE_APPEARING: + return mChangingAppearingAnim; + case CHANGE_DISAPPEARING: + return mChangingDisappearingAnim; + case APPEARING: + return mAppearingAnim; + case DISAPPEARING: + return mDisappearingAnim; + } + // shouldn't reach here + return null; + } + + /** + * This function sets up runs animations on all of the views that change during layout. + * For every child in the parent, we create a change animation of the appropriate + * type (appearing or disappearing) and ask it to populate its start values from its + * target view. We add layout listeners to all child views and listen for changes. For + * those views that change, we populate the end values for those animations and start them. + * Animations are not run on unchanging views. + * + * @param parent The container which is undergoing an appearing or disappearing change. + * @param newView The view being added to or removed from the parent. + * @param changeReason A value of APPEARING or DISAPPEARING, indicating whether the + * transition is occuring because an item is being added to or removed from the parent. + */ + private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) { + // reset the inter-animation delay, in case we use it later + staggerDelay = 0; + + final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup + int numChildren = parent.getChildCount(); + + for (int i = 0; i < numChildren; ++i) { + final View child = parent.getChildAt(i); + + // only animate the views not being added or removed + if (child != newView) { + + // If there's an animation running on this view already, cancel it + Animatable currentAnimation = currentAnimations.get(child); + if (currentAnimation != null) { + currentAnimation.cancel(); + currentAnimations.remove(child); + } + + // Make a copy of the appropriate animation + final Animatable anim = (changeReason == APPEARING) ? + mChangingAppearingAnim.clone() : + mChangingDisappearingAnim.clone(); + + // Set the target object for the animation + anim.setTarget(child); + + // A PropertyAnimator (or Sequencer of them) can extract start values from + // its target object + anim.setupStartValues(); + + // Add a listener to track layout changes on this view. If we don't get a callback, + // then there's nothing to animate. + View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() { + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + + // Cache the animation in case we need to cancel it later + currentAnimations.put(child, anim); + + // Tell the animation to extract end values from the changed object + anim.setupEndValues(); + + long startDelay; + long duration; + if (changeReason == APPEARING) { + startDelay = mChangingAppearingDelay + staggerDelay; + staggerDelay += mChangingAppearingStagger; + duration = mChangingAppearingDuration; + } else { + startDelay = mChangingDisappearingDelay + staggerDelay; + staggerDelay += mChangingDisappearingStagger; + duration = mChangingDisappearingDuration; + } + anim.setStartDelay(startDelay); + anim.setDuration(duration); + + // Remove the animation from the cache when it ends + anim.addListener(new AnimatableListenerAdapter() { + private boolean canceled = false; + public void onAnimationCancel(Animatable animatable) { + // we remove canceled animations immediately, not here + canceled = true; + } + public void onAnimationEnd(Animatable animatable) { + if (!canceled) { + currentAnimations.remove(child); + } + } + }); + if (anim instanceof PropertyAnimator) { + ((PropertyAnimator) anim).setCurrentPlayTime(0); + } + anim.start(); + + // this only removes listeners whose views changed - must clear the + // other listeners later + child.removeOnLayoutChangeListener(this); + layoutChangeListenerMap.remove(child); + } + }; + child.addOnLayoutChangeListener(listener); + // cache the listener for later removal + layoutChangeListenerMap.put(child, listener); + } + } + // This is the cleanup step. When we get this rendering event, we know that all of + // the appropriate animations have been set up and run. Now we can clear out the + // layout listeners. + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + int numChildren = parent.getChildCount(); + for (int i = 0; i < numChildren; ++i) { + final View child = parent.getChildAt(i); + child.removeOnLayoutChangeListener(layoutChangeListenerMap.get(child)); + } + layoutChangeListenerMap.clear(); + return true; + } + }); + } + + /** + * This method runs the animation that makes an added item appear. + * + * @param parent The ViewGroup to which the View is being added. + * @param child The View being added to the ViewGroup. + */ + private void runAppearingTransition(final ViewGroup parent, final View child) { + Animatable anim = mAppearingAnim.clone(); + anim.setTarget(child); + anim.setStartDelay(mAppearingDelay); + anim.setDuration(mAppearingDuration); + if (anim instanceof PropertyAnimator) { + ((PropertyAnimator) anim).setCurrentPlayTime(0); + } + if (mListeners != null) { + anim.addListener(new AnimatableListenerAdapter() { + public void onAnimationEnd() { + for (TransitionListener listener : mListeners) { + listener.endTransition(LayoutTransition.this, parent, child, APPEARING); + } + } + }); + } + anim.start(); + } + + /** + * This method runs the animation that makes a removed item disappear. + * + * @param parent The ViewGroup from which the View is being removed. + * @param child The View being removed from the ViewGroup. + */ + private void runDisappearingTransition(final ViewGroup parent, final View child) { + Animatable anim = mDisappearingAnim.clone(); + anim.setStartDelay(mDisappearingDelay); + anim.setDuration(mDisappearingDuration); + anim.setTarget(child); + if (mListeners != null) { + anim.addListener(new AnimatableListenerAdapter() { + public void onAnimationEnd() { + for (TransitionListener listener : mListeners) { + listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); + } + } + }); + } + if (anim instanceof PropertyAnimator) { + ((PropertyAnimator) anim).setCurrentPlayTime(0); + } + anim.start(); + } + + /** + * This method is called by ViewGroup when a child view is about to be added to the + * container. This callback starts the process of a transition; we grab the starting + * values, listen for changes to all of the children of the container, and start appropriate + * animations. + * + * @param parent The ViewGroup to which the View is being added. + * @param child The View being added to the ViewGroup. + */ + public void childAdd(ViewGroup parent, View child) { + if (mListeners != null) { + for (TransitionListener listener : mListeners) { + listener.startTransition(this, parent, child, APPEARING); + } + } + runChangeTransition(parent, child, APPEARING); + runAppearingTransition(parent, child); + } + + /** + * This method is called by ViewGroup when a child view is about to be removed from the + * container. This callback starts the process of a transition; we grab the starting + * values, listen for changes to all of the children of the container, and start appropriate + * animations. + * + * @param parent The ViewGroup from which the View is being removed. + * @param child The View being removed from the ViewGroup. + */ + public void childRemove(ViewGroup parent, View child) { + if (mListeners != null) { + for (TransitionListener listener : mListeners) { + listener.startTransition(this, parent, child, DISAPPEARING); + } + } + runChangeTransition(parent, child, DISAPPEARING); + runDisappearingTransition(parent, child); + } + + /** + * Add a listener that will be called when the bounds of the view change due to + * layout processing. + * + * @param listener The listener that will be called when layout bounds change. + */ + public void addTransitionListener(TransitionListener listener) { + if (mListeners == null) { + mListeners = new ArrayList<TransitionListener>(); + } + mListeners.add(listener); + } + + /** + * Remove a listener for layout changes. + * + * @param listener The listener for layout bounds change. + */ + public void removeTransitionListener(TransitionListener listener) { + if (mListeners == null) { + return; + } + mListeners.remove(listener); + } + + /** + * Gets the current list of listeners for layout changes. + * @return + */ + public List<TransitionListener> getTransitionListeners() { + return mListeners; + } + + /** + * This interface is used for listening to starting and ending events for transitions. + */ + public interface TransitionListener { + + /** + * This event is sent to listeners when an APPEARING or DISAPPEARING transition + * begins. + * + * @param transition The LayoutTransition sending out the event. + * @param container The ViewGroup on which the transition is playing. + * @param view The View object being added or removed from its parent. + * @param transitionType The type of transition that is beginning, either + * {@link android.animation.LayoutTransition#APPEARING} or + * {@link android.animation.LayoutTransition#DISAPPEARING}. + */ + public void startTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType); + + /** + * This event is sent to listeners when an APPEARING or DISAPPEARING transition ends. + * + * @param transition The LayoutTransition sending out the event. + * @param container The ViewGroup on which the transition is playing. + * @param view The View object being added or removed from its parent. + * @param transitionType The type of transition that is ending, either + * {@link android.animation.LayoutTransition#APPEARING} or + * {@link android.animation.LayoutTransition#DISAPPEARING}. + */ + public void endTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType); + } + +}
\ No newline at end of file diff --git a/core/java/android/animation/PropertyAnimator.java b/core/java/android/animation/PropertyAnimator.java index 8a6edcc..e555cc6 100644 --- a/core/java/android/animation/PropertyAnimator.java +++ b/core/java/android/animation/PropertyAnimator.java @@ -168,10 +168,14 @@ public final class PropertyAnimator<T> extends Animator<T> { */ @Override void initAnimation() { - super.initAnimation(); - int numValues = mValues.length; - for (int i = 0; i < numValues; ++i) { - mValues[i].setupSetterAndGetter(mTarget); + 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. + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setupSetterAndGetter(mTarget); + } + super.initAnimation(); } } @@ -190,10 +194,29 @@ public final class PropertyAnimator<T> extends Animator<T> { * * @param target The object being animated */ + @Override public void setTarget(Object target) { mTarget = target; } + @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 @@ -216,7 +239,7 @@ public final class PropertyAnimator<T> extends Animator<T> { } @Override - public PropertyAnimator clone() throws CloneNotSupportedException { + public PropertyAnimator clone() { final PropertyAnimator anim = (PropertyAnimator) super.clone(); return anim; } diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index fc829b8..b6ff54e 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -25,8 +25,12 @@ import java.util.HashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; /** + * 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 Animator or PropertyAnimator that operate on several different properties + * in parallel. */ -public class PropertyValuesHolder<T> { +public class PropertyValuesHolder<T> implements Cloneable { /** * The name of the property associated with the values. This need not be a real property, @@ -192,7 +196,7 @@ public class PropertyValuesHolder<T> { } } else { if (numKeyframes == 1) { - keyframes[0] = new Keyframe(0f, null); + keyframes[0] = new Keyframe(0f, (Object) null); keyframes[1] = new Keyframe(1f, values[0]); } else { keyframes[0] = new Keyframe(0f, values[0]); @@ -256,6 +260,8 @@ public class PropertyValuesHolder<T> { args[0] = typeVariant; try { returnVal = targetClass.getMethod(methodName, args); + // change the value type to suit + mValueType = typeVariant; return returnVal; } catch (NoSuchMethodException e) { // Swallow the error and keep trying other variants @@ -356,6 +362,63 @@ public class PropertyValuesHolder<T> { } /** + * 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) { + try { + if (mGetter == null) { + Class targetClass = target.getClass(); + setupGetter(targetClass); + } + kf.setValue((T) mGetter.invoke(target)); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); + } + } + + /** + * This function is called by PropertyAnimator 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 PropertyAnimator 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() { + ArrayList<Keyframe> keyframes = mKeyframeSet.mKeyframes; + int numKeyframes = mKeyframeSet.mKeyframes.size(); + Keyframe[] newKeyframes = new Keyframe[numKeyframes]; + for (int i = 0; i < numKeyframes; ++i) { + newKeyframes[i] = keyframes.get(i).clone(); + } + PropertyValuesHolder pvhClone = new PropertyValuesHolder(mPropertyName, + (Object[]) newKeyframes); + return pvhClone; + } + /** * 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 PropertyAnimator * to handle turning the value calculated by Animator into a value set on the object @@ -381,8 +444,9 @@ public class PropertyValuesHolder<T> { */ void init() { if (mEvaluator == null) { - mEvaluator = (mValueType == int.class) ? sIntEvaluator : - (mValueType == double.class) ? sDoubleEvaluator : sFloatEvaluator; + mEvaluator = (mValueType == int.class || mValueType == Integer.class) ? sIntEvaluator : + (mValueType == double.class || mValueType == Double.class) ? sDoubleEvaluator : + sFloatEvaluator; } } diff --git a/core/java/android/animation/Sequencer.java b/core/java/android/animation/Sequencer.java index 8779b3d..04bede0 100644 --- a/core/java/android/animation/Sequencer.java +++ b/core/java/android/animation/Sequencer.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; import java.util.ArrayList; import java.util.HashMap; @@ -96,6 +97,16 @@ public final class Sequencer extends Animatable { */ boolean mCanceled = false; + // The amount of time in ms to delay starting the animation after start() is called + private long mStartDelay = 0; + + + // 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 Sequencer. When a real duration is + // set, it is passed along to the child animations. + private long mDuration = -1; + + /** * Sets up this Sequencer to play all of the supplied animations at the same time. * @@ -153,6 +164,7 @@ public final class Sequencer extends Animatable { * * @param target The object being animated */ + @Override public void setTarget(Object target) { for (Node node : mNodes) { Animatable animation = node.animation; @@ -165,6 +177,19 @@ public final class Sequencer extends Animatable { } /** + * Sets the Interpolator for all current {@link #getChildAnimations() child animations} + * of this Sequencer. + * + * @param interpolator the interpolator to be used by each child animation of this Sequencer + */ + @Override + public void setInterpolator(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 @@ -266,6 +291,62 @@ public final class Sequencer extends Animatable { } /** + * 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 Sequencer. This value may + * be less than 0, which indicates that no duration has been set on this Sequencer + * 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 Sequencer. + */ + @Override + public long getDuration() { + return mDuration; + } + + /** + * Sets the length of each of the current child animations of this Sequencer. By default, + * each child animation will use its own duration. If the duration is set on the Sequencer, + * then each child animation inherits this duration. + * + * @param duration The length of the animation, in milliseconds, of each of the child + * animations of this Sequencer. + */ + @Override + public void 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 Sequencer to + // insert "play-after" delays + node.animation.setDuration(duration); + } + mDuration = duration; + } + + /** * {@inheritDoc} * * <p>Starting this <code>Sequencer</code> will, in turn, start the animations for which @@ -285,7 +366,7 @@ public final class Sequencer extends Animatable { // 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. - ArrayList<Node> nodesToStart = new ArrayList<Node>(); + final ArrayList<Node> nodesToStart = new ArrayList<Node>(); for (Node node : mSortedNodes) { if (mSequenceListener == null) { mSequenceListener = new SequencerAnimatableListener(this); @@ -302,9 +383,22 @@ public final class Sequencer extends Animatable { node.animation.addListener(mSequenceListener); } // Now that all dependencies are set up, start the animations that should be started. - for (Node node : nodesToStart) { - node.animation.start(); - mPlayingSet.add(node.animation); + if (mStartDelay <= 0) { + for (Node node : nodesToStart) { + node.animation.start(); + mPlayingSet.add(node.animation); + } + } else { + // TODO: Need to cancel out of the delay appropriately + Animator delayAnim = new Animator(mStartDelay, 0f, 1f); + delayAnim.addListener(new AnimatableListenerAdapter() { + public void onAnimationEnd(Animatable anim) { + for (Node node : nodesToStart) { + node.animation.start(); + mPlayingSet.add(node.animation); + } + } + }); } if (mListeners != null) { ArrayList<AnimatableListener> tmpListeners = @@ -316,7 +410,7 @@ public final class Sequencer extends Animatable { } @Override - public Sequencer clone() throws CloneNotSupportedException { + public Sequencer clone() { final Sequencer anim = (Sequencer) super.clone(); /* * The basic clone() operation copies all items. This doesn't work very well for @@ -688,10 +782,14 @@ public final class Sequencer extends Animatable { } @Override - public Node clone() throws CloneNotSupportedException { - Node node = (Node) super.clone(); - node.animation = (Animatable) animation.clone(); - return node; + public Node clone() { + try { + Node node = (Node) super.clone(); + node.animation = (Animatable) animation.clone(); + return node; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e4be0fb..47dae03 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -72,6 +72,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.WeakHashMap; /** @@ -1837,6 +1838,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility protected OnFocusChangeListener mOnFocusChangeListener; /** + * Listeners for layout change events. + */ + private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners; + + /** * Listener used to dispatch click events. * This field should be made private, so it is hidden from the SDK. * {@hide} @@ -2495,6 +2501,39 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Add a listener that will be called when the bounds of the view change due to + * layout processing. + * + * @param listener The listener that will be called when layout bounds change. + */ + public void addOnLayoutChangeListener(OnLayoutChangeListener listener) { + if (mOnLayoutChangeListeners == null) { + mOnLayoutChangeListeners = new ArrayList<OnLayoutChangeListener>(); + } + mOnLayoutChangeListeners.add(listener); + } + + /** + * Remove a listener for layout changes. + * + * @param listener The listener for layout bounds change. + */ + public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) { + if (mOnLayoutChangeListeners == null) { + return; + } + mOnLayoutChangeListeners.remove(listener); + } + + /** + * Gets the current list of listeners for layout changes. + * @return + */ + public List<OnLayoutChangeListener> getOnLayoutChangeListeners() { + return mOnLayoutChangeListeners; + } + + /** * Returns the focus-change callback registered for this view. * * @return The callback, or null if one is not registered. @@ -4805,6 +4844,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Interface definition for a callback to be invoked when the layout bounds of a view + * changes due to layout processing. + */ + public interface OnLayoutChangeListener { + /** + * Called when the focus state of a view has changed. + * + * @param v The view whose state has changed. + * @param left The new value of the view's left property. + * @param top The new value of the view's top property. + * @param right The new value of the view's right property. + * @param bottom The new value of the view's bottom property. + * @param oldLeft The previous value of the view's left property. + * @param oldTop The previous value of the view's top property. + * @param oldRight The previous value of the view's right property. + * @param oldBottom The previous value of the view's bottom property. + */ + void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom); + } + + /** * This is called during layout when the size of this view has changed. If * you were just added to the view hierarchy, you're called with the old * values of 0. @@ -5265,6 +5326,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Sets the top position of this view relative to its parent. This method is meant to be called + * by the layout system and should not generally be called otherwise, because the property + * may be changed at any time by the layout. + * + * @param top The top of this view, in pixels. + */ + public final void setTop(int top) { + if (top != mTop) { + if (hasIdentityMatrix()) { + final ViewParent p = mParent; + if (p != null && mAttachInfo != null) { + final Rect r = mAttachInfo.mTmpInvalRect; + int minTop; + int yLoc; + if (top < mTop) { + minTop = top; + yLoc = top - mTop; + } else { + minTop = mTop; + yLoc = 0; + } + r.set(0, yLoc, mRight - mLeft, mBottom - minTop); + p.invalidateChild(this, r); + } + } else { + // Double-invalidation is necessary to capture view's old and new areas + invalidate(); + } + + mTop = top; + + if (!mMatrixIsIdentity) { + mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + invalidate(); + } + } + } + + /** * Bottom position of this view relative to its parent. * * @return The bottom of this view, in pixels. @@ -5275,6 +5375,42 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Sets the bottom position of this view relative to its parent. This method is meant to be + * called by the layout system and should not generally be called otherwise, because the + * property may be changed at any time by the layout. + * + * @param bottom The bottom of this view, in pixels. + */ + public final void setBottom(int bottom) { + if (bottom != mBottom) { + if (hasIdentityMatrix()) { + final ViewParent p = mParent; + if (p != null && mAttachInfo != null) { + final Rect r = mAttachInfo.mTmpInvalRect; + int maxBottom; + if (bottom < mBottom) { + maxBottom = mBottom; + } else { + maxBottom = bottom; + } + r.set(0, 0, mRight - mLeft, maxBottom - mTop); + p.invalidateChild(this, r); + } + } else { + // Double-invalidation is necessary to capture view's old and new areas + invalidate(); + } + + mBottom = bottom; + + if (!mMatrixIsIdentity) { + mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + invalidate(); + } + } + } + + /** * Left position of this view relative to its parent. * * @return The left edge of this view, in pixels. @@ -5285,6 +5421,46 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Sets the left position of this view relative to its parent. This method is meant to be called + * by the layout system and should not generally be called otherwise, because the property + * may be changed at any time by the layout. + * + * @param left The bottom of this view, in pixels. + */ + public final void setLeft(int left) { + if (left != mLeft) { + System.out.println("view " + this + " left = " + left); + if (hasIdentityMatrix()) { + final ViewParent p = mParent; + if (p != null && mAttachInfo != null) { + final Rect r = mAttachInfo.mTmpInvalRect; + int minLeft; + int xLoc; + if (left < mLeft) { + minLeft = left; + xLoc = left - mLeft; + } else { + minLeft = mLeft; + xLoc = 0; + } + r.set(xLoc, 0, mRight - minLeft, mBottom - mTop); + p.invalidateChild(this, r); + } + } else { + // Double-invalidation is necessary to capture view's old and new areas + invalidate(); + } + + mLeft = left; + + if (!mMatrixIsIdentity) { + mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + invalidate(); + } + } + } + + /** * Right position of this view relative to its parent. * * @return The right edge of this view, in pixels. @@ -5295,6 +5471,42 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Sets the right position of this view relative to its parent. This method is meant to be called + * by the layout system and should not generally be called otherwise, because the property + * may be changed at any time by the layout. + * + * @param right The bottom of this view, in pixels. + */ + public final void setRight(int right) { + if (right != mRight) { + if (hasIdentityMatrix()) { + final ViewParent p = mParent; + if (p != null && mAttachInfo != null) { + final Rect r = mAttachInfo.mTmpInvalRect; + int maxRight; + if (right < mRight) { + maxRight = mRight; + } else { + maxRight = right; + } + r.set(0, 0, maxRight - mLeft, mBottom - mTop); + p.invalidateChild(this, r); + } + } else { + // Double-invalidation is necessary to capture view's old and new areas + invalidate(); + } + + mRight = right; + + if (!mMatrixIsIdentity) { + mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + invalidate(); + } + } + } + + /** * The visual x position of this view, in pixels. This is equivalent to the * {@link #setTranslationX(float) translationX} property plus the current * {@link #getLeft() left} property. @@ -7741,7 +7953,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * Derived classes with children should override * onLayout. In that method, they should - * call layout on each of their their children. + * call layout on each of their children. * * @param l Left position, relative to parent * @param t Top position, relative to parent @@ -7749,6 +7961,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @param b Bottom position, relative to parent */ public final void layout(int l, int t, int r, int b) { + int oldL = mLeft; + int oldT = mTop; + int oldB = mBottom; + int oldR = mRight; boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { @@ -7757,6 +7973,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; + + if (mOnLayoutChangeListeners != null) { + ArrayList<OnLayoutChangeListener> listenersCopy = + (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone(); + int numListeners = listenersCopy.size(); + for (int i = 0; i < numListeners; ++i) { + listenersCopy.get(i).onLayoutChange(this, l, r, t, b, oldL, oldT, oldR, oldB); + } + } } mPrivateFlags &= ~FORCE_LAYOUT; } @@ -7767,7 +7992,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * Derived classes with children should override * this method and call layout on each of - * their their children. + * their children. * @param changed This is a new size or position for this view * @param left Left position, relative to parent * @param top Top position, relative to parent diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index dfe4295..649f3e7 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -16,6 +16,7 @@ package android.view; +import android.animation.LayoutTransition; import com.android.internal.R; import android.content.Context; @@ -66,6 +67,7 @@ import java.util.ArrayList; * @attr ref android.R.styleable#ViewGroup_descendantFocusability */ public abstract class ViewGroup extends View implements ViewParent, ViewManager { + private static final boolean DBG = false; /** @@ -298,6 +300,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Used to draw cached views private final Paint mCachePaint = new Paint(); + // Used to animate add/remove changes in layout + private LayoutTransition mTransition; + + // The set of views that are currently being transitioned. This list is used to track views + // being removed that should not actually be removed from the parent yet because they are + // being animated. + private ArrayList<View> mTransitioningViews; + public ViewGroup(Context context) { super(context); initViewGroup(); @@ -2327,6 +2337,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager "You must call removeView() on the child's parent first."); } + if (mTransition != null) { + mTransition.childAdd(this, child); + } + if (!checkLayoutParams(params)) { params = generateLayoutParams(params); } @@ -2404,7 +2418,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // This method also sets the child's mParent to null private void removeFromArray(int index) { final View[] children = mChildren; - children[index].mParent = null; + if (!(mTransitioningViews != null && mTransitioningViews.contains(children[index]))) { + children[index].mParent = null; + } final int count = mChildrenCount; if (index == count - 1) { children[--mChildrenCount] = null; @@ -2539,13 +2555,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } private void removeViewInternal(int index, View view) { + + if (mTransition != null) { + mTransition.childRemove(this, view); + } + boolean clearChildFocus = false; if (view == mFocused) { view.clearFocusForRemoval(); clearChildFocus = true; } - if (view.getAnimation() != null) { + if (view.getAnimation() != null || + (mTransitioningViews != null && mTransitioningViews.contains(view))) { addDisappearingView(view); } else if (view.mAttachInfo != null) { view.dispatchDetachedFromWindow(); @@ -2564,6 +2586,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + /** + * Sets the LayoutTransition object for this ViewGroup. If the LayoutTransition object is + * not null, changes in layout which occur because of children being added to or removed from + * the ViewGroup will be animated according to the animations defined in that LayoutTransition + * object. By default, the transition object is null (so layout changes are not animated). + * + * @param transition The LayoutTransition object that will animated changes in layout. A value + * of <code>null</code> means no transition will run on layout changes. + */ + public void setLayoutTransition(LayoutTransition transition) { + mTransition = transition; + mTransition.addTransitionListener(mLayoutTransitionListener); + } + private void removeViewsInternal(int start, int count) { final OnHierarchyChangeListener onHierarchyChangeListener = mOnHierarchyChangeListener; final boolean notifyListener = onHierarchyChangeListener != null; @@ -2577,12 +2613,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = start; i < end; i++) { final View view = children[i]; + if (mTransition != null) { + mTransition.childRemove(this, view); + } + if (view == focused) { view.clearFocusForRemoval(); clearChildFocus = view; } - if (view.getAnimation() != null) { + if (view.getAnimation() != null || + (mTransitioningViews != null && mTransitioningViews.contains(view))) { addDisappearingView(view); } else if (detach) { view.dispatchDetachedFromWindow(); @@ -2641,12 +2682,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = count - 1; i >= 0; i--) { final View view = children[i]; + if (mTransition != null) { + mTransition.childRemove(this, view); + } + if (view == focused) { view.clearFocusForRemoval(); clearChildFocus = view; } - if (view.getAnimation() != null) { + if (view.getAnimation() != null || + (mTransitioningViews != null && mTransitioningViews.contains(view))) { addDisappearingView(view); } else if (detach) { view.dispatchDetachedFromWindow(); @@ -2679,11 +2725,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @see #detachViewFromParent(int) */ protected void removeDetachedView(View child, boolean animate) { + if (mTransition != null) { + mTransition.childRemove(this, child); + } + if (child == mFocused) { child.clearFocus(); } - if (animate && child.getAnimation() != null) { + if ((animate && child.getAnimation() != null) || + (mTransitioningViews != null && mTransitioningViews.contains(child))) { addDisappearingView(child); } else if (child.mAttachInfo != null) { child.dispatchDetachedFromWindow(); @@ -3658,6 +3709,41 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + private LayoutTransition.TransitionListener mLayoutTransitionListener = + new LayoutTransition.TransitionListener() { + @Override + public void startTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType) { + // We only care about disappearing items, since we need special logic to keep + // those items visible after they've been 'removed' + if (transitionType == LayoutTransition.DISAPPEARING) { + if (mTransitioningViews == null) { + mTransitioningViews = new ArrayList<View>(); + } + mTransitioningViews.add(view); + } + } + + @Override + public void endTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType) { + if (transitionType == LayoutTransition.DISAPPEARING && mTransitioningViews != null) { + mTransitioningViews.remove(view); + final ArrayList<View> disappearingChildren = mDisappearingChildren; + if (disappearingChildren != null && disappearingChildren.contains(view)) { + disappearingChildren.remove(view); + if (view.mAttachInfo != null) { + view.dispatchDetachedFromWindow(); + } + if (view.mParent != null) { + view.mParent = null; + } + mGroupFlags |= FLAG_INVALIDATE_REQUIRED; + } + } + } + }; + /** * {@inheritDoc} */ |
