Merge "Add transition effects for layout changes on ViewGroups"
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 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 @@
}
@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 @@
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 @@
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 @@
}
}
+ /**
+ * 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 @@
}
/**
+ * 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 @@
* 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 @@
* @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 @@
*
* @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 @@
* 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 @@
}
@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 @@
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 @@
* 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 @@
* 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 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 @@
*/
@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 @@
*
* @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 @@
}
@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.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 @@
}
} 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 @@
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 @@
}
/**
+ * 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 @@
*/
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.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 @@
*/
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 @@
*
* @param target The object being animated
*/
+ @Override
public void setTarget(Object target) {
for (Node node : mNodes) {
Animatable animation = node.animation;
@@ -165,6 +177,19 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
// 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 @@
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 @@
}
@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 @@
}
@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 e4be0fb44..47dae03 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -72,6 +72,7 @@
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 @@
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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
*
* 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 @@
* @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 @@
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 @@
*
* 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 @@
* @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 @@
// 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 @@
"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 @@
// 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 @@
}
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 @@
}
}
+ /**
+ * 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 @@
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 @@
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 @@
* @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 @@
}
}
+ 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}
*/