Animatable is the new AnimatedValue

Bug: 168014930
Test: New unit tests added

Relnote: "
New coroutine-based API `Animatable` that ensures mutual exclusiveness
among its animations.
New DecayAnimationSpec to support multi-dimensional decay animation
"

Change-Id: I820f29e24eaa512515c776db971444290dea97e9
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index fa36b55..cde0818 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -5,6 +5,35 @@
     method @VisibleForTesting public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function1<? super kotlinx.coroutines.CoroutineScope,? extends androidx.compose.animation.core.AnimationClockObservable> p);
   }
 
+  public final class Animatable<T, V extends androidx.compose.animation.core.AnimationVector> {
+    ctor public Animatable(T? initialValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? visibilityThreshold);
+    method public suspend Object? animateDecay(T? initialVelocity, androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Animatable<T,V>,kotlin.Unit>? block, optional kotlin.coroutines.Continuation<? super androidx.compose.animation.core.AnimationResult<T,V>> p);
+    method public suspend Object? animateTo(T? targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? initialVelocity, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Animatable<T,V>,kotlin.Unit>? block, optional kotlin.coroutines.Continuation<? super androidx.compose.animation.core.AnimationResult<T,V>> p);
+    method public T? getLowerBound();
+    method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
+    method public T? getUpperBound();
+    method public T! getValue();
+    method public T! getVelocity();
+    method public V getVelocityVector();
+    method public boolean isRunning();
+    method public void snapTo(T? targetValue);
+    method public void stop();
+    method public void updateBounds(optional T? lowerBound, optional T? upperBound);
+    property public final boolean isRunning;
+    property public final T? lowerBound;
+    property public final T! targetValue;
+    property public final androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
+    property public final T? upperBound;
+    property public final T! value;
+    property public final T! velocity;
+    property public final V velocityVector;
+  }
+
+  public final class AnimatableKt {
+    method public static androidx.compose.animation.core.Animatable<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> Animatable(float initialValue, optional float visibilityThreshold);
+  }
+
   public final class AnimateAsStateKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
@@ -47,15 +76,17 @@
   }
 
   public interface Animation<T, V extends androidx.compose.animation.core.AnimationVector> {
-    method public androidx.compose.animation.core.TwoWayConverter<T,V> getConverter();
+    method @Deprecated public default androidx.compose.animation.core.TwoWayConverter<T,V> getConverter();
     method public long getDurationMillis();
     method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue(long playTime);
     method public V getVelocityVector(long playTime);
     method public default boolean isFinished(long playTime);
-    property public abstract androidx.compose.animation.core.TwoWayConverter<T,V> converter;
+    property @Deprecated public default androidx.compose.animation.core.TwoWayConverter<T,V> converter;
     property public abstract long durationMillis;
     property public abstract T! targetValue;
+    property public abstract androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
   }
 
   public interface AnimationClockObservable {
@@ -75,12 +106,23 @@
 
   public enum AnimationEndReason {
     enum_constant public static final androidx.compose.animation.core.AnimationEndReason BoundReached;
+    enum_constant public static final androidx.compose.animation.core.AnimationEndReason Finished;
     enum_constant public static final androidx.compose.animation.core.AnimationEndReason Interrupted;
     enum_constant public static final androidx.compose.animation.core.AnimationEndReason TargetReached;
   }
 
   public final class AnimationKt {
-    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.TargetBasedAnimation<T,V> TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, T? initialValue, T? targetValue, T? initialVelocity, androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+    method public static androidx.compose.animation.core.DecayAnimation<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> DecayAnimation(androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, float initialValue, optional float initialVelocity);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.TargetBasedAnimation<T,V> TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? targetValue, T? initialVelocity);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> T! getVelocity(androidx.compose.animation.core.Animation<T,V>, long playTime);
+  }
+
+  public final class AnimationResult<T, V extends androidx.compose.animation.core.AnimationVector> {
+    ctor public AnimationResult(androidx.compose.animation.core.AnimationState<T,V> endState, androidx.compose.animation.core.AnimationEndReason endReason);
+    method public androidx.compose.animation.core.AnimationEndReason getEndReason();
+    method public androidx.compose.animation.core.AnimationState<T,V> getEndState();
+    property public final androidx.compose.animation.core.AnimationEndReason endReason;
+    property public final androidx.compose.animation.core.AnimationState<T,V> endState;
   }
 
   public final class AnimationScope<T, V extends androidx.compose.animation.core.AnimationVector> {
@@ -91,6 +133,7 @@
     method public T! getTargetValue();
     method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue();
+    method public T! getVelocity();
     method public V getVelocityVector();
     method public boolean isRunning();
     method public androidx.compose.animation.core.AnimationState<T,V> toAnimationState();
@@ -101,6 +144,7 @@
     property public final T! targetValue;
     property public final androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
     property public final T! value;
+    property public final T! velocity;
     property public final V velocityVector;
   }
 
@@ -116,6 +160,7 @@
     method public long getLastFrameTime-CLVl0cY();
     method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue();
+    method public T! getVelocity();
     method public V getVelocityVector();
     method public boolean isRunning();
     property public final long finishedTime;
@@ -123,16 +168,16 @@
     property public final long lastFrameTime;
     property public final androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
     property public T! value;
+    property public final T! velocity;
     property public final V velocityVector;
   }
 
   public final class AnimationStateKt {
-    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> AnimationState-SZyJPdc(float initialValue, optional float initialVelocity, optional long lastFrameTime, optional long endTime, optional boolean isRunning);
-    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> copy-mN48zRs(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, optional float value, optional float velocity, optional long lastFrameTime, optional long endTime, optional boolean isRunning);
-    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.AnimationState<T,V> copy-upwry94(androidx.compose.animation.core.AnimationState<T,V>, optional T? value, optional V? velocityVector, optional long lastFrameTime, optional long endTime, optional boolean isRunning);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.AnimationState<T,V> AnimationState-NYxC2dI(androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? initialVelocity, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
+    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> AnimationState-SZyJPdc(float initialValue, optional float initialVelocity, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
+    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> copy-mN48zRs(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, optional float value, optional float velocity, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.AnimationState<T,V> copy-upwry94(androidx.compose.animation.core.AnimationState<T,V>, optional T? value, optional V? velocityVector, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
     method public static <T, V extends androidx.compose.animation.core.AnimationVector> V createZeroVectorFrom(androidx.compose.animation.core.TwoWayConverter<T,V>, T? value);
-    method public static float getVelocity(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>);
-    method public static float getVelocity(androidx.compose.animation.core.AnimationScope<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>);
     method public static boolean isFinished(androidx.compose.animation.core.AnimationState<?,?>);
   }
 
@@ -234,16 +279,33 @@
     method public Float! invoke(float fraction);
   }
 
-  public final class DecayAnimation implements androidx.compose.animation.core.Animation<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
-    ctor public DecayAnimation(androidx.compose.animation.core.FloatDecayAnimationSpec anim, float initialValue, float initialVelocity);
-    method public androidx.compose.animation.core.TwoWayConverter<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> getConverter();
+  public final class DecayAnimation<T, V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.Animation<T,V> {
+    ctor @VisibleForTesting public DecayAnimation(androidx.compose.animation.core.VectorizedDecayAnimationSpec<V> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, V initialVelocityVector);
+    ctor public DecayAnimation(androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, V initialVelocityVector);
+    ctor public DecayAnimation(androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? initialVelocity);
     method public long getDurationMillis();
-    method public Float! getTargetValue();
-    method public Float! getValue(long playTime);
-    method public androidx.compose.animation.core.AnimationVector1D getVelocityVector(long playTime);
-    property public androidx.compose.animation.core.TwoWayConverter<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> converter;
+    method public T! getInitialValue();
+    method public V getInitialVelocityVector();
+    method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
+    method public T! getValue(long playTime);
+    method public V getVelocityVector(long playTime);
     property public long durationMillis;
-    property public Float! targetValue;
+    property public final T! initialValue;
+    property public final V initialVelocityVector;
+    property public T! targetValue;
+    property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
+  }
+
+  public interface DecayAnimationSpec<T> {
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedDecayAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter);
+  }
+
+  public final class DecayAnimationSpecKt {
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> T! calculateTargetValue(androidx.compose.animation.core.DecayAnimationSpec<T>, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? initialVelocity);
+    method public static float calculateTargetValue(androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>, float initialValue, float initialVelocity);
+    method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> exponentialDecay(optional @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float frictionMultiplier, optional @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float absVelocityThreshold);
+    method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> generateDecayAnimationSpec(androidx.compose.animation.core.FloatDecayAnimationSpec);
   }
 
   public final class DefaultAnimationClock extends androidx.compose.animation.core.BaseAnimationClock {
@@ -262,17 +324,6 @@
     method public static androidx.compose.animation.core.CubicBezierEasing getLinearOutSlowInEasing();
   }
 
-  public final class ExponentialDecay implements androidx.compose.animation.core.FloatDecayAnimationSpec {
-    ctor public ExponentialDecay(@FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float frictionMultiplier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float absVelocityThreshold);
-    ctor public ExponentialDecay();
-    method public float getAbsVelocityThreshold();
-    method public long getDurationMillis(float start, float startVelocity);
-    method public float getTarget(float start, float startVelocity);
-    method public float getValue(long playTime, float start, float startVelocity);
-    method public float getVelocity(long playTime, float start, float startVelocity);
-    property public float absVelocityThreshold;
-  }
-
   public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
     method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
   }
@@ -297,6 +348,17 @@
   public final class FloatDecayAnimationSpecKt {
   }
 
+  public final class FloatExponentialDecaySpec implements androidx.compose.animation.core.FloatDecayAnimationSpec {
+    ctor public FloatExponentialDecaySpec(@FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float frictionMultiplier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float absVelocityThreshold);
+    ctor public FloatExponentialDecaySpec();
+    method public float getAbsVelocityThreshold();
+    method public long getDurationMillis(float start, float startVelocity);
+    method public float getTarget(float start, float startVelocity);
+    method public float getValue(long playTime, float start, float startVelocity);
+    method public float getVelocity(long playTime, float start, float startVelocity);
+    property public float absVelocityThreshold;
+  }
+
   public final class FloatPropKey implements androidx.compose.animation.core.PropKey<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
     ctor public FloatPropKey(String label);
     ctor public FloatPropKey();
@@ -484,9 +546,10 @@
 
   public final class SuspendAnimationKt {
     method public static suspend Object? animate(float initialValue, float targetValue, optional float initialVelocity, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animate(T? initialValue, T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional V? initialVelocityVector, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animate(androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? targetValue, optional T? initialVelocity, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, kotlin.jvm.functions.Function2<? super T,? super T,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public static suspend Object? animateDecay(float initialValue, float initialVelocity, androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method public static suspend Object? animateDecay(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method @Deprecated public static suspend Object? animateDecay(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animateDecay(androidx.compose.animation.core.AnimationState<T,V>, androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<T,V>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animateTo(androidx.compose.animation.core.AnimationState<T,V>, T? targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<T,V>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
@@ -502,15 +565,15 @@
   }
 
   public final class TargetBasedAnimation<T, V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.Animation<T,V> {
-    ctor public TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, T? initialValue, T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> converter, V? initialVelocityVector);
-    method public androidx.compose.animation.core.TwoWayConverter<T,V> getConverter();
+    ctor public TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? targetValue, V? initialVelocityVector);
     method public long getDurationMillis();
     method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue(long playTime);
     method public V getVelocityVector(long playTime);
-    property public androidx.compose.animation.core.TwoWayConverter<T,V> converter;
     property public long durationMillis;
     property public T! targetValue;
+    property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
   }
 
   public final class ToolingGlueKt {
@@ -644,6 +707,15 @@
   public final class VectorizedAnimationSpecKt {
   }
 
+  public interface VectorizedDecayAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> {
+    method public float getAbsVelocityThreshold();
+    method public long getDurationMillis(V initialValue, V initialVelocity);
+    method public V getTarget(V initialValue, V initialVelocity);
+    method public V getValue(long playTime, V initialValue, V initialVelocity);
+    method public V getVelocity(long playTime, V initialValue, V initialVelocity);
+    property public abstract float absVelocityThreshold;
+  }
+
   public interface VectorizedDurationBasedAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     method public int getDelayMillis();
     method public int getDurationMillis();
diff --git a/compose/animation/animation-core/api/public_plus_experimental_current.txt b/compose/animation/animation-core/api/public_plus_experimental_current.txt
index fa36b55..cde0818 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_current.txt
@@ -5,6 +5,35 @@
     method @VisibleForTesting public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function1<? super kotlinx.coroutines.CoroutineScope,? extends androidx.compose.animation.core.AnimationClockObservable> p);
   }
 
+  public final class Animatable<T, V extends androidx.compose.animation.core.AnimationVector> {
+    ctor public Animatable(T? initialValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? visibilityThreshold);
+    method public suspend Object? animateDecay(T? initialVelocity, androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Animatable<T,V>,kotlin.Unit>? block, optional kotlin.coroutines.Continuation<? super androidx.compose.animation.core.AnimationResult<T,V>> p);
+    method public suspend Object? animateTo(T? targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? initialVelocity, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Animatable<T,V>,kotlin.Unit>? block, optional kotlin.coroutines.Continuation<? super androidx.compose.animation.core.AnimationResult<T,V>> p);
+    method public T? getLowerBound();
+    method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
+    method public T? getUpperBound();
+    method public T! getValue();
+    method public T! getVelocity();
+    method public V getVelocityVector();
+    method public boolean isRunning();
+    method public void snapTo(T? targetValue);
+    method public void stop();
+    method public void updateBounds(optional T? lowerBound, optional T? upperBound);
+    property public final boolean isRunning;
+    property public final T? lowerBound;
+    property public final T! targetValue;
+    property public final androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
+    property public final T? upperBound;
+    property public final T! value;
+    property public final T! velocity;
+    property public final V velocityVector;
+  }
+
+  public final class AnimatableKt {
+    method public static androidx.compose.animation.core.Animatable<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> Animatable(float initialValue, optional float visibilityThreshold);
+  }
+
   public final class AnimateAsStateKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
@@ -47,15 +76,17 @@
   }
 
   public interface Animation<T, V extends androidx.compose.animation.core.AnimationVector> {
-    method public androidx.compose.animation.core.TwoWayConverter<T,V> getConverter();
+    method @Deprecated public default androidx.compose.animation.core.TwoWayConverter<T,V> getConverter();
     method public long getDurationMillis();
     method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue(long playTime);
     method public V getVelocityVector(long playTime);
     method public default boolean isFinished(long playTime);
-    property public abstract androidx.compose.animation.core.TwoWayConverter<T,V> converter;
+    property @Deprecated public default androidx.compose.animation.core.TwoWayConverter<T,V> converter;
     property public abstract long durationMillis;
     property public abstract T! targetValue;
+    property public abstract androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
   }
 
   public interface AnimationClockObservable {
@@ -75,12 +106,23 @@
 
   public enum AnimationEndReason {
     enum_constant public static final androidx.compose.animation.core.AnimationEndReason BoundReached;
+    enum_constant public static final androidx.compose.animation.core.AnimationEndReason Finished;
     enum_constant public static final androidx.compose.animation.core.AnimationEndReason Interrupted;
     enum_constant public static final androidx.compose.animation.core.AnimationEndReason TargetReached;
   }
 
   public final class AnimationKt {
-    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.TargetBasedAnimation<T,V> TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, T? initialValue, T? targetValue, T? initialVelocity, androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+    method public static androidx.compose.animation.core.DecayAnimation<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> DecayAnimation(androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, float initialValue, optional float initialVelocity);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.TargetBasedAnimation<T,V> TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? targetValue, T? initialVelocity);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> T! getVelocity(androidx.compose.animation.core.Animation<T,V>, long playTime);
+  }
+
+  public final class AnimationResult<T, V extends androidx.compose.animation.core.AnimationVector> {
+    ctor public AnimationResult(androidx.compose.animation.core.AnimationState<T,V> endState, androidx.compose.animation.core.AnimationEndReason endReason);
+    method public androidx.compose.animation.core.AnimationEndReason getEndReason();
+    method public androidx.compose.animation.core.AnimationState<T,V> getEndState();
+    property public final androidx.compose.animation.core.AnimationEndReason endReason;
+    property public final androidx.compose.animation.core.AnimationState<T,V> endState;
   }
 
   public final class AnimationScope<T, V extends androidx.compose.animation.core.AnimationVector> {
@@ -91,6 +133,7 @@
     method public T! getTargetValue();
     method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue();
+    method public T! getVelocity();
     method public V getVelocityVector();
     method public boolean isRunning();
     method public androidx.compose.animation.core.AnimationState<T,V> toAnimationState();
@@ -101,6 +144,7 @@
     property public final T! targetValue;
     property public final androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
     property public final T! value;
+    property public final T! velocity;
     property public final V velocityVector;
   }
 
@@ -116,6 +160,7 @@
     method public long getLastFrameTime-CLVl0cY();
     method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue();
+    method public T! getVelocity();
     method public V getVelocityVector();
     method public boolean isRunning();
     property public final long finishedTime;
@@ -123,16 +168,16 @@
     property public final long lastFrameTime;
     property public final androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
     property public T! value;
+    property public final T! velocity;
     property public final V velocityVector;
   }
 
   public final class AnimationStateKt {
-    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> AnimationState-SZyJPdc(float initialValue, optional float initialVelocity, optional long lastFrameTime, optional long endTime, optional boolean isRunning);
-    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> copy-mN48zRs(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, optional float value, optional float velocity, optional long lastFrameTime, optional long endTime, optional boolean isRunning);
-    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.AnimationState<T,V> copy-upwry94(androidx.compose.animation.core.AnimationState<T,V>, optional T? value, optional V? velocityVector, optional long lastFrameTime, optional long endTime, optional boolean isRunning);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.AnimationState<T,V> AnimationState-NYxC2dI(androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? initialVelocity, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
+    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> AnimationState-SZyJPdc(float initialValue, optional float initialVelocity, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
+    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> copy-mN48zRs(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, optional float value, optional float velocity, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.AnimationState<T,V> copy-upwry94(androidx.compose.animation.core.AnimationState<T,V>, optional T? value, optional V? velocityVector, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
     method public static <T, V extends androidx.compose.animation.core.AnimationVector> V createZeroVectorFrom(androidx.compose.animation.core.TwoWayConverter<T,V>, T? value);
-    method public static float getVelocity(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>);
-    method public static float getVelocity(androidx.compose.animation.core.AnimationScope<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>);
     method public static boolean isFinished(androidx.compose.animation.core.AnimationState<?,?>);
   }
 
@@ -234,16 +279,33 @@
     method public Float! invoke(float fraction);
   }
 
-  public final class DecayAnimation implements androidx.compose.animation.core.Animation<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
-    ctor public DecayAnimation(androidx.compose.animation.core.FloatDecayAnimationSpec anim, float initialValue, float initialVelocity);
-    method public androidx.compose.animation.core.TwoWayConverter<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> getConverter();
+  public final class DecayAnimation<T, V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.Animation<T,V> {
+    ctor @VisibleForTesting public DecayAnimation(androidx.compose.animation.core.VectorizedDecayAnimationSpec<V> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, V initialVelocityVector);
+    ctor public DecayAnimation(androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, V initialVelocityVector);
+    ctor public DecayAnimation(androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? initialVelocity);
     method public long getDurationMillis();
-    method public Float! getTargetValue();
-    method public Float! getValue(long playTime);
-    method public androidx.compose.animation.core.AnimationVector1D getVelocityVector(long playTime);
-    property public androidx.compose.animation.core.TwoWayConverter<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> converter;
+    method public T! getInitialValue();
+    method public V getInitialVelocityVector();
+    method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
+    method public T! getValue(long playTime);
+    method public V getVelocityVector(long playTime);
     property public long durationMillis;
-    property public Float! targetValue;
+    property public final T! initialValue;
+    property public final V initialVelocityVector;
+    property public T! targetValue;
+    property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
+  }
+
+  public interface DecayAnimationSpec<T> {
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedDecayAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter);
+  }
+
+  public final class DecayAnimationSpecKt {
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> T! calculateTargetValue(androidx.compose.animation.core.DecayAnimationSpec<T>, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? initialVelocity);
+    method public static float calculateTargetValue(androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>, float initialValue, float initialVelocity);
+    method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> exponentialDecay(optional @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float frictionMultiplier, optional @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float absVelocityThreshold);
+    method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> generateDecayAnimationSpec(androidx.compose.animation.core.FloatDecayAnimationSpec);
   }
 
   public final class DefaultAnimationClock extends androidx.compose.animation.core.BaseAnimationClock {
@@ -262,17 +324,6 @@
     method public static androidx.compose.animation.core.CubicBezierEasing getLinearOutSlowInEasing();
   }
 
-  public final class ExponentialDecay implements androidx.compose.animation.core.FloatDecayAnimationSpec {
-    ctor public ExponentialDecay(@FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float frictionMultiplier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float absVelocityThreshold);
-    ctor public ExponentialDecay();
-    method public float getAbsVelocityThreshold();
-    method public long getDurationMillis(float start, float startVelocity);
-    method public float getTarget(float start, float startVelocity);
-    method public float getValue(long playTime, float start, float startVelocity);
-    method public float getVelocity(long playTime, float start, float startVelocity);
-    property public float absVelocityThreshold;
-  }
-
   public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
     method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
   }
@@ -297,6 +348,17 @@
   public final class FloatDecayAnimationSpecKt {
   }
 
+  public final class FloatExponentialDecaySpec implements androidx.compose.animation.core.FloatDecayAnimationSpec {
+    ctor public FloatExponentialDecaySpec(@FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float frictionMultiplier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float absVelocityThreshold);
+    ctor public FloatExponentialDecaySpec();
+    method public float getAbsVelocityThreshold();
+    method public long getDurationMillis(float start, float startVelocity);
+    method public float getTarget(float start, float startVelocity);
+    method public float getValue(long playTime, float start, float startVelocity);
+    method public float getVelocity(long playTime, float start, float startVelocity);
+    property public float absVelocityThreshold;
+  }
+
   public final class FloatPropKey implements androidx.compose.animation.core.PropKey<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
     ctor public FloatPropKey(String label);
     ctor public FloatPropKey();
@@ -484,9 +546,10 @@
 
   public final class SuspendAnimationKt {
     method public static suspend Object? animate(float initialValue, float targetValue, optional float initialVelocity, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animate(T? initialValue, T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional V? initialVelocityVector, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animate(androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? targetValue, optional T? initialVelocity, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, kotlin.jvm.functions.Function2<? super T,? super T,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public static suspend Object? animateDecay(float initialValue, float initialVelocity, androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method public static suspend Object? animateDecay(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method @Deprecated public static suspend Object? animateDecay(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animateDecay(androidx.compose.animation.core.AnimationState<T,V>, androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<T,V>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animateTo(androidx.compose.animation.core.AnimationState<T,V>, T? targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<T,V>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
@@ -502,15 +565,15 @@
   }
 
   public final class TargetBasedAnimation<T, V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.Animation<T,V> {
-    ctor public TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, T? initialValue, T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> converter, V? initialVelocityVector);
-    method public androidx.compose.animation.core.TwoWayConverter<T,V> getConverter();
+    ctor public TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? targetValue, V? initialVelocityVector);
     method public long getDurationMillis();
     method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue(long playTime);
     method public V getVelocityVector(long playTime);
-    property public androidx.compose.animation.core.TwoWayConverter<T,V> converter;
     property public long durationMillis;
     property public T! targetValue;
+    property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
   }
 
   public final class ToolingGlueKt {
@@ -644,6 +707,15 @@
   public final class VectorizedAnimationSpecKt {
   }
 
+  public interface VectorizedDecayAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> {
+    method public float getAbsVelocityThreshold();
+    method public long getDurationMillis(V initialValue, V initialVelocity);
+    method public V getTarget(V initialValue, V initialVelocity);
+    method public V getValue(long playTime, V initialValue, V initialVelocity);
+    method public V getVelocity(long playTime, V initialValue, V initialVelocity);
+    property public abstract float absVelocityThreshold;
+  }
+
   public interface VectorizedDurationBasedAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     method public int getDelayMillis();
     method public int getDurationMillis();
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 85ea8c2..42995c5 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -5,6 +5,35 @@
     method @VisibleForTesting public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function1<? super kotlinx.coroutines.CoroutineScope,? extends androidx.compose.animation.core.AnimationClockObservable> p);
   }
 
+  public final class Animatable<T, V extends androidx.compose.animation.core.AnimationVector> {
+    ctor public Animatable(T? initialValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? visibilityThreshold);
+    method public suspend Object? animateDecay(T? initialVelocity, androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Animatable<T,V>,kotlin.Unit>? block, optional kotlin.coroutines.Continuation<? super androidx.compose.animation.core.AnimationResult<T,V>> p);
+    method public suspend Object? animateTo(T? targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? initialVelocity, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Animatable<T,V>,kotlin.Unit>? block, optional kotlin.coroutines.Continuation<? super androidx.compose.animation.core.AnimationResult<T,V>> p);
+    method public T? getLowerBound();
+    method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
+    method public T? getUpperBound();
+    method public T! getValue();
+    method public T! getVelocity();
+    method public V getVelocityVector();
+    method public boolean isRunning();
+    method public void snapTo(T? targetValue);
+    method public void stop();
+    method public void updateBounds(optional T? lowerBound, optional T? upperBound);
+    property public final boolean isRunning;
+    property public final T? lowerBound;
+    property public final T! targetValue;
+    property public final androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
+    property public final T? upperBound;
+    property public final T! value;
+    property public final T! velocity;
+    property public final V velocityVector;
+  }
+
+  public final class AnimatableKt {
+    method public static androidx.compose.animation.core.Animatable<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> Animatable(float initialValue, optional float visibilityThreshold);
+  }
+
   public final class AnimateAsStateKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
@@ -47,15 +76,17 @@
   }
 
   public interface Animation<T, V extends androidx.compose.animation.core.AnimationVector> {
-    method public androidx.compose.animation.core.TwoWayConverter<T,V> getConverter();
+    method @Deprecated public default androidx.compose.animation.core.TwoWayConverter<T,V> getConverter();
     method public long getDurationMillis();
     method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue(long playTime);
     method public V getVelocityVector(long playTime);
     method public default boolean isFinished(long playTime);
-    property public abstract androidx.compose.animation.core.TwoWayConverter<T,V> converter;
+    property @Deprecated public default androidx.compose.animation.core.TwoWayConverter<T,V> converter;
     property public abstract long durationMillis;
     property public abstract T! targetValue;
+    property public abstract androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
   }
 
   public interface AnimationClockObservable {
@@ -75,12 +106,23 @@
 
   public enum AnimationEndReason {
     enum_constant public static final androidx.compose.animation.core.AnimationEndReason BoundReached;
+    enum_constant public static final androidx.compose.animation.core.AnimationEndReason Finished;
     enum_constant public static final androidx.compose.animation.core.AnimationEndReason Interrupted;
     enum_constant public static final androidx.compose.animation.core.AnimationEndReason TargetReached;
   }
 
   public final class AnimationKt {
-    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.TargetBasedAnimation<T,V> TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, T? initialValue, T? targetValue, T? initialVelocity, androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+    method public static androidx.compose.animation.core.DecayAnimation<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> DecayAnimation(androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, float initialValue, optional float initialVelocity);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.TargetBasedAnimation<T,V> TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? targetValue, T? initialVelocity);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> T! getVelocity(androidx.compose.animation.core.Animation<T,V>, long playTime);
+  }
+
+  public final class AnimationResult<T, V extends androidx.compose.animation.core.AnimationVector> {
+    ctor public AnimationResult(androidx.compose.animation.core.AnimationState<T,V> endState, androidx.compose.animation.core.AnimationEndReason endReason);
+    method public androidx.compose.animation.core.AnimationEndReason getEndReason();
+    method public androidx.compose.animation.core.AnimationState<T,V> getEndState();
+    property public final androidx.compose.animation.core.AnimationEndReason endReason;
+    property public final androidx.compose.animation.core.AnimationState<T,V> endState;
   }
 
   public final class AnimationScope<T, V extends androidx.compose.animation.core.AnimationVector> {
@@ -91,6 +133,7 @@
     method public T! getTargetValue();
     method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue();
+    method public T! getVelocity();
     method public V getVelocityVector();
     method public boolean isRunning();
     method public androidx.compose.animation.core.AnimationState<T,V> toAnimationState();
@@ -101,6 +144,7 @@
     property public final T! targetValue;
     property public final androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
     property public final T! value;
+    property public final T! velocity;
     property public final V velocityVector;
   }
 
@@ -116,6 +160,7 @@
     method public long getLastFrameTime-CLVl0cY();
     method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue();
+    method public T! getVelocity();
     method public V getVelocityVector();
     method public boolean isRunning();
     property public final long finishedTime;
@@ -123,16 +168,16 @@
     property public final long lastFrameTime;
     property public final androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
     property public T! value;
+    property public final T! velocity;
     property public final V velocityVector;
   }
 
   public final class AnimationStateKt {
-    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> AnimationState-SZyJPdc(float initialValue, optional float initialVelocity, optional long lastFrameTime, optional long endTime, optional boolean isRunning);
-    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> copy-mN48zRs(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, optional float value, optional float velocity, optional long lastFrameTime, optional long endTime, optional boolean isRunning);
-    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.AnimationState<T,V> copy-upwry94(androidx.compose.animation.core.AnimationState<T,V>, optional T? value, optional V? velocityVector, optional long lastFrameTime, optional long endTime, optional boolean isRunning);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.AnimationState<T,V> AnimationState-NYxC2dI(androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? initialVelocity, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
+    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> AnimationState-SZyJPdc(float initialValue, optional float initialVelocity, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
+    method public static androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> copy-mN48zRs(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, optional float value, optional float velocity, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.AnimationState<T,V> copy-upwry94(androidx.compose.animation.core.AnimationState<T,V>, optional T? value, optional V? velocityVector, optional long lastFrameTime, optional long finishedTime, optional boolean isRunning);
     method public static <T, V extends androidx.compose.animation.core.AnimationVector> V createZeroVectorFrom(androidx.compose.animation.core.TwoWayConverter<T,V>, T? value);
-    method public static float getVelocity(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>);
-    method public static float getVelocity(androidx.compose.animation.core.AnimationScope<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>);
     method public static boolean isFinished(androidx.compose.animation.core.AnimationState<?,?>);
   }
 
@@ -234,16 +279,33 @@
     method public Float! invoke(float fraction);
   }
 
-  public final class DecayAnimation implements androidx.compose.animation.core.Animation<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
-    ctor public DecayAnimation(androidx.compose.animation.core.FloatDecayAnimationSpec anim, float initialValue, float initialVelocity);
-    method public androidx.compose.animation.core.TwoWayConverter<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> getConverter();
+  public final class DecayAnimation<T, V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.Animation<T,V> {
+    ctor @VisibleForTesting public DecayAnimation(androidx.compose.animation.core.VectorizedDecayAnimationSpec<V> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, V initialVelocityVector);
+    ctor public DecayAnimation(androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, V initialVelocityVector);
+    ctor public DecayAnimation(androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? initialVelocity);
     method public long getDurationMillis();
-    method public Float! getTargetValue();
-    method public Float! getValue(long playTime);
-    method public androidx.compose.animation.core.AnimationVector1D getVelocityVector(long playTime);
-    property public androidx.compose.animation.core.TwoWayConverter<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> converter;
+    method public T! getInitialValue();
+    method public V getInitialVelocityVector();
+    method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
+    method public T! getValue(long playTime);
+    method public V getVelocityVector(long playTime);
     property public long durationMillis;
-    property public Float! targetValue;
+    property public final T! initialValue;
+    property public final V initialVelocityVector;
+    property public T! targetValue;
+    property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
+  }
+
+  public interface DecayAnimationSpec<T> {
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedDecayAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter);
+  }
+
+  public final class DecayAnimationSpecKt {
+    method public static <T, V extends androidx.compose.animation.core.AnimationVector> T! calculateTargetValue(androidx.compose.animation.core.DecayAnimationSpec<T>, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? initialVelocity);
+    method public static float calculateTargetValue(androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>, float initialValue, float initialVelocity);
+    method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> exponentialDecay(optional @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float frictionMultiplier, optional @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float absVelocityThreshold);
+    method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> generateDecayAnimationSpec(androidx.compose.animation.core.FloatDecayAnimationSpec);
   }
 
   public final class DefaultAnimationClock extends androidx.compose.animation.core.BaseAnimationClock {
@@ -262,17 +324,6 @@
     method public static androidx.compose.animation.core.CubicBezierEasing getLinearOutSlowInEasing();
   }
 
-  public final class ExponentialDecay implements androidx.compose.animation.core.FloatDecayAnimationSpec {
-    ctor public ExponentialDecay(@FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float frictionMultiplier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float absVelocityThreshold);
-    ctor public ExponentialDecay();
-    method public float getAbsVelocityThreshold();
-    method public long getDurationMillis(float start, float startVelocity);
-    method public float getTarget(float start, float startVelocity);
-    method public float getValue(long playTime, float start, float startVelocity);
-    method public float getVelocity(long playTime, float start, float startVelocity);
-    property public float absVelocityThreshold;
-  }
-
   public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
     method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
   }
@@ -297,6 +348,17 @@
   public final class FloatDecayAnimationSpecKt {
   }
 
+  public final class FloatExponentialDecaySpec implements androidx.compose.animation.core.FloatDecayAnimationSpec {
+    ctor public FloatExponentialDecaySpec(@FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float frictionMultiplier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float absVelocityThreshold);
+    ctor public FloatExponentialDecaySpec();
+    method public float getAbsVelocityThreshold();
+    method public long getDurationMillis(float start, float startVelocity);
+    method public float getTarget(float start, float startVelocity);
+    method public float getValue(long playTime, float start, float startVelocity);
+    method public float getVelocity(long playTime, float start, float startVelocity);
+    property public float absVelocityThreshold;
+  }
+
   public final class FloatPropKey implements androidx.compose.animation.core.PropKey<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
     ctor public FloatPropKey(String label);
     ctor public FloatPropKey();
@@ -484,9 +546,10 @@
 
   public final class SuspendAnimationKt {
     method public static suspend Object? animate(float initialValue, float targetValue, optional float initialVelocity, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animate(T? initialValue, T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional V? initialVelocityVector, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animate(androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? targetValue, optional T? initialVelocity, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, kotlin.jvm.functions.Function2<? super T,? super T,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public static suspend Object? animateDecay(float initialValue, float initialVelocity, androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method public static suspend Object? animateDecay(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method @Deprecated public static suspend Object? animateDecay(androidx.compose.animation.core.AnimationState<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>, androidx.compose.animation.core.FloatDecayAnimationSpec animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<java.lang.Float,androidx.compose.animation.core.AnimationVector1D>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animateDecay(androidx.compose.animation.core.AnimationState<T,V>, androidx.compose.animation.core.DecayAnimationSpec<T> animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<T,V>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public static suspend <T, V extends androidx.compose.animation.core.AnimationVector> Object? animateTo(androidx.compose.animation.core.AnimationState<T,V>, T? targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional boolean sequentialAnimation, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<T,V>,kotlin.Unit> block, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
@@ -502,15 +565,15 @@
   }
 
   public final class TargetBasedAnimation<T, V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.Animation<T,V> {
-    ctor public TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, T? initialValue, T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> converter, V? initialVelocityVector);
-    method public androidx.compose.animation.core.TwoWayConverter<T,V> getConverter();
+    ctor public TargetBasedAnimation(androidx.compose.animation.core.AnimationSpec<T> animationSpec, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, T? initialValue, T? targetValue, V? initialVelocityVector);
     method public long getDurationMillis();
     method public T! getTargetValue();
+    method public androidx.compose.animation.core.TwoWayConverter<T,V> getTypeConverter();
     method public T! getValue(long playTime);
     method public V getVelocityVector(long playTime);
-    property public androidx.compose.animation.core.TwoWayConverter<T,V> converter;
     property public long durationMillis;
     property public T! targetValue;
+    property public androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter;
   }
 
   public final class ToolingGlueKt {
@@ -662,6 +725,15 @@
   public final class VectorizedAnimationSpecKt {
   }
 
+  public interface VectorizedDecayAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> {
+    method public float getAbsVelocityThreshold();
+    method public long getDurationMillis(V initialValue, V initialVelocity);
+    method public V getTarget(V initialValue, V initialVelocity);
+    method public V getValue(long playTime, V initialValue, V initialVelocity);
+    method public V getVelocity(long playTime, V initialValue, V initialVelocity);
+    property public abstract float absVelocityThreshold;
+  }
+
   public interface VectorizedDurationBasedAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     method public int getDelayMillis();
     method public int getDurationMillis();
diff --git a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatableSamples.kt b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatableSamples.kt
new file mode 100644
index 0000000..cce61a8
--- /dev/null
+++ b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatableSamples.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2020 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 androidx.compose.animation.core.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationEndReason
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.animation.core.exponentialDecay
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.animation.androidFlingDecay
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.verticalDrag
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.gesture.util.VelocityTracker
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlin.math.roundToInt
+
+@Sampled
+@Composable
+fun AnimatableAnimateToGenericsType() {
+    // Creates an `Animatable` to animate Offset and `remember` it.
+    val animatedOffset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }
+
+    Box(
+        Modifier.fillMaxSize().background(Color(0xffb99aff)).pointerInput {
+            coroutineScope {
+                while (true) {
+                    val offset = awaitPointerEventScope {
+                        awaitFirstDown().current.position
+                    }
+                    // Launch a new coroutine for animation so the touch detection thread is not
+                    // blocked.
+                    launch {
+                        // Animates to the pressed position, with the given animation spec.
+                        animatedOffset.animateTo(
+                            offset,
+                            animationSpec = spring(stiffness = Spring.StiffnessLow)
+                        )
+                    }
+                }
+            }
+        }
+    ) {
+        Text("Tap anywhere", Modifier.align(Alignment.Center))
+        Box(
+            Modifier
+                .offset {
+                    IntOffset(
+                        animatedOffset.value.x.roundToInt(),
+                        animatedOffset.value.y.roundToInt()
+                    )
+                }
+                .size(40.dp)
+                .background(Color(0xff3c1361), CircleShape)
+        )
+    }
+}
+
+@Sampled
+fun AnimatableDecayAndAnimateToSample() {
+    fun Modifier.swipeToDismiss(): Modifier = composed {
+        // Creates a Float type `Animatable` and `remember`s it
+        val animatedOffset = remember { Animatable(0f) }
+        this.pointerInput {
+            coroutineScope {
+                while (true) {
+                    val pointerId = awaitPointerEventScope {
+                        awaitFirstDown().id
+                    }
+                    val velocityTracker = VelocityTracker()
+                    awaitPointerEventScope {
+                        verticalDrag(pointerId) {
+                            // Snaps the value by the amount of finger movement
+                            animatedOffset.snapTo(animatedOffset.value + it.positionChange().y)
+                            velocityTracker.addPosition(
+                                it.current.uptime,
+                                it.current.position
+                            )
+                        }
+                    }
+                    val velocity = velocityTracker.calculateVelocity().y
+                    launch {
+                        // Either fling vertically up, or spring back
+                        val decay = androidFlingDecay<Float>(this@pointerInput)
+                        // Checks where the animation will end using decay
+                        if (decay.calculateTargetValue(
+                                animatedOffset.value,
+                                velocity
+                            ) < -size.height
+                        ) { // If the animation can naturally end outside of visual bounds, we will
+                            // animate with decay.
+
+                            // (Optionally) updates lower bounds. This stops the animation as soon
+                            // as bounds are reached.
+                            animatedOffset.updateBounds(
+                                lowerBound = -size.height.toFloat()
+                            )
+                            // Animate with the decay animation spec using the fling velocity
+                            animatedOffset.animateDecay(velocity, decay)
+                        } else {
+                            // Not enough velocity to be dismissed, spring back to 0f
+                            animatedOffset.animateTo(0f, initialVelocity = velocity)
+                        }
+                    }
+                }
+            }
+        }.offset { IntOffset(0, animatedOffset.value.roundToInt()) }
+    }
+}
+
+@Sampled
+fun AnimatableAnimationResultSample() {
+    suspend fun CoroutineScope.animateBouncingOffBounds(
+        animatable: Animatable<Offset, *>,
+        flingVelocity: Offset,
+        parentSize: Size
+    ) {
+        launch {
+            var startVelocity = flingVelocity
+            // Set bounds for the animation, so that when it reaches bounds it will stop
+            // immediately. We can then inspect the returned `AnimationResult` and decide whether
+            // we should start another animation.
+            animatable.updateBounds(Offset(0f, 0f), Offset(parentSize.width, parentSize.height))
+            do {
+                val result = animatable.animateDecay(startVelocity, exponentialDecay())
+                // Copy out the end velocity of the previous animation.
+                startVelocity = result.endState.velocity
+
+                // Negate the velocity for the dimension that hits the bounds, to create a
+                // bouncing off the bounds effect.
+                with(animatable) {
+                    if (value.x == upperBound?.x || value.x == lowerBound?.x) {
+                        // x dimension hits bounds
+                        startVelocity = startVelocity.copy(x = -startVelocity.x)
+                    }
+                    if (value.y == upperBound?.y || value.y == lowerBound?.y) {
+                        // y dimension hits bounds
+                        startVelocity = startVelocity.copy(y = -startVelocity.y)
+                    }
+                }
+                // Repeat the animation until the animation ends for reasons other than hitting
+                // bounds, e.g. if `stop()` is called, or preempted by another animation.
+            } while (result.endReason == AnimationEndReason.BoundReached)
+        }
+    }
+}
+
+@Sampled
+fun AnimatableFadeIn() {
+    fun Modifier.fadeIn(): Modifier = composed {
+        // Creates an `Animatable` and remembers it.
+        val alpha = remember { Animatable(0f) }
+        // Launches a coroutine for the animation when entering the composition.
+        // Uses `Unit` as the subject so the job in `LaunchedEffect` will run once, until it
+        // leaves composition.
+        LaunchedEffect(Unit) {
+            // Animates to 1f from 0f for the fade-in, and uses a 500ms tween animation.
+            alpha.animateTo(
+                targetValue = 1f,
+                // Default animationSpec uses [spring] animation, here we overwrite the default.
+                animationSpec = tween(500)
+            )
+        }
+        this.graphicsLayer(alpha = alpha.value)
+    }
+}
diff --git a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
index 6d4d2ac..6775700 100644
--- a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
+++ b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
@@ -45,28 +45,28 @@
         val target = mutableStateOf(AnimStates.From)
         val floatAnim1 = TargetBasedAnimation(
             spring(dampingRatio = Spring.DampingRatioHighBouncy),
+            Float.VectorConverter,
             0f,
-            1f,
-            Float.VectorConverter
+            1f
         )
         val floatAnim2 = TargetBasedAnimation(
             spring(dampingRatio = Spring.DampingRatioLowBouncy, stiffness = Spring.StiffnessLow),
+            Float.VectorConverter,
             1f,
-            0f,
-            Float.VectorConverter
+            0f
         )
 
         val colorAnim1 = TargetBasedAnimation(
             tween(1000),
+            Color.VectorConverter(Color.Red.colorSpace),
             Color.Red,
-            Color.Green,
-            Color.VectorConverter(Color.Red.colorSpace)
+            Color.Green
         )
         val colorAnim2 = TargetBasedAnimation(
             tween(1000),
+            Color.VectorConverter(Color.Red.colorSpace),
             Color.Green,
             Color.Red,
-            Color.VectorConverter(Color.Red.colorSpace)
         )
 
         // Animate from 0f to 0f for 1000ms
@@ -86,15 +86,15 @@
 
         val keyframesAnim1 = TargetBasedAnimation(
             keyframes1,
+            Float.VectorConverter,
             0f,
-            0f,
-            Float.VectorConverter
+            0f
         )
         val keyframesAnim2 = TargetBasedAnimation(
             keyframes2,
+            Float.VectorConverter,
             0f,
-            0f,
-            Float.VectorConverter
+            0f
         )
         val animFloat = mutableStateOf(-1f)
         val animColor = mutableStateOf(Color.Gray)
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animatable.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animatable.kt
new file mode 100644
index 0000000..535d987
--- /dev/null
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animatable.kt
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2019 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 androidx.compose.animation.core
+
+import androidx.compose.animation.core.AnimationEndReason.BoundReached
+import androidx.compose.animation.core.AnimationEndReason.Finished
+import androidx.compose.animation.core.AnimationEndReason.Interrupted
+import androidx.compose.runtime.AtomicReference
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.Uptime
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
+
+/**
+ * [Animatable] is a value holder that automatically animates its value when the value is
+ * changed via [animateTo]. If [animateTo] is invoked during an ongoing value change animation,
+ * a new animation will transition [Animatable] from its current value (i.e. value at the point of
+ * interruption) to the new [targetValue]. This ensures that the value change is __always__
+ * continuous using [animateTo]. If a [spring] animation (e.g. default animation) is used with
+ * [animateTo], the velocity change will guarantee to be continuous as well.
+ *
+ * Unlike [AnimationState], [Animatable] ensures *mutual exclusiveness* on its animations. To
+ * achieve this, when a new animation is started via [animateTo] (or [animateDecay]), any ongoing
+ * animation will be canceled.
+ *
+ * @sample androidx.compose.animation.core.samples.AnimatableAnimateToGenericsType
+ *
+ * @param initialValue initial value of the animatable value holder
+ * @param typeConverter A two-way converter that converts the given type [T] from and to
+ *                      [AnimationVector]
+ * @param visibilityThreshold Threshold at which the animation may round off to its target value.
+ */
+@Suppress("NotCloseable")
+class Animatable<T, V : AnimationVector>(
+    initialValue: T,
+    val typeConverter: TwoWayConverter<T, V>,
+    private val visibilityThreshold: T? = null
+) {
+
+    internal val internalState = AnimationState(
+        typeConverter = typeConverter,
+        initialValue = initialValue
+    )
+
+    /**
+     * Current value of the animation.
+     */
+    val value: T
+        get() = internalState.value
+
+    /**
+     * Velocity vector of the animation (in the form of [AnimationVector].
+     */
+    val velocityVector: V
+        get() = internalState.velocityVector
+
+    /**
+     * Returns the velocity, converted from [velocityVector].
+     */
+    val velocity: T
+        get() = typeConverter.convertFromVector(velocityVector)
+
+    /**
+     * Indicates whether the animation is running.
+     */
+    val isRunning: Boolean
+        get() = currentJob.get() != null
+
+    /**
+     * The target of the current animation. If the animation finishes un-interrupted, it will
+     * reach this target value.
+     */
+    var targetValue: T by mutableStateOf(initialValue)
+        private set
+
+    /**
+     * Lower bound of the animation. Defaults to null, which means no lower bound. Bounds can be
+     * changed using [updateBounds].
+     *
+     * Animation will stop as soon as *any* dimension specified in [lowerBound] is reached. For
+     * example: For an Animatable<Offset> with an [lowerBound] set to Offset(100f, 200f), when
+     * the [value].x drops below 100f *or* [value].y drops below 200f, the animation will stop.
+     */
+    var lowerBound: T? = null
+        private set
+
+    /**
+     * Upper bound of the animation. Defaults to null, which means no upper bound. Bounds can be
+     * changed using [updateBounds].
+     *
+     * Animation will stop as soon as *any* dimension specified in [upperBound] is reached. For
+     * example: For an Animatable<Offset> with an [upperBound] set to Offset(100f, 200f), when
+     * the [value].x exceeds 100f *or* [value].y exceeds 200f, the animation will stop.
+     */
+    var upperBound: T? = null
+        private set
+
+    private var currentJob = AtomicReference<Job?>(null)
+    internal val defaultSpringSpec: SpringSpec<T> =
+        SpringSpec(visibilityThreshold = visibilityThreshold)
+
+    private val negativeInfinityBounds = createVector(Float.NEGATIVE_INFINITY)
+    private val positiveInfinityBounds = createVector(Float.POSITIVE_INFINITY)
+
+    private var lowerBoundVector: V = negativeInfinityBounds
+    private var upperBoundVector: V = positiveInfinityBounds
+
+    private fun createVector(value: Float): V {
+        val newVector = typeConverter.convertToVector(this.value)
+        for (i in 0 until newVector.size) {
+            newVector[i] = value
+        }
+        return newVector
+    }
+
+    /**
+     * Updates either [lowerBound] or [upperBound], or both. This will update
+     * [Animatable.lowerBound] and/or [Animatable.upperBound] accordingly after a check to ensure
+     * the provided [lowerBound] is no greater than [upperBound] in any dimension.
+     *
+     * Setting the bounds will immediate clamp the [value], only if the animation isn't running.
+     * For the on-going animation, the value at the next frame update will be checked against the
+     * bounds. If the value reaches the bound, then the animation will end with [BoundReached]
+     * end reason.
+     *
+     * @param lowerBound lower bound of the animation. Defaults to the [Animatable.lowerBound]
+     *                   that is currently set.
+     * @param upperBound upper bound of the animation. Defaults to the [Animatable.upperBound]
+     *                   that is currently set.
+     * @throws [IllegalStateException] if the [lowerBound] is greater than [upperBound] in any
+     *                                 dimension.
+     */
+    fun updateBounds(lowerBound: T? = this.lowerBound, upperBound: T? = this.upperBound) {
+        val lowerBoundVector = lowerBound?.run { typeConverter.convertToVector(this) }
+            ?: negativeInfinityBounds
+
+        val upperBoundVector = upperBound?.run { typeConverter.convertToVector(this) }
+            ?: positiveInfinityBounds
+
+        for (i in 0 until lowerBoundVector.size) {
+            // TODO: is this check too aggressive?
+            check(lowerBoundVector[i] <= upperBoundVector[i]) {
+                "Lower bound must be no greater than upper bound on *all* dimensions. The " +
+                    "provided lower bound: $lowerBoundVector is greater than upper bound " +
+                    "$upperBoundVector on index $i"
+            }
+        }
+        // After the correctness check:
+        this.lowerBoundVector = lowerBoundVector
+        this.upperBoundVector = upperBoundVector
+
+        this.upperBound = upperBound
+        this.lowerBound = lowerBound
+        if (!isRunning) {
+            val clampedValue = clampToBounds(value)
+            if (clampedValue != value) {
+                this.internalState.value = value
+            }
+        }
+    }
+
+    /**
+     * Sets the target value, which effectively starts an animation to change the value from [value]
+     * to the [targetValue]. If there is already an animation in-flight, this method will cancel
+     * the ongoing animation and start a new animation continuing the current [value] and
+     * [velocity]. It's recommended to set the optional [initialVelocity] only when [animateTo] is
+     * used immediately after a fling. In most of the other cases, altering velocity would result
+     * in visual discontinuity.
+     *
+     * The animation will use the provided [animationSpec] to animate the value towards the
+     * [targetValue]. When no [animationSpec] is specified, a [spring] will be used.  [block] will
+     * be invoked on each animation frame.
+     *
+     * Returns an [AnimationResult] object. It contains: 1) the reason for ending the animation,
+     * and 2) an end state of the animation. The reason for ending the animation can be any of the
+     * following three:
+     * -  [Finished], when the animation finishes successfully without any interruption,
+     * -  [Interrupted], if/when the animation gets interrupted by 1) another call to start an
+     *    animation (i.e. [animateTo]/[animateDecay]), 2) [Animatable.stop], or 3)
+     *    [Animatable.snapTo].
+     * -  [BoundReached] If the animation reaches the either [lowerBound] or [upperBound] in any
+     *    dimension, the animation will end with [BoundReached] being the end reason.
+     *
+     * __Note__: once the animation ends, its velocity will be reset to 0. The animation state at
+     * the point of interruption/reaching bound is captured in the returned [AnimationResult].
+     * If there's a need to continue the momentum that the animation had before it was interrupted
+     * or reached the bound, it's recommended to use the velocity in the returned
+     * [AnimationResult.endState] to start another animation.
+     *
+     * @sample androidx.compose.animation.core.samples.AnimatableAnimateToGenericsType
+     * @sample androidx.compose.animation.core.samples.AnimatableFadeIn
+     */
+    suspend fun animateTo(
+        targetValue: T,
+        animationSpec: AnimationSpec<T> = defaultSpringSpec,
+        initialVelocity: T = velocity,
+        block: (Animatable<T, V>.() -> Unit)? = null
+    ): AnimationResult<T, V> {
+        internalState.velocityVector = typeConverter.convertToVector(initialVelocity)
+        val anim = TargetBasedAnimation(
+            animationSpec = animationSpec,
+            initialValue = value,
+            targetValue = targetValue,
+            typeConverter = typeConverter,
+            initialVelocity = initialVelocity
+        )
+        return runAnimation(anim, block)
+    }
+
+    /**
+     * Starts an animation that slows down from the given [initialVelocity] starting at
+     * current [Animatable.value] until the velocity reaches 0. If there's already an ongoing
+     * animation, the animation in-flight will be immediately cancelled. Decay animation is often
+     * used after a fling gesture.
+     *
+     * [animationSpec] defines the decay animation that will be used for this animation. Some
+     * options for this [animationSpec] include: [androidFlingDecay][androidx.compose
+     * .foundation.animation.androidFlingDecay] and [exponentialDecay]. [block] will be
+     * invoked on each animation frame.
+     *
+     * Returns an [AnimationResult] object, that contains the [reason][AnimationEndReason] for
+     * ending the animation, and an end state of the animation. The reason for ending the animation
+     * will be [Finished], when the animation finishes successfully without any interruption,
+     * If/when the animation gets interrupted by 1) another call to start an animation
+     * (i.e. [animateTo]/[animateDecay]), 2) [stop], or 3) [snapTo]
+     * [Interrupted] will be returned. If the animation reaches the either [lowerBound] or
+     * [upperBound] in any dimension, the animation will end with [BoundReached] being the
+     * end reason.
+     *
+     * Note, once the animation ends, its velocity will be reset to 0. If there's a need to
+     * continue the momentum before the animation gets interrupted or reaches the bound, it's
+     * recommended to use the velocity in the returned [AnimationResult.endState] to start
+     * another animation.
+     *
+     * @sample androidx.compose.animation.core.samples.AnimatableDecayAndAnimateToSample
+     */
+    suspend fun animateDecay(
+        initialVelocity: T,
+        animationSpec: DecayAnimationSpec<T>,
+        block: (Animatable<T, V>.() -> Unit)? = null
+    ): AnimationResult<T, V> {
+        internalState.velocityVector = typeConverter.convertToVector(initialVelocity)
+        val anim = DecayAnimation(
+            animationSpec = animationSpec,
+            initialValue = value,
+            initialVelocityVector = velocityVector.copy(),
+            typeConverter = typeConverter
+        )
+        return runAnimation(anim, block)
+    }
+
+    // All the different types of animation code paths eventually converge to this method.
+    private suspend fun runAnimation(
+        animation: Animation<T, V>,
+        block: (Animatable<T, V>.() -> Unit)?
+    ): AnimationResult<T, V> {
+        targetValue = animation.targetValue
+        return coroutineScope {
+            // Update current job, and cancel old job (i.e. existing animation)
+            val oldJob = currentJob.getAndSet(coroutineContext[Job])
+            oldJob?.also { it.cancelAnimation() } != null
+
+            val startState = internalState.copy(finishedTime = Uptime.Unspecified)
+            val endReason = try {
+                var clampingNeeded = false
+                startState.animate(
+                    animation,
+                    internalState.lastFrameTime
+                ) {
+                    if (currentJob.get() == coroutineContext[Job]) {
+                        updateState(internalState)
+                        val clamped = clampToBounds(value)
+                        if (clamped != value) {
+                            internalState.value = clamped
+                            startState.value = clamped
+                            block?.invoke(this@Animatable)
+                            cancelAnimation()
+                            clampingNeeded = true
+                        } else {
+                            block?.invoke(this@Animatable)
+                        }
+                    } else {
+                        // Cancelled by another job *initiated by* Animatable
+                        cancelAnimation()
+                    }
+                }
+                if (startState.isFinished) {
+                    Finished
+                } else {
+                    if (clampingNeeded) BoundReached else Interrupted
+                }
+            } catch (e: CancellationException) {
+                if (e is AnimationCancellationException) {
+                    Interrupted
+                } else {
+                    // External cancellation. Clean up internal states first, then throw.
+                    if (currentJob.compareAndSet(coroutineContext[Job], null)) {
+                        endAnimation()
+                    }
+                    throw e
+                }
+            }
+
+            // Reset the animation if it wasn't interrupted
+            if (currentJob.compareAndSet(coroutineContext[Job], null)) {
+                endAnimation()
+            }
+
+            AnimationResult(startState, endReason)
+        }
+    }
+
+    private fun clampToBounds(value: T): T {
+        if (
+            lowerBoundVector == negativeInfinityBounds &&
+            upperBoundVector == negativeInfinityBounds
+        ) {
+            // Expect this to be the most common use case
+            return value
+        }
+        val valueVector = typeConverter.convertToVector(value)
+        var clamped = false
+        for (i in 0 until valueVector.size) {
+            if (valueVector[i] < lowerBoundVector[i] || valueVector[i] > upperBoundVector[i]) {
+                clamped = true
+                valueVector[i] =
+                    valueVector[i].coerceIn(lowerBoundVector[i], upperBoundVector[i])
+            }
+        }
+        if (clamped) {
+            return typeConverter.convertFromVector(valueVector)
+        } else {
+            return value
+        }
+    }
+
+    private fun endAnimation() {
+        // Reset velocity
+        internalState.apply {
+            velocityVector.reset()
+            lastFrameTime = Uptime.Unspecified
+        }
+    }
+
+    /**
+     * Sets the current value to the target value immediately, without any animation. This will
+     * also cancel any on-going animation
+     *
+     * @param targetValue The new target value to set [value] to.
+     */
+    fun snapTo(targetValue: T) {
+        stop()
+        internalState.value = targetValue
+        this.targetValue = targetValue
+    }
+
+    /**
+     * Stops any on-going animation. No op if no animation is running. Note that this method does
+     * not skip the animation value to its target value. Rather the animation will be stopped in its
+     * track.
+     */
+    fun stop() {
+        currentJob.getAndSet(null)?.cancelAnimation()
+        endAnimation()
+    }
+
+    private fun Job.cancelAnimation() {
+        cancel(AnimationCancellationException())
+    }
+
+    private class AnimationCancellationException : CancellationException(
+        "Interrupted by another animation, or stopped."
+    )
+}
+
+/**
+ * This [Animatable] function creates a float value holder that automatically
+ * animates its value when the value is changed via [animateTo]. [Animatable] supports value
+ * change during an ongoing value change animation. When that happens, a new animation will
+ * transition [Animatable] from its current value (i.e. value at the point of interruption) to the
+ * new target. This ensures that the value change is *always* continuous using [animateTo]. If
+ * [spring] animation (i.e. default animation) is used with [animateTo], the velocity change will
+ * be guaranteed to be continuous as well.
+ *
+ * Unlike [AnimationState], [Animatable] ensures mutual exclusiveness on its animation. To
+ * do so, when a new animation is started via [animateTo] (or [animateDecay]), any ongoing
+ * animation job will be cancelled.
+ *
+ * @sample androidx.compose.animation.core.samples.AnimatableDecayAndAnimateToSample
+ *
+ * @param initialValue initial value of the animatable value holder
+ * @param visibilityThreshold Threshold at which the animation may round off to its target value.
+ *                            [Spring.DefaultDisplacementThreshold] by default.
+ */
+fun Animatable(
+    initialValue: Float,
+    visibilityThreshold: Float = Spring.DefaultDisplacementThreshold
+) = Animatable(
+    initialValue,
+    Float.VectorConverter,
+    visibilityThreshold
+)
+
+// TODO: Consider some version of @Composable fun<T, V: AnimationVector> Animatable<T, V>.animateTo
+/**
+ * AnimationResult contains information about an animation at the end of the animation. [endState]
+ * captures the value/velocity/frame time, etc of the animation at its last frame. It can be
+ * useful for starting another animation to continue the velocity from the previously interrupted
+ * animation. [endReason] describes why the animation ended, it could be one of the following three:
+ * -  [Finished], when the animation finishes successfully without any interruption
+ * -  [Interrupted], if/when the animation gets interrupted by 1) another call to start an
+ *    animation (i.e. [animateTo]/[animateDecay]), 2) [Animatable.stop], or 3)
+ *    [Animatable.snapTo].
+ * -  [BoundReached] If the animation reaches the either [lowerBound][Animatable.lowerBound] or
+ *    [upperBound][Animatable.upperBound] in any dimension, the animation will end with
+ *    [BoundReached] being the end reason.
+ *
+ * @sample androidx.compose.animation.core.samples.AnimatableAnimationResultSample
+ */
+class AnimationResult<T, V : AnimationVector>(
+    val endState: AnimationState<T, V>,
+    val endReason: AnimationEndReason
+)
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
index ebd6cdd..386ddc0 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
@@ -485,7 +485,7 @@
     finishedListener: ((T) -> Unit)? = null
 ): State<T> {
     val animationState: AnimationState<T, V> = remember(typeConverter) {
-        AnimationState(targetValue, typeConverter = typeConverter)
+        AnimationState(typeConverter, targetValue)
     }
 
     val listener by rememberUpdatedState(finishedListener)
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimatedValue.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimatedValue.kt
index e04c380..a259068 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimatedValue.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimatedValue.kt
@@ -140,7 +140,7 @@
 
         this.targetValue = targetValue
         val animationWrapper = TargetBasedAnimation(
-            anim, value, targetValue, typeConverter, velocityVector
+            anim, typeConverter, value, targetValue, velocityVector
         )
 
         this.onEnd = onEnd
@@ -348,7 +348,7 @@
 // TODO: Figure out an API for customizing the type of decay & the friction
 fun AnimatedFloat.fling(
     startVelocity: Float,
-    decay: FloatDecayAnimationSpec = ExponentialDecay(),
+    decay: FloatDecayAnimationSpec = FloatExponentialDecaySpec(),
     onEnd: OnAnimationEnd? = null
 ) {
     if (isRunning) {
@@ -381,7 +381,7 @@
  */
 fun AnimatedFloat.fling(
     startVelocity: Float,
-    decay: FloatDecayAnimationSpec = ExponentialDecay(),
+    decay: FloatDecayAnimationSpec = FloatExponentialDecaySpec(),
     adjustTarget: (Float) -> TargetAnimation?,
     onEnd: OnAnimationEnd? = null
 ) {
@@ -403,9 +403,9 @@
         targetValue = targetAnimation.target
         val animWrapper = TargetBasedAnimation(
             targetAnimation.animation,
+            typeConverter,
             value,
             targetAnimation.target,
-            typeConverter,
             AnimationVector1D(startVelocity)
         )
         startAnimation(animWrapper)
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animation.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animation.kt
index 3df305a..916bdc0 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animation.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animation.kt
@@ -17,7 +17,6 @@
 package androidx.compose.animation.core
 
 import androidx.compose.ui.util.annotation.VisibleForTesting
-import kotlin.math.sign
 
 /**
  * This interface provides a convenient way to query from an [VectorizedAnimationSpec] or
@@ -30,11 +29,11 @@
  *
  * __Note__: [Animation] does not track the lifecycle of an animation. It merely reacts to play time
  * change and returns the new value/velocity as a result. It can be used as a building block for
- * more lifecycle aware animations. In contrast, [AnimatedValue] and [TransitionAnimation] are
+ * more lifecycle aware animations. In contrast, [Animatable] and [TransitionAnimation] are
  * stateful and manage their own lifecycles, and subscribe/unsubscribe from an
  * [AnimationClockObservable] as needed.
  *
- * @see [AnimatedValue]
+ * @see [Animatable]
  * @see [androidx.compose.animation.transition]
  */
 interface Animation<T, V : AnimationVector> {
@@ -48,7 +47,11 @@
      * type to [AnimationVector]. This makes it possible to animate different dimensions of the
      * data object independently (e.g. x/y dimensions of the position data).
      */
+    val typeConverter: TwoWayConverter<T, V>
+
+    @Deprecated("Renamed to typeConverter", ReplaceWith("typeConverter"))
     val converter: TwoWayConverter<T, V>
+        get() = typeConverter
 
     /**
      * This is the value that the [Animation] will reach when it finishes uninterrupted.
@@ -84,8 +87,8 @@
  *
  * @param playTime the play time that is used to calculate the velocity of the animation.
  */
-internal fun <T, V : AnimationVector> Animation<T, V>.getVelocity(playTime: Long): T =
-    converter.convertFromVector(getVelocityVector(playTime))
+fun <T, V : AnimationVector> Animation<T, V>.getVelocity(playTime: Long): T =
+    typeConverter.convertFromVector(getVelocityVector(playTime))
 
 /**
  * Creates a [TargetBasedAnimation] from a given [VectorizedAnimationSpec] of [AnimationVector] type. This
@@ -108,35 +111,10 @@
         initialValue = initialValue,
         targetValue = targetValue,
         initialVelocityVector = initialVelocity,
-        converter = TwoWayConverter({ it }, { it })
+        typeConverter = TwoWayConverter({ it }, { it })
     )
 
 /**
- * Creates a [TargetBasedAnimation] from a given [VectorizedAnimationSpec] of [AnimationVector] type.
- *
- * @param initialValue the value that the animation will start from
- * @param targetValue the value that the animation will end at
- * @param initialVelocityVector the initial velocity (in the form of [AnimationVector]) to start the
- *                            animation at.
- * @param converter a [TwoWayConverter] that converts the from [AnimationVector] to the animation
- *                  data type [T], and vice versa.
- *
- * @see TargetBasedAnimation
- */
-internal fun <T, V : AnimationVector> VectorizedAnimationSpec<V>.createAnimation(
-    initialValue: T,
-    targetValue: T,
-    initialVelocityVector: V,
-    converter: TwoWayConverter<T, V>
-) = TargetBasedAnimation<T, V>(
-    animationSpec = this,
-    initialValue = initialValue,
-    targetValue = targetValue,
-    initialVelocityVector = initialVelocityVector,
-    converter = converter
-)
-
-/**
  * Creates a [TargetBasedAnimation] with the given start/end conditions of the animation, and
  * the provided [animationSpec].
  *
@@ -147,7 +125,7 @@
  *
  * __Note__: When interruptions happen to the [TargetBasedAnimation], a new instance should
  * be created that use the current value and velocity as the starting conditions. This type of
- * interruption handling is the default behavior for both [AnimatedValue] and
+ * interruption handling is the default behavior for both [Animatable] and
  * [TransitionAnimation]. Consider using those APIs for the interruption handling, as well as
  * built-in animation lifecycle management.
  *
@@ -155,20 +133,20 @@
  * @param initialValue the start value of the animation
  * @param targetValue the end value of the animation
  * @param initialVelocity the start velocity (of type [T] of the animation
- * @param converter the [TwoWayConverter] that is used to convert animation type [T] from/to [V]
+ * @param typeConverter the [TwoWayConverter] that is used to convert animation type [T] from/to [V]
  */
 fun <T, V : AnimationVector> TargetBasedAnimation(
     animationSpec: AnimationSpec<T>,
+    typeConverter: TwoWayConverter<T, V>,
     initialValue: T,
     targetValue: T,
-    initialVelocity: T,
-    converter: TwoWayConverter<T, V>
+    initialVelocity: T
 ) = TargetBasedAnimation(
     animationSpec,
+    typeConverter,
     initialValue,
     targetValue,
-    converter,
-    converter.convertToVector(initialVelocity)
+    typeConverter.convertToVector(initialVelocity)
 )
 
 /**
@@ -181,25 +159,25 @@
  *
  * __Note__: When interruptions happen to the [TargetBasedAnimation], a new instance should
  * be created that use the current value and velocity as the starting conditions. This type of
- * interruption handling is the default behavior for both [AnimatedValue] and
+ * interruption handling is the default behavior for both [Animatable] and
  * [TransitionAnimation]. Consider using those APIs for the interruption handling, as well as
  * built-in animation lifecycle management.
  *
  * @param animationSpec the [VectorizedAnimationSpec] that will be used to calculate value/velocity
  * @param initialValue the start value of the animation
  * @param targetValue the end value of the animation
- * @param converter the [TwoWayConverter] that is used to convert animation type [T] from/to [V]
+ * @param typeConverter the [TwoWayConverter] that is used to convert animation type [T] from/to [V]
  * @param initialVelocityVector the start velocity of the animation in the form of [AnimationVector]
  *
  * @see [TransitionAnimation]
  * @see [androidx.compose.animation.transition]
- * @see [AnimatedValue]
+ * @see [Animatable]
  */
 class TargetBasedAnimation<T, V : AnimationVector> internal constructor(
     internal val animationSpec: VectorizedAnimationSpec<V>,
+    override val typeConverter: TwoWayConverter<T, V>,
     initialValue: T,
     override val targetValue: T,
-    override val converter: TwoWayConverter<T, V>,
     initialVelocityVector: V? = null
 ) : Animation<T, V> {
 
@@ -214,38 +192,39 @@
      *
      * __Note__: When interruptions happen to the [TargetBasedAnimation], a new instance should
      * be created that use the current value and velocity as the starting conditions. This type of
-     * interruption handling is the default behavior for both [AnimatedValue] and
+     * interruption handling is the default behavior for both [Animatable] and
      * [TransitionAnimation]. Consider using those APIs for the interruption handling, as well as
      * built-in animation lifecycle management.
      *
      * @param animationSpec the [AnimationSpec] that will be used to calculate value/velocity
+     * @param typeConverter the [TwoWayConverter] that is used to convert animation type [T] from/to [V]
      * @param initialValue the start value of the animation
      * @param targetValue the end value of the animation
-     * @param converter the [TwoWayConverter] that is used to convert animation type [T] from/to [V]
      * @param initialVelocityVector the start velocity vector, null by default (meaning 0 velocity).
      */
     constructor(
         animationSpec: AnimationSpec<T>,
+        typeConverter: TwoWayConverter<T, V>,
         initialValue: T,
         targetValue: T,
-        converter: TwoWayConverter<T, V>,
         initialVelocityVector: V? = null
     ) : this(
-        animationSpec.vectorize(converter),
+        animationSpec.vectorize(typeConverter),
+        typeConverter,
         initialValue,
         targetValue,
-        converter,
         initialVelocityVector
     )
 
-    private val initialValueVector = converter.convertToVector.invoke(initialValue)
-    private val targetValueVector = converter.convertToVector.invoke(targetValue)
+    private val initialValueVector = typeConverter.convertToVector(initialValue)
+    private val targetValueVector = typeConverter.convertToVector(targetValue)
     private val initialVelocityVector =
-        initialVelocityVector ?: converter.convertToVector.invoke(initialValue).newInstance()
+        initialVelocityVector?.copy() ?: typeConverter.convertToVector(initialValue)
+            .newInstance()
 
     override fun getValue(playTime: Long): T {
         return if (playTime < durationMillis) {
-            converter.convertFromVector.invoke(
+            typeConverter.convertFromVector(
                 animationSpec.getValue(
                     playTime, initialValueVector,
                     targetValueVector, initialVelocityVector
@@ -283,41 +262,154 @@
 }
 
 /**
- * Fixed Decay animation wraps around a [FloatDecayAnimationSpec] and assumes its starting value and
- * velocity never change throughout the animation.
+ * [DecayAnimation] is an animation that slows down from [initialVelocityVector] as
+ * time goes on. [DecayAnimation] is stateless, and it does not have any concept of lifecycle. It
+ * serves as an animation calculation engine that supports convenient query of value/velocity
+ * given a play time. To achieve that, [DecayAnimation] stores all the animation related
+ * information: [initialValue], [initialVelocityVector], decay animation spec, [typeConverter].
  *
- * @param anim decay animation that will be used
- * @param initialValue starting value that will be passed to the decay animation
- * @param initialVelocity starting velocity for the decay animation
+ * __Note__: Unless there's a need to control the timing manually, it's
+ * generally recommended to use higher level animation APIs that build on top [DecayAnimation],
+ * such as [Animatable.animateDecay], [AnimationState.animateDecay], etc.
+ *
+ * @see Animatable.animateDecay
+ * @see AnimationState.animateDecay
  */
-class DecayAnimation(
-    private val anim: FloatDecayAnimationSpec,
-    private val initialValue: Float,
-    private val initialVelocity: Float = 0f
-) : Animation<Float, AnimationVector1D> {
-    override val targetValue: Float = anim.getTarget(initialValue, initialVelocity)
-    private val velocityVector: AnimationVector1D = AnimationVector1D(0f)
-    override val converter: TwoWayConverter<Float, AnimationVector1D>
-        get() = Float.VectorConverter
+class DecayAnimation<T, V : AnimationVector> @VisibleForTesting constructor(
+    private val animationSpec: VectorizedDecayAnimationSpec<V>,
+    override val typeConverter: TwoWayConverter<T, V>,
+    val initialValue: T,
+    initialVelocityVector: V
+) : Animation<T, V> {
+    private val initialValueVector: V = typeConverter.convertToVector(initialValue)
+    val initialVelocityVector: V = initialVelocityVector.copy()
+    private val endVelocity: V
 
-    // TODO: Remove the MissingNullability suppression when b/134803955 is fixed.
-    @Suppress("AutoBoxing", "MissingNullability")
-    override fun getValue(playTime: Long): Float {
+    override val targetValue: T = typeConverter.convertFromVector(
+        animationSpec.getTarget(initialValueVector, initialVelocityVector)
+    )
+    override val durationMillis: Long
+
+    /**
+     * [DecayAnimation] is an animation that slows down from [initialVelocityVector] as time goes
+     * on. [DecayAnimation] is stateless, and it does not have any concept of lifecycle. It
+     * serves as an animation calculation engine that supports convenient query of value/velocity
+     * given a play time. To achieve that, [DecayAnimation] stores all the animation related
+     * information: [initialValue], [initialVelocityVector], decay animation spec, [typeConverter].
+     *
+     * __Note__: Unless there's a need to control the timing manually, it's
+     * generally recommended to use higher level animation APIs that build on top [DecayAnimation],
+     * such as [Animatable.animateDecay], [AnimationState.animateDecay], etc.
+     *
+     * @param animationSpec Decay animation spec that defines the slow-down curve of the animation
+     * @param typeConverter Type converter to convert the type [T] from and to [AnimationVector]
+     * @param initialValue The starting value of the animation
+     * @param initialVelocityVector The starting velocity of the animation in [AnimationVector] form
+     *
+     * @see Animatable.animateDecay
+     * @see AnimationState.animateDecay
+     */
+    constructor(
+        animationSpec: DecayAnimationSpec<T>,
+        typeConverter: TwoWayConverter<T, V>,
+        initialValue: T,
+        initialVelocityVector: V
+    ) : this(
+        animationSpec.vectorize(typeConverter),
+        typeConverter,
+        initialValue,
+        initialVelocityVector
+    )
+
+    /**
+     * [DecayAnimation] is an animation that slows down from [initialVelocity] as time goes on.
+     * [DecayAnimation] is stateless, and it does not have any concept of lifecycle. It
+     * serves as an animation calculation engine that supports convenient query of value/velocity
+     * given a play time. To achieve that, [DecayAnimation] stores all the animation related
+     * information: [initialValue], [initialVelocity], [animationSpec], [typeConverter].
+     *
+     * __Note__: Unless there's a need to control the timing manually, it's
+     * generally recommended to use higher level animation APIs that build on top [DecayAnimation],
+     * such as [Animatable.animateDecay], [AnimationState.animateDecay], etc.
+     *
+     * @param animationSpec Decay animation spec that defines the slow-down curve of the animation
+     * @param typeConverter Type converter to convert the type [T] from and to [AnimationVector]
+     * @param initialValue The starting value of the animation
+     * @param initialVelocity The starting velocity of the animation
+     *
+     * @see Animatable.animateDecay
+     * @see AnimationState.animateDecay
+     */
+    constructor(
+        animationSpec: DecayAnimationSpec<T>,
+        typeConverter: TwoWayConverter<T, V>,
+        initialValue: T,
+        initialVelocity: T
+    ) : this(
+        animationSpec.vectorize(typeConverter),
+        typeConverter,
+        initialValue,
+        typeConverter.convertToVector(initialVelocity)
+    )
+
+    init {
+        durationMillis = animationSpec.getDurationMillis(
+            initialValueVector, initialVelocityVector
+        )
+        endVelocity = animationSpec.getVelocity(
+            durationMillis,
+            initialValueVector,
+            initialVelocityVector
+        ).copy()
+        for (i in 0 until endVelocity.size) {
+            endVelocity[i] = endVelocity[i].coerceIn(
+                -animationSpec.absVelocityThreshold,
+                animationSpec.absVelocityThreshold
+            )
+        }
+    }
+
+    override fun getValue(playTime: Long): T {
         if (!isFinished(playTime)) {
-            return anim.getValue(playTime, initialValue, initialVelocity)
+            return typeConverter.convertFromVector(
+                animationSpec.getValue(playTime, initialValueVector, initialVelocityVector)
+            )
         } else {
             return targetValue
         }
     }
 
-    override fun getVelocityVector(playTime: Long): AnimationVector1D {
+    override fun getVelocityVector(playTime: Long): V {
         if (!isFinished(playTime)) {
-            velocityVector.value = anim.getVelocity(playTime, initialValue, initialVelocity)
+            return animationSpec.getVelocity(playTime, initialValueVector, initialVelocityVector)
         } else {
-            velocityVector.value = anim.absVelocityThreshold * sign(initialVelocity)
+            return endVelocity
         }
-        return velocityVector
     }
+}
 
-    override val durationMillis: Long = anim.getDurationMillis(initialValue, initialVelocity)
-}
\ No newline at end of file
+/**
+ * [DecayAnimation] is an animation that slows down from [initialVelocity] as
+ * time goes on. [DecayAnimation] is stateless, and it does not have any concept of lifecycle. It
+ * serves as an animation calculation engine that supports convenient query of value/velocity
+ * given a play time. To achieve that, [DecayAnimation] stores all the animation related
+ * information: [initialValue], [initialVelocity], decay animation spec.
+ *
+ * __Note__: Unless there's a need to control the timing manually, it's
+ * generally recommended to use higher level animation APIs that build on top [DecayAnimation],
+ * such as [Animatable.animateDecay], [animateDecay], etc.
+ *
+ * @param animationSpec decay animation that will be used
+ * @param initialValue starting value that will be passed to the decay animation
+ * @param initialVelocity starting velocity for the decay animation, 0f by default
+ */
+fun DecayAnimation(
+    animationSpec: FloatDecayAnimationSpec,
+    initialValue: Float,
+    initialVelocity: Float = 0f
+) = DecayAnimation(
+    animationSpec.generateDecayAnimationSpec(),
+    Float.VectorConverter,
+    initialValue,
+    AnimationVector(initialVelocity)
+)
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
index 7f7b55a..c921df1 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
@@ -19,6 +19,7 @@
 import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
 import androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig
 import androidx.compose.runtime.Immutable
+import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.util.annotation.IntRange
 
 object AnimationConstants {
@@ -41,12 +42,12 @@
 /**
  * [AnimationSpec] stores the specification of an animation, including 1) the data type to be
  * animated, and 2) the animation configuration (i.e. [VectorizedAnimationSpec]) that will be used
- * to once the data (of type [T]) has been converted to [AnimationVector].
+ * once the data (of type [T]) has been converted to [AnimationVector].
  *
  * Any type [T] can be animated by the system as long as a [TwoWayConverter] is supplied to convert
  * the data type [T] from and to an [AnimationVector]. There are a number of converters
  * available out of the box. For example, to animate [androidx.compose.ui.unit.IntOffset] the system
- * uses [androidx.compose.animation.IntOffset.VectorConverter] to convert the object to
+ * uses [IntOffset.VectorConverter][IntOffset.Companion.VectorConverter] to convert the object to
  * [AnimationVector2D], so that both x and y dimensions are animated independently with separate
  * velocity tracking. This enables multidimensional objects to be animated in a true
  * multi-dimensional way. It is particularly useful for smoothly handling animation interruptions
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationState.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationState.kt
index 7e82eb7..e8f0c84 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationState.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationState.kt
@@ -16,10 +16,10 @@
 
 package androidx.compose.animation.core
 
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.State
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.unit.Uptime
 
 /**
@@ -28,18 +28,20 @@
  * there's a need to mutate some of the fields of an [AnimationState], consider using [copy]
  * functions.
  *
- * @param initialValue initial value of the [AnimationState]
  * @param typeConverter [TwoWayConverter] to convert type [T] from and to [AnimationVector]
+ * @param initialValue initial value of the [AnimationState]
  * @param initialVelocityVector initial velocity of the [AnimationState], null (i.e. no velocity)
  *                              by default.
  * @param lastFrameTime last frame time of the animation, [Uptime.Unspecified] by default
- * @param finishedTime the time that the animation finished, [Uptime.Unspecified] by default.
+ * @param finishedTime the time that the animation finished successfully, [Uptime.Unspecified]
+ *                     until then
+ *
  * @param isRunning whether the [AnimationState] is currently being updated by an animation.
  *                  False by default
  */
 class AnimationState<T, V : AnimationVector>(
-    initialValue: T,
     val typeConverter: TwoWayConverter<T, V>,
+    initialValue: T,
     initialVelocityVector: V? = null,
     lastFrameTime: Uptime = Uptime.Unspecified,
     finishedTime: Uptime = Uptime.Unspecified,
@@ -55,7 +57,7 @@
      * Current velocity vector of the [AnimationState].
      */
     var velocityVector: V =
-        initialVelocityVector ?: typeConverter.createZeroVectorFrom(initialValue)
+        initialVelocityVector?.copy() ?: typeConverter.createZeroVectorFrom(initialValue)
         internal set
 
     /**
@@ -71,7 +73,7 @@
         internal set
 
     /**
-     * The time when the animation is finished. If the animation has never finished
+     * The time when the animation finished successfully. If the animation has never finished
      * (i.e. currently running, interrupted, or never started), this will be [Uptime.Unspecified],
      * unless specified otherwise in [AnimationState] constructor.
      */
@@ -83,6 +85,12 @@
      */
     var isRunning: Boolean = isRunning
         internal set
+
+    /**
+     * Velocity of type [T], converted from [velocityVector].
+     */
+    val velocity: T
+        get() = typeConverter.convertFromVector(velocityVector)
 }
 
 /**
@@ -93,18 +101,6 @@
     get() = finishedTime != Uptime.Unspecified
 
 /**
- * Returns the [Float] velocity of the [AnimationState].
- */
-val AnimationState<Float, AnimationVector1D>.velocity
-    get() = velocityVector.value
-
-/**
- * Returns the [Float] velocity of the [AnimationScope].
- */
-val AnimationScope<Float, AnimationVector1D>.velocity
-    get() = velocityVector.value
-
-/**
  * [AnimationScope] provides all the animation related info specific to an animation run. An
  * [AnimationScope] will be accessible during an animation.
  *
@@ -132,7 +128,7 @@
     // Externally immutable fields
     var value: T by mutableStateOf(initialValue)
         internal set
-    var velocityVector: V = initialVelocityVector
+    var velocityVector: V = initialVelocityVector.copy()
         internal set
     var lastFrameTime: Uptime = lastFrameTime
         internal set
@@ -142,6 +138,12 @@
         internal set
 
     /**
+     * Velocity of type [T], converted from [velocityVector].
+     */
+    val velocity
+        get() = typeConverter.convertFromVector(velocityVector)
+
+    /**
      * Cancels the animation that this [AnimationScope] corresponds to. The scope will not be
      * updated any more after [cancelAnimation] is called.
      */
@@ -155,7 +157,7 @@
      * [AnimationScope].
      */
     fun toAnimationState() = AnimationState(
-        value, typeConverter, velocityVector, lastFrameTime, finishedTime, isRunning
+        typeConverter, value, velocityVector, lastFrameTime, finishedTime, isRunning
     )
 }
 
@@ -169,8 +171,8 @@
  *                 [AnimationState] by default.
  * @param lastFrameTime last frame time of the animation, same as the given [AnimationState] by
  *                      default
- * @param endTime the time that the animation ended. This will be [Uptime.Unspecified] until the
- *                animation ends. Default value is the same as the given [AnimationState].
+ * @param finishedTime the time that the animation finished successfully, [Uptime.Unspecified] until
+ *                     then. Default value is the same as the given [AnimationState].
  * @param isRunning whether the [AnimationState] is currently being updated by an animation.
  *                  Same as the given [AnimationState] by default
  *
@@ -179,12 +181,14 @@
  */
 fun <T, V : AnimationVector> AnimationState<T, V>.copy(
     value: T = this.value,
-    velocityVector: V? = this.velocityVector,
+    velocityVector: V? = this.velocityVector.copy(),
     lastFrameTime: Uptime = this.lastFrameTime,
-    endTime: Uptime = this.finishedTime,
+    finishedTime: Uptime = this.finishedTime,
     isRunning: Boolean = this.isRunning
 ): AnimationState<T, V> =
-    AnimationState(value, this.typeConverter, velocityVector, lastFrameTime, endTime, isRunning)
+    AnimationState(
+        this.typeConverter, value, velocityVector, lastFrameTime, finishedTime, isRunning
+    )
 
 /**
  * Creates a new [AnimationState] of Float [value] type from a given [AnimationState] of the same
@@ -196,8 +200,8 @@
  *                 [AnimationState] by default.
  * @param lastFrameTime last frame time of the animation, same as the given [AnimationState] by
  *                      default
- * @param endTime the time that the animation ended, same as the given [AnimationState] by
- *                     default.
+ * @param finishedTime the time that the animation finished successfully, same as the given
+ *                     [AnimationState] by default.
  * @param isRunning whether the [AnimationState] is currently being updated by an animation.
  *                  Same as the given [AnimationState] by default
  *
@@ -208,11 +212,11 @@
     value: Float = this.value,
     velocity: Float = this.velocityVector.value,
     lastFrameTime: Uptime = this.lastFrameTime,
-    endTime: Uptime = this.finishedTime,
+    finishedTime: Uptime = this.finishedTime,
     isRunning: Boolean = this.isRunning
 ): AnimationState<Float, AnimationVector1D> =
     AnimationState(
-        value, this.typeConverter, AnimationVector(velocity), lastFrameTime, endTime, isRunning
+        this.typeConverter, value, AnimationVector(velocity), lastFrameTime, finishedTime, isRunning
     )
 
 /**
@@ -221,7 +225,8 @@
  * @param initialValue initial value of the [AnimationState]
  * @param initialVelocity initial velocity of the [AnimationState], 0 (i.e. no velocity) by default
  * @param lastFrameTime last frame time of the animation, [Uptime.Unspecified] by default
- * @param endTime the time that the animation ended, [Uptime.Unspecified] by default.
+ * @param finishedTime the time that the animation finished successfully, [Uptime.Unspecified] by
+ *                     default.
  * @param isRunning whether the [AnimationState] is currently being updated by an animation.
  *                  False by default
  *
@@ -231,15 +236,47 @@
     initialValue: Float,
     initialVelocity: Float = 0f,
     lastFrameTime: Uptime = Uptime.Unspecified,
-    endTime: Uptime = Uptime.Unspecified,
+    finishedTime: Uptime = Uptime.Unspecified,
     isRunning: Boolean = false
 ): AnimationState<Float, AnimationVector1D> {
     return AnimationState(
-        initialValue,
         Float.VectorConverter,
+        initialValue,
         AnimationVector(initialVelocity),
         lastFrameTime,
-        endTime,
+        finishedTime,
+        isRunning
+    )
+}
+
+/**
+ * Factory method for creating an [AnimationState] with an [initialValue] and an [initialVelocity].
+ *
+ * @param typeConverter [TwoWayConverter] to convert type [T] from and to [AnimationVector]
+ * @param initialValue initial value of the [AnimationState]
+ * @param initialVelocity initial velocity of the [AnimationState]
+ * @param lastFrameTime last frame time of the animation, [Uptime.Unspecified] by default
+ * @param finishedTime the time that the animation finished successfully, [Uptime.Unspecified] by
+ *                     default.
+ * @param isRunning whether the [AnimationState] is currently being updated by an animation.
+ *                  False by default
+ *
+ * @return A new [AnimationState] instance
+ */
+fun <T, V : AnimationVector> AnimationState(
+    typeConverter: TwoWayConverter<T, V>,
+    initialValue: T,
+    initialVelocity: T,
+    lastFrameTime: Uptime = Uptime.Unspecified,
+    finishedTime: Uptime = Uptime.Unspecified,
+    isRunning: Boolean = false
+): AnimationState<T, V> {
+    return AnimationState(
+        typeConverter,
+        initialValue,
+        typeConverter.convertToVector(initialVelocity),
+        lastFrameTime,
+        finishedTime,
         isRunning
     )
 }
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationVectors.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationVectors.kt
index 566a0c4..b04031a3 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationVectors.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationVectors.kt
@@ -78,6 +78,20 @@
     return this.newVector() as T
 }
 
+internal fun <T : AnimationVector> T.copy(): T {
+    val newVector = newInstance()
+    for (i in 0 until newVector.size) {
+        newVector[i] = this[i]
+    }
+    return newVector
+}
+
+internal fun <T : AnimationVector> T.copyFrom(source: T) {
+    for (i in 0 until size) {
+        this[i] = source[i]
+    }
+}
+
 /**
  * This class defines a 1D vector. It contains only one Float value that is initialized in the
  * constructor.
@@ -135,6 +149,7 @@
      */
     var v1: Float = v1
         internal set
+
     /**
      * Float value field for the second dimension of the 2D vector.
      */
@@ -189,11 +204,13 @@
      */
     var v1: Float = v1
         internal set
+
     /**
      * Float value field for the second dimension of the 3D vector.
      */
     var v2: Float = v2
         internal set
+
     /**
      * Float value field for the third dimension of the 3D vector.
      */
@@ -253,16 +270,19 @@
      */
     var v1: Float = v1
         internal set
+
     /**
      * Float value field for the second dimension of the 4D vector.
      */
     var v2: Float = v2
         internal set
+
     /**
      * Float value field for the third dimension of the 4D vector.
      */
     var v3: Float = v3
         internal set
+
     /**
      * Float value field for the fourth dimension of the 4D vector.
      */
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/DecayAnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/DecayAnimationSpec.kt
new file mode 100644
index 0000000..b19ff5b
--- /dev/null
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/DecayAnimationSpec.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2020 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 androidx.compose.animation.core
+
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.util.annotation.FloatRange
+
+/**
+ * [DecayAnimationSpec] stores the specification of an animation, including 1) the data type to be
+ * animated, and 2) the animation configuration (i.e. [VectorizedDecayAnimationSpec]) that will be
+ * used once the data (of type [T]) has been converted to [AnimationVector].
+ *
+ * Any type [T] can be animated by the system as long as a [TwoWayConverter] is supplied to convert
+ * the data type [T] from and to an [AnimationVector]. There are a number of converters
+ * available out of the box. For example, to animate [androidx.compose.ui.unit.IntOffset] the system
+ * uses [IntOffset.VectorConverter][IntOffset.Companion.VectorConverter] to convert the object to
+ * [AnimationVector2D], so that both x and y dimensions are animated independently with separate
+ * velocity tracking. This enables multidimensional objects to be animated in a true
+ * multi-dimensional way. It is particularly useful for smoothly handling animation interruptions
+ * (such as when the target changes during the animation).
+ */
+interface DecayAnimationSpec<T> {
+
+    /**
+     * Creates a [VectorizedDecayAnimationSpec] with the given [TwoWayConverter].
+     *
+     * The underlying animation system operates on [AnimationVector]s. [T] will be converted to
+     * [AnimationVector] to animate. [VectorizedDecayAnimationSpec] describes how the
+     * converted [AnimationVector] should be animated.
+     *
+     * @param typeConverter converts the type [T] from and to [AnimationVector] type
+     */
+    fun <V : AnimationVector> vectorize(
+        typeConverter: TwoWayConverter<T, V>
+    ): VectorizedDecayAnimationSpec<V>
+}
+
+/**
+ * Calculates the target value of a decay animation based on the [initialValue] and
+ * [initialVelocity], and the [typeConverter] that converts the given type [T] to [AnimationVector].
+ *
+ * @return target value where the animation will come to a natural stop
+ */
+fun <T, V : AnimationVector> DecayAnimationSpec<T>.calculateTargetValue(
+    typeConverter: TwoWayConverter<T, V>,
+    initialValue: T,
+    initialVelocity: T
+): T {
+    val vectorizedSpec = vectorize(typeConverter)
+    val targetVector = vectorizedSpec.getTarget(
+        typeConverter.convertToVector(initialValue),
+        typeConverter.convertToVector(initialVelocity)
+    )
+    return typeConverter.convertFromVector(targetVector)
+}
+
+/**
+ * Calculates the target value of a Float decay animation based on the [initialValue] and
+ * [initialVelocity].
+ *
+ * @return target value where the animation will come to a natural stop
+ */
+fun DecayAnimationSpec<Float>.calculateTargetValue(
+    initialValue: Float,
+    initialVelocity: Float
+): Float {
+    val vectorizedSpec = vectorize(Float.VectorConverter)
+    val targetVector = vectorizedSpec.getTarget(
+        AnimationVector(initialValue),
+        AnimationVector(initialVelocity)
+    )
+    return targetVector.value
+}
+
+/**
+ * Creates a decay animation spec where the friction/deceleration is always proportional to the
+ * velocity. As a result, the velocity goes under an exponential decay. The constructor parameter,
+ * [frictionMultiplier], can be tuned to adjust the amount of friction applied in the decay. The
+ * higher the multiplier, the higher the friction, the sooner the animation will stop, and the
+ * shorter distance the animation will travel with the same starting condition.
+ * [absVelocityThreshold] describes the absolute value of a velocity threshold, below which the
+ * animation is considered finished.
+ */
+fun <T> exponentialDecay(
+    @FloatRange(
+        from = 0.0,
+        // TODO(b/158069385): use POSITIVE_INFINITY constant once it's possible to do in MPP code.
+        to = 3.4e38, // POSITIVE_INFINITY,
+        fromInclusive = false
+    ) frictionMultiplier: Float = 1f,
+    @FloatRange(
+        from = 0.0,
+        // TODO(b/158069385): use POSITIVE_INFINITY constant once it's possible to do in MPP code.
+        to = 3.4e38, // POSITIVE_INFINITY,
+        fromInclusive = false
+    ) absVelocityThreshold: Float = 0.1f
+): DecayAnimationSpec<T> =
+    FloatExponentialDecaySpec(frictionMultiplier, absVelocityThreshold).generateDecayAnimationSpec()
+
+/**
+ * Creates a [DecayAnimationSpec] from a [FloatDecayAnimationSpec] by applying the given
+ * [FloatDecayAnimationSpec] on every dimension of the [AnimationVector] that [T] converts to.
+ */
+fun <T> FloatDecayAnimationSpec.generateDecayAnimationSpec(): DecayAnimationSpec<T> {
+    return DecayAnimationSpecImpl(this)
+}
+
+private class DecayAnimationSpecImpl<T>(
+    private val floatDecaySpec: FloatDecayAnimationSpec
+) : DecayAnimationSpec<T> {
+    override fun <V : AnimationVector> vectorize(
+        typeConverter: TwoWayConverter<T, V>
+    ): VectorizedDecayAnimationSpec<V> = VectorizedFloatDecaySpec(floatDecaySpec)
+}
+
+private class VectorizedFloatDecaySpec<V : AnimationVector>(
+    val floatDecaySpec: FloatDecayAnimationSpec
+) : VectorizedDecayAnimationSpec<V> {
+    private lateinit var valueVector: V
+    private lateinit var velocityVector: V
+    private lateinit var targetVector: V
+    override val absVelocityThreshold: Float = floatDecaySpec.absVelocityThreshold
+
+    override fun getValue(playTime: Long, initialValue: V, initialVelocity: V): V {
+        if (!::valueVector.isInitialized) {
+            valueVector = initialValue.newInstance()
+        }
+        for (i in 0 until valueVector.size) {
+            valueVector[i] = floatDecaySpec.getValue(playTime, initialValue[i], initialVelocity[i])
+        }
+        return valueVector
+    }
+
+    override fun getDurationMillis(initialValue: V, initialVelocity: V): Long {
+        var maxDuration = 0L
+        if (!::velocityVector.isInitialized) {
+            velocityVector = initialValue.newInstance()
+        }
+        for (i in 0 until velocityVector.size) {
+            maxDuration = maxOf(
+                maxDuration,
+                floatDecaySpec.getDurationMillis(initialValue[i], initialVelocity[i])
+            )
+        }
+        return maxDuration
+    }
+
+    override fun getVelocity(playTime: Long, initialValue: V, initialVelocity: V): V {
+        if (!::velocityVector.isInitialized) {
+            velocityVector = initialValue.newInstance()
+        }
+        for (i in 0 until velocityVector.size) {
+            velocityVector[i] = floatDecaySpec.getVelocity(
+                playTime,
+                initialValue[i],
+                initialVelocity[i]
+            )
+        }
+        return velocityVector
+    }
+
+    override fun getTarget(initialValue: V, initialVelocity: V): V {
+        if (!::targetVector.isInitialized) {
+            targetVector = initialValue.newInstance()
+        }
+        for (i in 0 until targetVector.size) {
+            targetVector[i] = floatDecaySpec.getTarget(
+                initialValue[i],
+                initialVelocity[i]
+            )
+        }
+        return targetVector
+    }
+}
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/DynamicTargetAnimation.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/DynamicTargetAnimation.kt
index 0751503..b80978c 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/DynamicTargetAnimation.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/DynamicTargetAnimation.kt
@@ -50,5 +50,10 @@
      * and the remaining velocity can be obtained via `onEnd` param in [AnimatedFloat.fling]
      * callback
      */
-    BoundReached
+    BoundReached,
+    // TODO: deprecate TargetReached
+    /**
+     * Animation has finished successfully without any interruption.
+     */
+    Finished
 }
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/FloatDecayAnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/FloatDecayAnimationSpec.kt
index 96302eb..7c2265d 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/FloatDecayAnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/FloatDecayAnimationSpec.kt
@@ -27,7 +27,6 @@
  * Animation<T>, DecayAnimation does not have an end value defined. The end value is a
  * result of the animation rather than an input.
  */
-// TODO: Figure out a better story for non-floats
 interface FloatDecayAnimationSpec {
     /**
      * This is the absolute value of a velocity threshold, below which the animation is considered
@@ -87,6 +86,12 @@
 
 private const val ExponentialDecayFriction = -4.2f
 
+@Deprecated(
+    "ExponentialDecay has been renamed to FloatExponentialDecaySpec",
+    replaceWith = ReplaceWith("FloatExponentialDecaySpec")
+)
+typealias ExponentialDecay = FloatExponentialDecaySpec
+
 /**
  * This is a decay animation where the friction/deceleration is always proportional to the velocity.
  * As a result, the velocity goes under an exponential decay. The constructor parameter, friction
@@ -94,7 +99,7 @@
  * multiplier, the higher the friction, the sooner the animation will stop, and the shorter distance
  * the animation will travel with the same starting condition.
  */
-class ExponentialDecay(
+class FloatExponentialDecaySpec(
     @FloatRange(
         from = 0.0,
         // TODO(b/158069385): use POSITIVE_INFINITY constant once it's possible to do in MPP code.
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt
index 6250793..a1c5bdb 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt
@@ -41,14 +41,13 @@
     block: (value: Float, velocity: Float) -> Unit
 ) {
     animate(
+        Float.VectorConverter,
         initialValue,
         targetValue,
-        Float.VectorConverter,
-        AnimationVector1D(initialVelocity),
-        animationSpec
-    ) { value, velocity ->
-        block(value, velocity.value)
-    }
+        initialVelocity,
+        animationSpec,
+        block
+    )
 }
 
 /**
@@ -56,8 +55,8 @@
  * the velocity reaches 0. This is often used after a fling gesture.
  *
  * [animationSpec] defines the decay animation that will be used for this animation. Some options
- * for this [animationSpec] include: [AndroidFlingDecaySpec][androidx.compose.foundation.animation
- * .AndroidFlingDecaySpec] and [ExponentialDecay]. [block] will be invoked on each animation frame
+ * for this [animationSpec] include: [androidFlingDecay][androidx.compose.foundation.animation
+ * .androidFlingDecay] and [exponentialDecay]. [block] will be invoked on each animation frame
  * with up-to-date value and velocity.
  *
  * This is a convenient method for decay animation. If there's a need to access more info related to
@@ -81,9 +80,9 @@
 /**
  * Target based animation for animating any data type [T], so long as [T] can be converted to an
  * [AnimationVector] using [typeConverter]. The animation will start from the [initialValue] and
- * animate to the [targetValue] value. The [initialVelocityVector] will be an all-0 [AnimationVector]
- * unless specified. [animationSpec] can be provided to create a specific look and feel for the
- * animation. By default, a [spring] will be used.
+ * animate to the [targetValue] value. The [initialVelocity] will be derived from an all-0
+ * [AnimationVector] unless specified. [animationSpec] can be provided to create a specific look and
+ * feel for the animation. By default, a [spring] will be used.
  *
  * This is a convenient method for target-based animation. If there's a need to access more info
  * related to the animation such as start time, target, etc, consider using
@@ -92,22 +91,24 @@
  * @see [AnimationState.animateTo]
  */
 suspend fun <T, V : AnimationVector> animate(
+    typeConverter: TwoWayConverter<T, V>,
     initialValue: T,
     targetValue: T,
-    typeConverter: TwoWayConverter<T, V>,
-    initialVelocityVector: V? = null,
+    initialVelocity: T? = null,
     animationSpec: AnimationSpec<T> = spring(),
-    block: (value: T, velocity: V) -> Unit
+    block: (value: T, velocity: T) -> Unit
 ) {
+    val initialVelocityVector = initialVelocity?.let { typeConverter.convertToVector(it) }
+        ?: typeConverter.convertToVector(initialValue).newInstance()
     val anim = TargetBasedAnimation(
         animationSpec = animationSpec,
         initialValue = initialValue,
         targetValue = targetValue,
-        converter = typeConverter,
+        typeConverter = typeConverter,
         initialVelocityVector = initialVelocityVector
     )
-    AnimationState(initialValue, typeConverter, initialVelocityVector).animate(anim) {
-        block(value, velocityVector)
+    AnimationState(typeConverter, initialValue, initialVelocityVector).animate(anim) {
+        block(value, typeConverter.convertFromVector(velocityVector))
     }
 }
 
@@ -143,7 +144,7 @@
         animationSpec = animationSpec,
         initialValue = value,
         targetValue = targetValue,
-        converter = typeConverter,
+        typeConverter = typeConverter,
         initialVelocityVector = velocityVector
     )
     animate(anim, if (sequentialAnimation) lastFrameTime else Uptime.Unspecified, block)
@@ -156,8 +157,8 @@
  * of a fling gesture.
  *
  * [animationSpec] defines the decay animation that will be used for this animation. Some options
- * for [animationSpec] include: [AndroidFlingDecaySpec][androidx.compose.foundation.animation
- * .AndroidFlingDecaySpec] and [ExponentialDecay].
+ * for [animationSpec] include: [androidFlingDecay][androidx.compose.foundation.animation
+ * .androidFlingDecay] and [exponentialDecay].
  *
  * During the animation, [block] will be invoked on every frame, and the [AnimationScope] will be
  * checked against cancellation before the animation continues. To cancel the animation from the
@@ -172,6 +173,12 @@
  * momentum, using the interruption time (captured in [AnimationState.lastFrameTime] creates
  * a smoother animation.
  */
+@Deprecated(
+    "Please use animateDecay that takes a [DecayAnimationSpec] instead",
+    ReplaceWith(
+        "animateDecay(animationSpec.generateDecayAnimationSpec(), sequentialAnimation, block)"
+    )
+)
 suspend fun AnimationState<Float, AnimationVector1D>.animateDecay(
     animationSpec: FloatDecayAnimationSpec,
     // Indicates whether the animation should start from last frame
@@ -179,7 +186,7 @@
     block: AnimationScope<Float, AnimationVector1D>.() -> Unit = {}
 ) {
     val anim = DecayAnimation(
-        anim = animationSpec,
+        animationSpec = animationSpec,
         initialValue = value,
         initialVelocity = velocityVector.value
     )
@@ -191,6 +198,44 @@
 }
 
 /**
+ * Decay animation that slows down from the current velocity and value captured in [AnimationState]
+ * until the velocity reaches 0. During the animation, the given [AnimationState] will be updated
+ * with the up-to-date value/velocity, frame time, etc. This is often used to animate the result
+ * of a fling gesture.
+ *
+ * [animationSpec] defines the decay animation that will be used for this animation. Some options
+ * for [animationSpec] include: [androidFlingDecay][androidx.compose.foundation.animation
+ * .androidFlingDecay] and [exponentialDecay].
+ *
+ * During the animation, [block] will be invoked on every frame, and the [AnimationScope] will be
+ * checked against cancellation before the animation continues. To cancel the animation from the
+ * [block], simply call [AnimationScope.cancelAnimation].  After [AnimationScope.cancelAnimation] is
+ * called, [block] will not be invoked again. The animation loop will exit after the [block]
+ * returns. All the animation related info can be accessed via [AnimationScope].
+ *
+ * [sequentialAnimation] indicates whether the animation should use the
+ * [AnimationState.lastFrameTime] as the starting time (if true), or start in a new frame. By
+ * default, [sequentialAnimation] is false, to start the animation in a few frame. In cases where
+ * an on-going animation is interrupted and a new animation is started to carry over the
+ * momentum, using the interruption time (captured in [AnimationState.lastFrameTime] creates
+ * a smoother animation.
+ */
+suspend fun <T, V : AnimationVector> AnimationState<T, V>.animateDecay(
+    animationSpec: DecayAnimationSpec<T>,
+    // Indicates whether the animation should start from last frame
+    sequentialAnimation: Boolean = false,
+    block: AnimationScope<T, V>.() -> Unit = {}
+) {
+    val anim = DecayAnimation<T, V>(
+        animationSpec = animationSpec,
+        initialValue = value,
+        initialVelocityVector = velocityVector,
+        typeConverter = typeConverter
+    )
+    animate(anim, if (sequentialAnimation) lastFrameTime else Uptime.Unspecified, block)
+}
+
+/**
  * This animation function runs the animation defined in the given [animation] from start to
  * finish. During the animation, the [AnimationState] will be updated with the up-to-date
  * value/velocity, frame time, etc.
@@ -211,7 +256,7 @@
 // TODO: This method uses AnimationState and Animation at the same time, it's potentially confusing
 // as to which is the source of truth for initial value/velocity. Consider letting [Animation] have
 // some suspend fun differently.
-private suspend fun <T, V : AnimationVector> AnimationState<T, V>.animate(
+internal suspend fun <T, V : AnimationVector> AnimationState<T, V>.animate(
     animation: Animation<T, V>,
     startTime: Uptime = Uptime.Unspecified,
     block: AnimationScope<T, V>.() -> Unit = {}
@@ -224,7 +269,7 @@
             if (startTime == Uptime.Unspecified) Uptime(withFrameNanos { it }) else startTime
         lateInitScope = AnimationScope(
             initialValue = initialValue,
-            typeConverter = animation.converter,
+            typeConverter = animation.typeConverter,
             initialVelocityVector = initialVelocityVector,
             lastFrameTime = startTimeSpecified,
             targetValue = animation.targetValue,
@@ -251,9 +296,11 @@
     }
 }
 
-private fun <T, V : AnimationVector> AnimationScope<T, V>.updateState(state: AnimationState<T, V>) {
+internal fun <T, V : AnimationVector> AnimationScope<T, V>.updateState(
+    state: AnimationState<T, V>
+) {
     state.value = value
-    state.velocityVector = velocityVector
+    state.velocityVector.copyFrom(velocityVector)
     state.finishedTime = finishedTime
     state.lastFrameTime = lastFrameTime
     state.isRunning = isRunning
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
index fa3482e..6cdfd0e 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
@@ -245,9 +245,9 @@
         internal fun onPlayTimeChanged(playTimeNanos: Long) {
             val anim = animation ?: TargetBasedAnimation<T, V>(
                 animationSpec,
+                typeConverter,
                 value,
                 targetValue,
-                typeConverter,
                 velocityVector
             ).also { animation = it }
             val playTimeMillis = (playTimeNanos - offsetTimeNanos) / 1_000_000L
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/TransitionAnimation.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/TransitionAnimation.kt
index 9bbeaa9..a269f80 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/TransitionAnimation.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/TransitionAnimation.kt
@@ -315,7 +315,7 @@
     startVelocity: V?,
     end: T
 ): Animation<T, V> =
-    TargetBasedAnimation(anim, start, end, typeConverter, startVelocity)
+    TargetBasedAnimation(anim, typeConverter, start, end, startVelocity)
 
 /**
  * Private class allows mutation on the prop values.
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedDecayAnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedDecayAnimationSpec.kt
new file mode 100644
index 0000000..fba75bb
--- /dev/null
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedDecayAnimationSpec.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2020 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 androidx.compose.animation.core
+
+/**
+ * [VectorizedDecayAnimationSpec]s are stateless vector based decay animation specifications.
+ * They do not assume any starting/ending conditions. Nor do they manage a lifecycle. All it stores
+ * is the configuration that is particular to the type of the decay animation: friction multiplier
+ * for [exponentialDecay]. Its stateless nature allows the same [VectorizedDecayAnimationSpec] to
+ * be reused by a few different running animations with different starting and ending values.
+ *
+ * Since [VectorizedDecayAnimationSpec]s are stateless, it requires starting value/velocity and
+ * ending value to be passed in, along with playtime, to calculate the value or velocity at that
+ * time. Play time here is the progress of the animation in terms of milliseconds, where 0 means the
+ * start of the animation and [getDurationMillis] returns the play time for the end of the
+ * animation.
+ *
+ * __Note__: For use cases where the starting values/velocity and ending values aren't expected
+ * to change, it is recommended to use [DecayAnimation] that caches these static values and hence
+ * does not require them to be supplied in the value/velocity calculation.
+ *
+ * @see DecayAnimation
+ */
+interface VectorizedDecayAnimationSpec<V : AnimationVector> {
+    /**
+     * This is the absolute value of a velocity threshold, below which the animation is considered
+     * finished.
+     */
+    val absVelocityThreshold: Float
+
+    /**
+     * Returns the value of the animation at the given time.
+     *
+     * @param playTime The time elapsed in milliseconds since the initialValue of the animation
+     * @param initialValue The initialValue value of the animation
+     * @param initialVelocity The initialValue velocity of the animation
+     */
+    fun getValue(
+        playTime: Long,
+        initialValue: V,
+        initialVelocity: V
+    ): V
+
+    /**
+     * Returns the duration of the decay animation, in milliseconds.
+     *
+     * @param initialValue initialValue value of the animation
+     * @param initialVelocity initialValue velocity of the animation
+     */
+    fun getDurationMillis(
+        initialValue: V,
+        initialVelocity: V
+    ): Long
+
+    /**
+     * Returns the velocity of the animation at the given time.
+     *
+     * @param playTime The time elapsed in milliseconds since the initialValue of the animation
+     * @param initialValue The initialValue value of the animation
+     * @param initialVelocity The initialValue velocity of the animation
+     */
+    fun getVelocity(
+        playTime: Long,
+        initialValue: V,
+        initialVelocity: V
+    ): V
+
+    /**
+     * Returns the target value of the animation based on the initial condition of the animation (
+     * i.e. initial value and initial velocity).
+     *
+     * @param initialValue The initial value of the animation
+     * @param initialVelocity The initial velocity of the animation
+     */
+    fun getTarget(
+        initialValue: V,
+        initialVelocity: V
+    ): V
+}
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimatableTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimatableTest.kt
new file mode 100644
index 0000000..3f9d010
--- /dev/null
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimatableTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2020 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 androidx.compose.animation.core
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Offset
+import junit.framework.TestCase
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
+import junit.framework.TestCase.assertTrue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.math.abs
+
+@RunWith(JUnit4::class)
+class AnimatableTest {
+    @Test
+    fun animateDecayTest() {
+        runBlocking {
+            val from = 9f
+            val initialVelocity = 20f
+            val decaySpec = FloatExponentialDecaySpec()
+            val anim = DecayAnimation(
+                decaySpec,
+                initialValue = from,
+                initialVelocity = initialVelocity
+            )
+            val clock = SuspendAnimationTest.TestFrameClock()
+            val interval = 50
+            withContext(clock) {
+                // Put in a bunch of frames 50 milliseconds apart
+                for (frameTimeMillis in 0..5000 step interval) {
+                    clock.frame(frameTimeMillis * 1_000_000L)
+                }
+                var playTimeMillis = 0L
+                val animatable = Animatable(9f)
+                val result = animatable.animateDecay(20f, animationSpec = exponentialDecay()) {
+                    assertTrue(isRunning)
+                    assertEquals(anim.targetValue, targetValue)
+                    TestCase.assertEquals(anim.getValue(playTimeMillis), value, 0.001f)
+                    TestCase.assertEquals(anim.getVelocity(playTimeMillis), velocity, 0.001f)
+                    playTimeMillis += interval
+                    TestCase.assertEquals(value, animatable.value, 0.0001f)
+                    TestCase.assertEquals(velocity, animatable.velocity, 0.0001f)
+                }
+                // After animation
+                assertEquals(anim.targetValue, animatable.value)
+                assertEquals(false, animatable.isRunning)
+                assertEquals(0f, animatable.velocity)
+                assertEquals(AnimationEndReason.Finished, result.endReason)
+                assertTrue(abs(result.endState.velocity) <= decaySpec.absVelocityThreshold)
+            }
+        }
+    }
+
+    @Test
+    fun animateToTest() {
+        runBlocking {
+            val anim = TargetBasedAnimation(
+                spring(dampingRatio = Spring.DampingRatioMediumBouncy), Float.VectorConverter,
+                initialValue = 0f, targetValue = 1f
+            )
+            val clock = SuspendAnimationTest.TestFrameClock()
+            val interval = 50
+            val animatable = Animatable(0f)
+            withContext(clock) {
+                // Put in a bunch of frames 50 milliseconds apart
+                for (frameTimeMillis in 0..5000 step interval) {
+                    clock.frame(frameTimeMillis * 1_000_000L)
+                }
+                var playTimeMillis = 0L
+                val result = animatable.animateTo(
+                    1f,
+                    spring(dampingRatio = Spring.DampingRatioMediumBouncy)
+                ) {
+                    assertTrue(isRunning)
+                    assertEquals(1f, targetValue)
+                    assertEquals(anim.getValue(playTimeMillis), value, 0.001f)
+                    assertEquals(anim.getVelocity(playTimeMillis), velocity, 0.001f)
+                    playTimeMillis += interval
+                }
+                // After animation
+                assertEquals(anim.targetValue, animatable.value)
+                assertEquals(0f, animatable.velocity)
+                assertEquals(false, animatable.isRunning)
+                assertEquals(AnimationEndReason.Finished, result.endReason)
+            }
+        }
+    }
+
+    @Test
+    fun animateToGenericTypeTest() =
+        runBlocking<Unit> {
+            val from = Offset(666f, 321f)
+            val to = Offset(919f, 864f)
+            val offsetToVector: TwoWayConverter<Offset, AnimationVector2D> =
+                TwoWayConverter(
+                    convertToVector = { AnimationVector2D(it.x, it.y) },
+                    convertFromVector = { Offset(it.v1, it.v2) }
+                )
+            val anim = TargetBasedAnimation(
+                tween(500), offsetToVector,
+                initialValue = from, targetValue = to
+            )
+            val clock = SuspendAnimationTest.TestFrameClock()
+            val interval = 50
+            val animatable = Animatable(
+                initialValue = from,
+                typeConverter = offsetToVector
+            )
+            coroutineScope {
+                withContext(clock) {
+                    // Put in a bunch of frames 50 milliseconds apart
+                    for (frameTimeMillis in 0..1000 step interval) {
+                        clock.frame(frameTimeMillis * 1_000_000L)
+                    }
+                    launch {
+                        // The first frame should start at 100ms
+                        var playTimeMillis = 0L
+                        val endReason = animatable.animateTo(
+                            to,
+                            animationSpec = tween(500)
+                        ) {
+                            assertTrue("PlayTime Millis: $playTimeMillis", isRunning)
+                            assertEquals(to, targetValue)
+                            val expectedValue = anim.getValue(playTimeMillis)
+                            assertEquals(
+                                "PlayTime Millis: $playTimeMillis",
+                                expectedValue.x,
+                                value.x,
+                                0.001f
+                            )
+                            assertEquals(
+                                "PlayTime Millis: $playTimeMillis",
+                                expectedValue.y,
+                                value.y,
+                                0.001f
+                            )
+                            playTimeMillis += interval
+
+                            if (playTimeMillis == 300L) {
+                                // Prematurely cancel the animation and check corresponding states
+                                stop()
+                                assertFalse(isRunning)
+                            }
+                        }
+
+                        assertEquals(AnimationEndReason.Interrupted, endReason)
+
+                        // Check that no more frames happened after cancel()
+                        assertEquals(playTimeMillis, 300L)
+                        assertFalse(animatable.isRunning)
+                        assertEquals(to, animatable.targetValue)
+                        assertEquals(AnimationVector(0f, 0f), animatable.velocityVector)
+                    }
+                }
+            }
+        }
+
+    @Test
+    fun animateToWithInterruption() {
+        runBlocking {
+            val anim1 = TargetBasedAnimation(
+                tween(200, easing = LinearEasing),
+                Float.VectorConverter,
+                0f,
+                200f
+            )
+            val clock = SuspendAnimationTest.TestFrameClock()
+            val interval = 50
+            coroutineScope {
+                withContext(clock) {
+                    val animatable = Animatable(0f)
+                    // Put in a bunch of frames 50 milliseconds apart
+                    for (frameTimeMillis in 0..1000 step interval) {
+                        clock.frame(frameTimeMillis * 1_000_000L)
+                    }
+                    // The first frame should start at 100ms
+                    var playTimeMillis by mutableStateOf(0L)
+                    launch {
+                        val result1 = animatable.animateTo(
+                            200f,
+                            animationSpec = tween(200, easing = LinearEasing)
+                        ) {
+                            assertTrue(isRunning)
+                            assertEquals(targetValue, 200f)
+                            assertEquals(anim1.getValue(playTimeMillis), value)
+                            assertEquals(anim1.getVelocity(playTimeMillis), velocity)
+
+                            assertTrue(playTimeMillis <= 100)
+                            if (playTimeMillis == 100L) {
+                                // Interrupt here
+                                animatable.interruptAt(100, interval, this@withContext)
+                            }
+                            playTimeMillis += 50L
+                        }
+                        // Check states after animation ends
+                        assertFalse(animatable.isRunning)
+                        assertEquals(AnimationEndReason.Interrupted, result1.endReason)
+                        assertEquals(300f, animatable.targetValue)
+                        assertEquals(300f, animatable.value)
+                        assertEquals(0f, animatable.velocity)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun Animatable<Float, *>.interruptAt(
+        playTime: Long,
+        interval: Int,
+        parentScope: CoroutineScope
+    ) {
+        // Never block send.
+        val playTimeChannel = Channel<Long>(Channel.UNLIMITED)
+        parentScope.launch {
+            var playTimeMillis2 = playTime
+            val anim2 = TargetBasedAnimation(
+                spring(),
+                Float.VectorConverter,
+                value,
+                300f,
+                velocity
+            )
+            val result2 = animateTo(300f, spring()) {
+                launch {
+                    playTimeChannel.send(playTimeMillis2)
+                }
+                assertTrue(isRunning)
+                assertEquals(300f, targetValue)
+                assertEquals(
+                    anim2.getValue((playTimeMillis2 - 100)),
+                    value
+                )
+                assertEquals(
+                    anim2.getVelocity((playTimeMillis2 - 100)),
+                    velocity
+                )
+                playTimeMillis2 += interval
+            }
+            assertFalse(isRunning)
+            assertEquals(AnimationEndReason.Finished, result2.endReason)
+            assertEquals(300f, targetValue)
+            assertEquals(300f, value)
+            assertEquals(0f, velocity)
+        }
+        runBlocking {
+            // Make sure we receive a frame before returning
+            playTimeChannel.receive()
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationTest.kt
index edf6467..6310405 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationTest.kt
@@ -271,7 +271,13 @@
         end: AnimationVector4D,
         startVelocity: AnimationVector4D
     ) {
-        val fixedAnim = anim.createAnimation(start, end, startVelocity)
+        val fixedAnim = TargetBasedAnimation(
+            anim,
+            TwoWayConverter({ it }, { it }),
+            start,
+            end,
+            startVelocity
+        )
         for (playtime in 0..fixedAnim.durationMillis step 100) {
             assertEquals(
                 anim.getValue(playtime, start, end, startVelocity),
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/DecayAnimationTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/DecayAnimationTest.kt
index d1fdda5..07af20a 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/DecayAnimationTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/DecayAnimationTest.kt
@@ -29,7 +29,7 @@
 
     @Test
     fun testExponentialDecay() {
-        val anim = ExponentialDecay(absVelocityThreshold = 2.0f)
+        val anim = FloatExponentialDecaySpec(absVelocityThreshold = 2.0f)
         val startValue = 200f
         val startVelocity = -800f
 
@@ -70,12 +70,12 @@
     fun testDecayThreshold() {
         // TODO: Use parameterized tests
         val threshold = 500f
-        val anim1 = ExponentialDecay(absVelocityThreshold = threshold)
-        val anim2 = ExponentialDecay(absVelocityThreshold = 0f)
+        val anim1 = FloatExponentialDecaySpec(absVelocityThreshold = threshold)
+        val anim2 = FloatExponentialDecaySpec(absVelocityThreshold = 0f)
 
         val startValue = 2000f
         val startVelocity = 800f
-        val fullAnim = ExponentialDecay(absVelocityThreshold = 0f).createAnimation(
+        val fullAnim = FloatExponentialDecaySpec(absVelocityThreshold = 0f).createAnimation(
             startValue,
             startVelocity
         )
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/PhysicsAnimationTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/PhysicsAnimationTest.kt
index df61bf8..1ad5905 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/PhysicsAnimationTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/PhysicsAnimationTest.kt
@@ -45,8 +45,7 @@
         val end = 500
         val playTime = 150L
         val animation = TargetBasedAnimation(
-            spring(), start, end, 0,
-            Int.VectorConverter
+            spring(), Int.VectorConverter, start, end, 0
         )
 
         val velocity = animation.getVelocity(playTime)
@@ -65,8 +64,7 @@
         val interruptionTime = 150L
 
         val animation = TargetBasedAnimation(
-            spring(), start1, end1, 0f,
-            Float.VectorConverter
+            spring(), Float.VectorConverter, start1, end1, 0f
         )
 
         val interruptionValue = animation.getValue(interruptionTime)
@@ -79,8 +77,7 @@
         val startVelocity2 = interruptionVelocity
 
         val animation2 = TargetBasedAnimation(
-            spring(), start2, end2, startVelocity2,
-            Float.VectorConverter
+            spring(), Float.VectorConverter, start2, end2, startVelocity2
         )
         // let's verify values after 15 ms of the second animation
         val playTime = 15L
@@ -117,7 +114,7 @@
             initialValue = startValue,
             targetValue = endValue,
             initialVelocity = startVelocity,
-            converter = Float.VectorConverter
+            typeConverter = Float.VectorConverter
         )
 
         assertEquals(
@@ -150,7 +147,7 @@
             initialValue = startValue,
             targetValue = endValue,
             initialVelocity = startVelocity,
-            converter = Float.VectorConverter
+            typeConverter = Float.VectorConverter
         )
 
         assertEquals(
@@ -183,7 +180,7 @@
             initialValue = startValue,
             targetValue = endValue,
             initialVelocity = startVelocity,
-            converter = Float.VectorConverter
+            typeConverter = Float.VectorConverter
         )
 
         assertEquals(
@@ -202,10 +199,10 @@
     fun testEndSnapping() {
         TargetBasedAnimation(
             spring(),
+            Float.VectorConverter,
             0f,
             100f,
-            0f,
-            Float.VectorConverter
+            0f
         ).also { animation ->
             assertEquals(0f, animation.getVelocityVector(animation.durationMillis).value)
             assertEquals(100f, animation.getValue(animation.durationMillis))
@@ -306,11 +303,12 @@
         startVelocity: Float,
         endValue: Float
     ): Animation<Float, AnimationVector1D> {
-        return this.createAnimation(
+        return TargetBasedAnimation(
+            animationSpec = this,
             initialValue = startValue,
             targetValue = endValue,
             initialVelocityVector = AnimationVector(startVelocity),
-            converter = Float.VectorConverter
+            typeConverter = Float.VectorConverter
         )
     }
 
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt
index 6524f1f..b6873c4 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt
@@ -41,9 +41,9 @@
 
         val animationWrapper = TargetBasedAnimation(
             repeat,
+            Float.VectorConverter,
             0f,
-            0f,
-            Float.VectorConverter
+            0f
         )
 
         assertThat(repeat.at(0)).isEqualTo(0f)
@@ -84,9 +84,9 @@
 
         val repeatAnim = TargetBasedAnimation(
             repeat,
+            Float.VectorConverter,
             0f,
-            100f,
-            Float.VectorConverter
+            100f
         )
 
         for (playtime in 0..100L) {
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SnapAnimationTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SnapAnimationTest.kt
index 3734e53..eb2f71d 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SnapAnimationTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SnapAnimationTest.kt
@@ -29,9 +29,9 @@
         val animation = VectorizedSnapSpec<AnimationVector1D>()
         val animationWrapper = TargetBasedAnimation(
             animation,
+            Float.VectorConverter,
             0f,
-            0f,
-            Float.VectorConverter
+            0f
         )
 
         assertThat(animation.at(0)).isEqualTo(1f)
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SuspendAnimationTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SuspendAnimationTest.kt
index 7fd583d..6846b12 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SuspendAnimationTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/SuspendAnimationTest.kt
@@ -37,8 +37,8 @@
     fun animateFloatVariantTest() =
         runBlocking {
             val anim = TargetBasedAnimation(
-                spring(dampingRatio = Spring.DampingRatioMediumBouncy),
-                initialValue = 0f, targetValue = 1f, Float.VectorConverter
+                spring(dampingRatio = Spring.DampingRatioMediumBouncy), Float.VectorConverter,
+                initialValue = 0f, targetValue = 1f
             )
             val clock = TestFrameClock()
             val interval = 50
@@ -70,8 +70,7 @@
                     convertFromVector = { Offset(it.v1, it.v2) }
                 )
             val anim = TargetBasedAnimation(
-                tween(500),
-                initialValue = from, targetValue = to, offsetToVector
+                tween(500), offsetToVector, initialValue = from, targetValue = to
             )
             val clock = TestFrameClock()
             val interval = 50
@@ -82,8 +81,8 @@
                 }
                 var playTimeMillis = 0L
                 animate(
+                    offsetToVector,
                     from, to,
-                    typeConverter = offsetToVector,
                     animationSpec = tween(500)
                 ) { value, _ ->
                     val expectedValue = anim.getValue(playTimeMillis)
@@ -100,7 +99,7 @@
             val from = 666f
             val velocity = 999f
             val anim = DecayAnimation(
-                ExponentialDecay(),
+                FloatExponentialDecaySpec(),
                 initialValue = from, initialVelocity = velocity
             )
             val clock = TestFrameClock()
@@ -113,7 +112,7 @@
                 var playTimeMillis = 0L
                 animateDecay(
                     from, velocity,
-                    animationSpec = ExponentialDecay()
+                    animationSpec = FloatExponentialDecaySpec()
                 ) { value, velocity ->
                     assertEquals(anim.getValue(playTimeMillis), value, 0.001f)
                     assertEquals(anim.getVelocity(playTimeMillis), velocity, 0.001f)
@@ -133,8 +132,7 @@
                     convertFromVector = { Offset(it.v1, it.v2) }
                 )
             val anim = TargetBasedAnimation(
-                tween(500),
-                initialValue = from, targetValue = to, offsetToVector
+                tween(500), offsetToVector, initialValue = from, targetValue = to
             )
             val clock = TestFrameClock()
             val interval = 50
@@ -189,7 +187,7 @@
             val from = 9f
             val initialVelocity = 20f
             val anim = DecayAnimation(
-                ExponentialDecay(),
+                FloatExponentialDecaySpec(),
                 initialValue = from, initialVelocity = initialVelocity
             )
             val clock = TestFrameClock()
@@ -201,7 +199,9 @@
                 }
                 var playTimeMillis = 0L
                 val state = AnimationState(9f, 20f)
-                state.animateDecay(animationSpec = ExponentialDecay()) {
+                state.animateDecay(
+                    FloatExponentialDecaySpec().generateDecayAnimationSpec()
+                ) {
                     assertEquals(anim.getValue(playTimeMillis), value, 0.001f)
                     assertEquals(anim.getVelocity(playTimeMillis), velocity, 0.001f)
                     playTimeMillis += interval
@@ -211,7 +211,7 @@
             }
         }
 
-    private class TestFrameClock : MonotonicFrameClock {
+    internal class TestFrameClock : MonotonicFrameClock {
         // Make the send non-blocking
         private val frameCh = Channel<Long>(Channel.UNLIMITED)
 
diff --git a/compose/animation/animation/api/current.txt b/compose/animation/animation/api/current.txt
index 49f9db8..5008b43 100644
--- a/compose/animation/animation/api/current.txt
+++ b/compose/animation/animation/api/current.txt
@@ -134,6 +134,7 @@
   }
 
   public final class SingleValueAnimationKt {
+    method public static androidx.compose.animation.core.Animatable<androidx.compose.ui.graphics.Color,androidx.compose.animation.core.AnimationVector4D> Animatable-8_81llA(long initialValue);
     method @Deprecated @androidx.compose.runtime.Composable public static float animate(float target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? endListener);
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
diff --git a/compose/animation/animation/api/public_plus_experimental_current.txt b/compose/animation/animation/api/public_plus_experimental_current.txt
index 49f9db8..5008b43 100644
--- a/compose/animation/animation/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation/api/public_plus_experimental_current.txt
@@ -134,6 +134,7 @@
   }
 
   public final class SingleValueAnimationKt {
+    method public static androidx.compose.animation.core.Animatable<androidx.compose.ui.graphics.Color,androidx.compose.animation.core.AnimationVector4D> Animatable-8_81llA(long initialValue);
     method @Deprecated @androidx.compose.runtime.Composable public static float animate(float target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? endListener);
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
diff --git a/compose/animation/animation/api/restricted_current.txt b/compose/animation/animation/api/restricted_current.txt
index 49f9db8..5008b43 100644
--- a/compose/animation/animation/api/restricted_current.txt
+++ b/compose/animation/animation/api/restricted_current.txt
@@ -134,6 +134,7 @@
   }
 
   public final class SingleValueAnimationKt {
+    method public static androidx.compose.animation.core.Animatable<androidx.compose.ui.graphics.Color,androidx.compose.animation.core.AnimationVector4D> Animatable-8_81llA(long initialValue);
     method @Deprecated @androidx.compose.runtime.Composable public static float animate(float target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? endListener);
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
index 59b6638..8437e70 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
@@ -33,13 +33,6 @@
             )
         ),
         DemoCategory(
-            "Single Value Animation Demos",
-            listOf(
-                ComposableDemo("Animated scrolling") { FancyScrollingDemo() },
-                ComposableDemo("animate()") { SingleValueAnimationDemo() },
-            )
-        ),
-        DemoCategory(
             "Layout Animation Demos",
             listOf(
                 ComposableDemo("Animate Content Size") { AnimateContentSizeDemo() },
@@ -54,7 +47,10 @@
         DemoCategory(
             "Suspend Animation Demos",
             listOf(
+                ComposableDemo("Animated scrolling") { FancyScrollingDemo() },
+                ComposableDemo("animateAsState()") { SingleValueAnimationDemo() },
                 ComposableDemo("Follow the tap") { SuspendAnimationDemo() },
+                ComposableDemo("Game of fling") { FlingGame() },
                 ComposableDemo("Infinitely Animating") { InfiniteAnimationDemo() },
                 ComposableDemo("Spring back scrolling") { SpringBackScrollingDemo() },
                 ComposableDemo("Swipe to dismiss") { SwipeToDismissDemo() },
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/FancyScrollingDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/FancyScrollingDemo.kt
index 38bfcb5..ec2943c 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/FancyScrollingDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/FancyScrollingDemo.kt
@@ -17,11 +17,12 @@
 package androidx.compose.animation.demos
 
 import android.util.Log
-import androidx.compose.animation.animatedFloat
+import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.SpringSpec
-import androidx.compose.animation.core.TargetAnimation
-import androidx.compose.animation.core.fling
+import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.animation.core.exponentialDecay
 import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.draggable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
@@ -30,15 +31,16 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.gesture.DragObserver
-import androidx.compose.ui.gesture.rawDragGestureFilter
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import kotlinx.coroutines.launch
 import kotlin.math.roundToInt
 
 const val DEBUG = false
@@ -51,40 +53,39 @@
             fontSize = 20.sp,
             modifier = Modifier.padding(40.dp)
         )
-        val animScroll = animatedFloat(0f)
+        val animScroll = remember { Animatable(0f) }
         val itemWidth = remember { mutableStateOf(0f) }
-        val gesture = Modifier.rawDragGestureFilter(
-            dragObserver = object : DragObserver {
-                override fun onDrag(dragDistance: Offset): Offset {
-                    // Snap to new drag position
-                    animScroll.snapTo(animScroll.value + dragDistance.x)
-                    return dragDistance
-                }
+        val scope = rememberCoroutineScope()
+        val modifier = Modifier.draggable(
+            orientation = Orientation.Horizontal,
+            onDrag = { delta: Float ->
+                // Snap to new drag position
+                animScroll.snapTo(animScroll.value + delta)
+            },
 
-                override fun onStop(velocity: Offset) {
+            onDragStopped = { velocity: Float ->
 
-                    // Uses default decay animation to calculate where the fling will settle,
-                    // and adjust that position as needed. The target animation will be used for
-                    // animating to the adjusted target.
-                    animScroll.fling(
-                        velocity.x,
-                        adjustTarget = { target ->
-                            // Adjust the target position to center align the item
-                            var rem = target % itemWidth.value
-                            if (rem < 0) {
-                                rem += itemWidth.value
-                            }
-                            TargetAnimation(
-                                (target - rem),
-                                SpringSpec(dampingRatio = 2.0f, stiffness = 100f)
-                            )
-                        }
+                // Uses default decay animation to calculate where the fling will settle,
+                // and adjust that position as needed. The target animation will be used for
+                // animating to the adjusted target.
+                scope.launch {
+                    val decay = exponentialDecay<Float>()
+                    val target = decay.calculateTargetValue(animScroll.value, velocity)
+                    // Adjust the target position to center align the item
+                    var rem = target % itemWidth.value
+                    if (rem < 0) {
+                        rem += itemWidth.value
+                    }
+                    animScroll.animateTo(
+                        targetValue = target - rem,
+                        initialVelocity = velocity,
+                        animationSpec = SpringSpec(dampingRatio = 2.0f, stiffness = 100f)
                     )
                 }
             }
         )
 
-        Canvas(gesture.fillMaxWidth().preferredHeight(400.dp)) {
+        Canvas(modifier.fillMaxWidth().preferredHeight(400.dp)) {
             val width = size.width / 2f
             val scroll = animScroll.value + width / 2
             itemWidth.value = width
@@ -92,7 +93,7 @@
                 Log.w(
                     "Anim",
                     "Drawing items with updated" +
-                        " AnimatedFloat: ${animScroll.value}"
+                        " Scroll: ${animScroll.value}"
                 )
             }
             drawItems(scroll, width, size.height)
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/FlingGame.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/FlingGame.kt
new file mode 100644
index 0000000..9d7d627
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/FlingGame.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2020 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 androidx.compose.animation.demos
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationEndReason
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.exponentialDecay
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.drag
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.gesture.util.VelocityTracker
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.positionChange
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+@Composable
+fun FlingGame() {
+    Box(Modifier.fillMaxSize()) {
+        Text("Throw me around, see what happens", Modifier.align(Alignment.Center))
+        val anim = remember { Animatable(Offset(100f, 100f), Offset.VectorConverter) }
+        Box(
+            Modifier.fillMaxSize().pointerInput {
+                coroutineScope {
+                    while (true) {
+                        val pointerId = awaitPointerEventScope {
+                            awaitFirstDown().run {
+                                anim.snapTo(current.position)
+                                id
+                            }
+                        }
+                        val velocityTracker = VelocityTracker()
+                        awaitPointerEventScope {
+                            drag(pointerId) {
+                                anim.snapTo(anim.value + it.positionChange())
+                                velocityTracker.addPosition(
+                                    it.current.uptime,
+                                    it.current.position
+                                )
+                            }
+                        }
+                        val (x, y) = velocityTracker.calculateVelocity()
+                        anim.updateBounds(
+                            Offset(100f, 100f),
+                            Offset(size.width.toFloat() - 100f, size.height.toFloat() - 100f)
+                        )
+                        launch {
+                            var startVelocity = Offset(x, y)
+                            do {
+                                val result = anim.animateDecay(startVelocity, exponentialDecay())
+                                startVelocity = result.endState.velocity
+
+                                with(anim) {
+                                    if (value.x == upperBound?.x || value.x == lowerBound?.x) {
+                                        // x dimension hits bounds
+                                        startVelocity = startVelocity.copy(x = -startVelocity.x)
+                                    }
+                                    if (value.y == upperBound?.y || value.y == lowerBound?.y) {
+                                        // y dimension hits bounds
+                                        startVelocity = startVelocity.copy(y = -startVelocity.y)
+                                    }
+                                }
+                            } while (result.endReason == AnimationEndReason.BoundReached)
+                        }
+                    }
+                }
+            }.drawWithContent {
+                drawCircle(Color(0xff3c1361), 100f, anim.value)
+            }
+        )
+    }
+}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt
index 7cb7a93..966e5da 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt
@@ -16,23 +16,52 @@
 
 package androidx.compose.animation.demos
 
-import androidx.compose.animation.animateAsState
+import androidx.compose.animation.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.spring
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
 
 @Composable
 fun SingleValueAnimationDemo() {
     val enabled = remember { mutableStateOf(true) }
-    val color by animateAsState(if (enabled.value) Color.Green else Color.Red)
+    val alpha: Float by animateAsState(if (enabled.value) 1f else 0.5f)
+    val color = myAnimate(
+        if (enabled.value) Color.Green else Color.Magenta,
+        spring()
+    ) {
+        println("Finished at color $it")
+    }
     Box(
-        Modifier.fillMaxSize().clickable { enabled.value = !enabled.value }.background(color)
+        Modifier.fillMaxSize().clickable(indication = null) { enabled.value = !enabled.value }
+            .graphicsLayer(alpha = alpha)
+            .background(color)
     )
-}
\ No newline at end of file
+}
+
+@Composable
+private fun myAnimate(
+    targetValue: Color,
+    animationSpec: AnimationSpec<Color>,
+    onFinished: (Color) -> Unit
+): Color {
+    val color = remember { Animatable(targetValue) }
+    val finishedListener = rememberUpdatedState(onFinished)
+    LaunchedEffect(targetValue, animationSpec) {
+        color.animateTo(targetValue, animationSpec)
+        finishedListener.value(targetValue)
+    }
+    return color.value
+}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringBackScrollingDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringBackScrollingDemo.kt
index e9d301f..aa12144 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringBackScrollingDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringBackScrollingDemo.kt
@@ -17,12 +17,12 @@
 package androidx.compose.animation.demos
 
 import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.ExponentialDecay
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.animateDecay
 import androidx.compose.animation.core.animateTo
+import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.animation.core.exponentialDecay
 import androidx.compose.animation.core.isFinished
-import androidx.compose.animation.core.velocity
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
@@ -90,14 +90,15 @@
                     launch {
                         mutatorMutex.mutate {
                             animation = AnimationState(scrollPosition, velocity)
-                            val target = ExponentialDecay().getTarget(scrollPosition, velocity)
+                            val target = exponentialDecay<Float>()
+                                .calculateTargetValue(scrollPosition, velocity)
                             val springBackTarget: Float = calculateSpringBackTarget(
                                 target,
                                 velocity,
                                 itemWidth.value
                             )
 
-                            animation.animateDecay(ExponentialDecay()) {
+                            animation.animateDecay(exponentialDecay()) {
                                 scrollPosition = this.value
                                 // Spring back as soon as the target position is crossed.
                                 if ((this.velocity > 0 && value > springBackTarget) ||
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SuspendAnimationDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SuspendAnimationDemo.kt
index 070ac30..3e51340b 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SuspendAnimationDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SuspendAnimationDemo.kt
@@ -16,80 +16,11 @@
 
 package androidx.compose.animation.demos
 
-import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.animateTo
-import androidx.compose.animation.core.isFinished
-import androidx.compose.animation.core.spring
-import androidx.compose.foundation.MutatorMutex
-import androidx.compose.foundation.background
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.Text
+import androidx.compose.animation.core.samples.AnimatableAnimateToGenericsType
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-import kotlin.math.roundToInt
 
 @Composable
 fun SuspendAnimationDemo() {
-    var animStateX by remember {
-        mutableStateOf(AnimationState(0f))
-    }
-    var animStateY by remember {
-        mutableStateOf(AnimationState(0f))
-    }
-    val mutex = remember { MutatorMutex() }
-
-    Box(
-        Modifier.fillMaxSize().background(Color(0xffb99aff)).pointerInput {
-            coroutineScope {
-                while (true) {
-                    val offset = awaitPointerEventScope {
-                        awaitFirstDown().current.position
-                    }
-                    val x = offset.x
-                    val y = offset.y
-                    mutex.mutate {
-                        launch {
-                            animStateX.animateTo(
-                                x,
-                                sequentialAnimation = !animStateX.isFinished,
-                                animationSpec = spring(stiffness = Spring.StiffnessLow)
-                            )
-                        }
-                        launch {
-                            animStateY.animateTo(
-                                y,
-                                sequentialAnimation = !animStateY.isFinished,
-                                animationSpec = spring(stiffness = Spring.StiffnessLow)
-                            )
-                        }
-                    }
-                }
-            }
-        }
-    ) {
-        Text("Tap anywhere", Modifier.align(Alignment.Center))
-        Box(
-            Modifier
-                .offset { IntOffset(animStateX.value.roundToInt(), animStateY.value.roundToInt()) }
-                .size(40.dp)
-                .background(Color(0xff3c1361), CircleShape)
-        )
-    }
+    // Pulling from sample code
+    AnimatableAnimateToGenericsType()
 }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SwipeToDismissDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SwipeToDismissDemo.kt
index 93ca71b..5afd044 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SwipeToDismissDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SwipeToDismissDemo.kt
@@ -16,12 +16,9 @@
 
 package androidx.compose.animation.demos
 
-import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.animateDecay
-import androidx.compose.animation.core.animateTo
-import androidx.compose.foundation.MutatePriority
-import androidx.compose.foundation.MutatorMutex
-import androidx.compose.foundation.animation.AndroidFlingDecaySpec
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.foundation.animation.androidFlingDecay
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.verticalDrag
@@ -35,9 +32,10 @@
 import androidx.compose.material.Button
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.onCommit
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -52,17 +50,15 @@
 import androidx.compose.ui.unit.sp
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
-import kotlin.math.abs
 import kotlin.math.roundToInt
 
 @Composable
 fun SwipeToDismissDemo() {
     Column {
         var index by remember { mutableStateOf(0) }
-        val dismissState = remember { DismissState() }
         Box(Modifier.height(300.dp).fillMaxWidth()) {
             Box(
-                Modifier.swipeToDismiss(dismissState).align(Alignment.BottomCenter).size(150.dp)
+                Modifier.swipeToDismiss(index).align(Alignment.BottomCenter).size(150.dp)
                     .background(pastelColors[index])
             )
         }
@@ -74,8 +70,6 @@
         Button(
             onClick = {
                 index = (index + 1) % pastelColors.size
-                dismissState.alpha = 1f
-                dismissState.offset = 0f
             },
             modifier = Modifier.align(Alignment.CenterHorizontally)
         ) {
@@ -84,68 +78,57 @@
     }
 }
 
-private fun Modifier.swipeToDismiss(dismissState: DismissState): Modifier = composed {
-    val mutatorMutex = remember { MutatorMutex() }
-
+private fun Modifier.swipeToDismiss(index: Int): Modifier = composed {
+    val animatedOffset = remember { Animatable(0f) }
+    val height = remember { mutableStateOf(0) }
+    onCommit(index) {
+        animatedOffset.snapTo(0f)
+    }
     this.pointerInput {
-        fun updateOffset(value: Float) {
-            dismissState.offset = value
-            dismissState.alpha = 1f - abs(dismissState.offset / size.height)
-        }
         coroutineScope {
             while (true) {
                 val pointerId = awaitPointerEventScope {
                     awaitFirstDown().id
                 }
+                height.value = size.height
                 val velocityTracker = VelocityTracker()
-                // Set a high priority on the mutatorMutex for gestures
-                mutatorMutex.mutate(MutatePriority.UserInput) {
-                    awaitPointerEventScope {
-                        verticalDrag(pointerId) {
-                            updateOffset(dismissState.offset + it.positionChange().y)
-                            velocityTracker.addPosition(
-                                it.current.uptime,
-                                it.current.position
-                            )
-                        }
+                awaitPointerEventScope {
+                    verticalDrag(pointerId) {
+                        animatedOffset.snapTo(animatedOffset.value + it.positionChange().y)
+                        velocityTracker.addPosition(
+                            it.current.uptime,
+                            it.current.position
+                        )
                     }
                 }
                 val velocity = velocityTracker.calculateVelocity().y
                 launch {
-                    // Use mutatorMutex to make sure drag gesture would cancel any on-going
-                    // animation job.
-                    mutatorMutex.mutate {
-                        // Either fling out of the sight, or snap back
-                        val animationState = AnimationState(dismissState.offset, velocity)
-                        val decay = AndroidFlingDecaySpec(this@pointerInput)
-                        if (decay.getTarget(dismissState.offset, velocity) >= -size.height) {
-                            // Not enough velocity to be dismissed
-                            animationState.animateTo(0f) {
-                                updateOffset(value)
-                            }
-                        } else {
-                            animationState.animateDecay(decay) {
-                                // End animation early if it reaches the bounds
-                                if (value <= -size.height) {
-                                    cancelAnimation()
-                                    updateOffset(-size.height.toFloat())
-                                } else {
-                                    updateOffset(value)
-                                }
-                            }
-                        }
+                    // Either fling out of the sight, or snap back
+                    val decay = androidFlingDecay<Float>(this@pointerInput)
+                    if (decay.calculateTargetValue(
+                            animatedOffset.value,
+                            velocity
+                        ) >= -size.height
+                    ) {
+                        // Not enough velocity to be dismissed
+                        animatedOffset.animateTo(0f, initialVelocity = velocity)
+                    } else {
+                        animatedOffset.updateBounds(
+                            lowerBound = -size.height.toFloat()
+                        )
+                        animatedOffset.animateDecay(velocity, decay)
                     }
                 }
             }
         }
-    }
-        .offset { IntOffset(0, dismissState.offset.roundToInt()) }
-        .graphicsLayer(alpha = dismissState.alpha)
+    }.offset { IntOffset(0, animatedOffset.value.roundToInt()) }
+        .graphicsLayer(alpha = calculateAlpha(animatedOffset.value, height.value))
 }
 
-private class DismissState {
-    var alpha by mutableStateOf(1f)
-    var offset by mutableStateOf(0f)
+private fun calculateAlpha(offset: Float, size: Int): Float {
+    if (size <= 0) return 1f
+    val alpha = (offset + size) / size
+    return alpha.coerceIn(0f, 1f)
 }
 
 internal val pastelColors = listOf(
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt
index 712e84a..6f506e3 100644
--- a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt
@@ -17,8 +17,10 @@
 package androidx.compose.animation.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.animation.Animatable
 import androidx.compose.animation.animate
 import androidx.compose.animation.animateAsState
+import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.AnimationVector2D
 import androidx.compose.animation.core.TwoWayConverter
 import androidx.compose.foundation.background
@@ -29,8 +31,10 @@
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.geometry.Offset
@@ -127,4 +131,27 @@
         )
         Box(modifier = Modifier.background(color))
     }
+}
+
+@Sampled
+fun AnimatableColor() {
+    @Composable
+    fun animate(
+        targetValue: Color,
+        animationSpec: AnimationSpec<Color>,
+        onFinished: (Color) -> Unit
+    ): Color {
+        // Creates an Animatable of Color, and remembers it.
+        val color = remember { Animatable(targetValue) }
+        val finishedListener = rememberUpdatedState(onFinished)
+        // Launches a new coroutine whenever the target value or animation spec has changed. This
+        // automatically cancels the previous job/animation.
+        LaunchedEffect(targetValue, animationSpec) {
+            color.animateTo(targetValue, animationSpec)
+            // Invokes finished listener. This line will not be executed if the job gets canceled
+            // halfway through an animation.
+            finishedListener.value(targetValue)
+        }
+        return color.value
+    }
 }
\ No newline at end of file
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt
index 04a4d98..5de481d 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt
@@ -18,16 +18,19 @@
 
 package androidx.compose.animation
 
+import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationEndReason
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.AnimationState
 import androidx.compose.animation.core.AnimationVector
 import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.AnimationVector4D
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.TwoWayConverter
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.VisibilityThreshold
 import androidx.compose.animation.core.animateAsState
+import androidx.compose.animation.core.animateDecay
 import androidx.compose.animation.core.animateTo
 import androidx.compose.animation.core.isFinished
 import androidx.compose.animation.core.spring
@@ -579,4 +582,24 @@
     return animateAsState(
         targetValue, converter, animationSpec, finishedListener = finishedListener
     )
-}
\ No newline at end of file
+}
+
+/**
+ * This [Animatable] function creates a Color value holder that automatically
+ * animates its value when the value is changed via [animateTo]. [Animatable] supports value
+ * change during an ongoing value change animation. When that happens, a new animation will
+ * transition [Animatable] from its current value (i.e. value at the point of interruption) to the
+ * new target. This ensures that the value change is *always* continuous using [animateTo]. If
+ * [spring] animation (i.e. default animation) is used with [animateTo], the velocity change will
+ * be guaranteed to be continuous as well.
+ *
+ * Unlike [AnimationState], [Animatable] ensures mutual exclusiveness on its animation. To
+ * do so, when a new animation is started via [animateTo] (or [animateDecay]), any ongoing
+ * animation job will be cancelled.
+ *
+ * @sample androidx.compose.animation.samples.AnimatableColor
+ *
+ * @param initialValue initial value of the animatable value holder
+ */
+fun Animatable(initialValue: Color): Animatable<Color, AnimationVector4D> =
+    Animatable(initialValue, (Color.VectorConverter)(initialValue.colorSpace))
\ No newline at end of file
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 20739ef..6c0c722 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -176,14 +176,8 @@
   public final class AndroidFlingConfigKt {
   }
 
-  public final class AndroidFlingDecaySpec implements androidx.compose.animation.core.FloatDecayAnimationSpec {
-    ctor public AndroidFlingDecaySpec(androidx.compose.ui.unit.Density density);
-    method public float getAbsVelocityThreshold();
-    method public long getDurationMillis(float start, float startVelocity);
-    method public float getTarget(float start, float startVelocity);
-    method public float getValue(long playTime, float start, float startVelocity);
-    method public float getVelocity(long playTime, float start, float startVelocity);
-    property public float absVelocityThreshold;
+  public final class AndroidFlingDecaySpecKt {
+    method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> androidFlingDecay(androidx.compose.ui.unit.Density density);
   }
 
   public final class AndroidFlingSplineKt {
@@ -206,6 +200,16 @@
     method public static void fling(androidx.compose.animation.core.AnimatedFloat, float startVelocity, androidx.compose.foundation.animation.FlingConfig config, optional kotlin.jvm.functions.Function3<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,? super java.lang.Float,kotlin.Unit>? onAnimationEnd);
   }
 
+  public final class FloatAndroidFlingDecaySpec implements androidx.compose.animation.core.FloatDecayAnimationSpec {
+    ctor public FloatAndroidFlingDecaySpec(androidx.compose.ui.unit.Density density);
+    method public float getAbsVelocityThreshold();
+    method public long getDurationMillis(float start, float startVelocity);
+    method public float getTarget(float start, float startVelocity);
+    method public float getValue(long playTime, float start, float startVelocity);
+    method public float getVelocity(long playTime, float start, float startVelocity);
+    property public float absVelocityThreshold;
+  }
+
   public final class SmoothScrollKt {
     method public static suspend Object? smoothScrollBy(androidx.compose.foundation.gestures.Scrollable, float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.coroutines.Continuation<? super java.lang.Float> p);
   }
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 20739ef..6c0c722 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -176,14 +176,8 @@
   public final class AndroidFlingConfigKt {
   }
 
-  public final class AndroidFlingDecaySpec implements androidx.compose.animation.core.FloatDecayAnimationSpec {
-    ctor public AndroidFlingDecaySpec(androidx.compose.ui.unit.Density density);
-    method public float getAbsVelocityThreshold();
-    method public long getDurationMillis(float start, float startVelocity);
-    method public float getTarget(float start, float startVelocity);
-    method public float getValue(long playTime, float start, float startVelocity);
-    method public float getVelocity(long playTime, float start, float startVelocity);
-    property public float absVelocityThreshold;
+  public final class AndroidFlingDecaySpecKt {
+    method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> androidFlingDecay(androidx.compose.ui.unit.Density density);
   }
 
   public final class AndroidFlingSplineKt {
@@ -206,6 +200,16 @@
     method public static void fling(androidx.compose.animation.core.AnimatedFloat, float startVelocity, androidx.compose.foundation.animation.FlingConfig config, optional kotlin.jvm.functions.Function3<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,? super java.lang.Float,kotlin.Unit>? onAnimationEnd);
   }
 
+  public final class FloatAndroidFlingDecaySpec implements androidx.compose.animation.core.FloatDecayAnimationSpec {
+    ctor public FloatAndroidFlingDecaySpec(androidx.compose.ui.unit.Density density);
+    method public float getAbsVelocityThreshold();
+    method public long getDurationMillis(float start, float startVelocity);
+    method public float getTarget(float start, float startVelocity);
+    method public float getValue(long playTime, float start, float startVelocity);
+    method public float getVelocity(long playTime, float start, float startVelocity);
+    property public float absVelocityThreshold;
+  }
+
   public final class SmoothScrollKt {
     method public static suspend Object? smoothScrollBy(androidx.compose.foundation.gestures.Scrollable, float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.coroutines.Continuation<? super java.lang.Float> p);
   }
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 20739ef..6c0c722 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -176,14 +176,8 @@
   public final class AndroidFlingConfigKt {
   }
 
-  public final class AndroidFlingDecaySpec implements androidx.compose.animation.core.FloatDecayAnimationSpec {
-    ctor public AndroidFlingDecaySpec(androidx.compose.ui.unit.Density density);
-    method public float getAbsVelocityThreshold();
-    method public long getDurationMillis(float start, float startVelocity);
-    method public float getTarget(float start, float startVelocity);
-    method public float getValue(long playTime, float start, float startVelocity);
-    method public float getVelocity(long playTime, float start, float startVelocity);
-    property public float absVelocityThreshold;
+  public final class AndroidFlingDecaySpecKt {
+    method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> androidFlingDecay(androidx.compose.ui.unit.Density density);
   }
 
   public final class AndroidFlingSplineKt {
@@ -206,6 +200,16 @@
     method public static void fling(androidx.compose.animation.core.AnimatedFloat, float startVelocity, androidx.compose.foundation.animation.FlingConfig config, optional kotlin.jvm.functions.Function3<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,? super java.lang.Float,kotlin.Unit>? onAnimationEnd);
   }
 
+  public final class FloatAndroidFlingDecaySpec implements androidx.compose.animation.core.FloatDecayAnimationSpec {
+    ctor public FloatAndroidFlingDecaySpec(androidx.compose.ui.unit.Density density);
+    method public float getAbsVelocityThreshold();
+    method public long getDurationMillis(float start, float startVelocity);
+    method public float getTarget(float start, float startVelocity);
+    method public float getValue(long playTime, float start, float startVelocity);
+    method public float getVelocity(long playTime, float start, float startVelocity);
+    property public float absVelocityThreshold;
+  }
+
   public final class SmoothScrollKt {
     method public static suspend Object? smoothScrollBy(androidx.compose.foundation.gestures.Scrollable, float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.coroutines.Continuation<? super java.lang.Float> p);
   }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt
index 73e8e32..97cc0ed 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt
@@ -18,7 +18,7 @@
 import android.os.Handler
 import android.os.Looper
 import androidx.annotation.RequiresApi
-import androidx.compose.animation.core.ExponentialDecay
+import androidx.compose.animation.core.FloatExponentialDecaySpec
 import androidx.compose.animation.core.ManualAnimationClock
 import androidx.compose.foundation.animation.FlingConfig
 import androidx.compose.foundation.layout.Box
@@ -120,7 +120,7 @@
     fun verticalScroller_SmallContent_Unscrollable() {
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
 
@@ -146,7 +146,7 @@
     fun verticalScroller_LargeContent_ScrollToEnd() {
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
         val height = 30
@@ -170,7 +170,7 @@
     fun verticalScroller_Reversed() {
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
         val height = 30
@@ -186,7 +186,7 @@
     fun verticalScroller_LargeContent_Reversed_ScrollToEnd() {
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
         val height = 20
@@ -251,7 +251,7 @@
 
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
 
@@ -276,7 +276,7 @@
 
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
 
@@ -298,7 +298,7 @@
     fun horizontalScroller_reversed() {
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
         val width = 30
@@ -314,7 +314,7 @@
     fun horizontalScroller_rtl_reversed() {
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
         val width = 30
@@ -333,7 +333,7 @@
 
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
 
@@ -357,7 +357,7 @@
 
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
 
@@ -411,7 +411,7 @@
             isVertical = true,
             scrollState = ScrollState(
                 initial = 0f,
-                flingConfig = FlingConfig(ExponentialDecay()),
+                flingConfig = FlingConfig(FloatExponentialDecaySpec()),
                 animationClock = ManualAnimationClock(0)
             ),
             isReversed = true
@@ -430,7 +430,7 @@
             isVertical = false,
             scrollState = ScrollState(
                 initial = 0f,
-                flingConfig = FlingConfig(ExponentialDecay()),
+                flingConfig = FlingConfig(FloatExponentialDecaySpec()),
                 animationClock = ManualAnimationClock(0)
             ),
             isReversed = true
@@ -502,7 +502,7 @@
         val clock = ManualAnimationClock(0)
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = clock
         )
 
@@ -549,7 +549,7 @@
         val clock = ManualAnimationClock(0)
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = clock
         )
         val itemCount = mutableStateOf(100)
@@ -590,7 +590,7 @@
         val clock = ManualAnimationClock(0)
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = clock
         )
 
@@ -627,7 +627,7 @@
         val clock = ManualAnimationClock(0)
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = clock
         )
 
@@ -709,7 +709,7 @@
         val clock = ManualAnimationClock(0)
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = clock
         )
 
@@ -752,7 +752,7 @@
     private fun composeVerticalScroller(
         scrollState: ScrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         ),
         isReversed: Boolean = false,
@@ -787,7 +787,7 @@
     private fun composeHorizontalScroller(
         scrollState: ScrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         ),
         isReversed: Boolean = false,
@@ -862,7 +862,7 @@
         isReversed: Boolean = false,
         scrollState: ScrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         ),
         isRtl: Boolean = false
@@ -926,7 +926,7 @@
     fun testInspectorValue() {
         val state = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
         rule.setContent {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 80c7c86..ffb3e30 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.foundation
 
-import androidx.compose.animation.core.ExponentialDecay
+import androidx.compose.animation.core.FloatExponentialDecaySpec
 import androidx.compose.animation.core.ManualAnimationClock
 import androidx.compose.animation.core.ManualFrameClock
 import androidx.compose.animation.core.advanceClockMillis
@@ -98,7 +98,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
         )
         setScrollableContent {
@@ -154,7 +154,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
         )
         setScrollableContent {
@@ -212,7 +212,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
         )
         setScrollableContent {
@@ -257,7 +257,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
         )
         setScrollableContent {
@@ -303,7 +303,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
         )
         setScrollableContent {
@@ -350,7 +350,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
         )
         setScrollableContent {
@@ -395,7 +395,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext)
         )
         setScrollableContent {
@@ -433,7 +433,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext)
         )
         setScrollableContent {
@@ -460,7 +460,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
         )
         setScrollableContent {
@@ -501,7 +501,7 @@
                 outerDrag += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = animationClock
         )
         val innerState = ScrollableController(
@@ -509,7 +509,7 @@
                 innerDrag += it / 2
                 it / 2
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = animationClock
         )
 
@@ -570,7 +570,7 @@
                 outerDrag += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = animationClock
         )
         val innerState = ScrollableController(
@@ -578,7 +578,7 @@
                 innerDrag += it / 2
                 it / 2
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = animationClock
         )
 
@@ -645,7 +645,7 @@
                     value += it
                     it
                 },
-                flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+                flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
                 animationClock = animationClock
             )
             val preConsumingParent = object : NestedScrollConnection {
@@ -713,7 +713,7 @@
                     expectedLeft = it - toConsume
                     toConsume
                 },
-                flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+                flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
                 animationClock = animationClock
             )
             val parent = object : NestedScrollConnection {
@@ -788,7 +788,7 @@
                     value += expectedConsumed
                     expectedConsumed
                 },
-                flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+                flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
                 animationClock = animationClock
             )
             val child = object : NestedScrollConnection {}
@@ -857,7 +857,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext),
             interactionState = interactionState
         )
@@ -904,7 +904,7 @@
                 total += it
                 it
             },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = monotonicFrameAnimationClockOf(coroutineContext),
             interactionState = interactionState
         )
@@ -953,7 +953,7 @@
     fun testInspectorValue() {
         val controller = ScrollableController(
             consumeScrollDelta = { it },
-            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            flingConfig = FlingConfig(decayAnimation = FloatExponentialDecaySpec()),
             animationClock = ManualAnimationClock(0)
         )
         rule.setContent {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
index 10e9e7a..1c78a3f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.foundation.lazy
 
-import androidx.compose.animation.core.ExponentialDecay
+import androidx.compose.animation.core.FloatExponentialDecaySpec
 import androidx.compose.animation.core.ManualAnimationClock
 import androidx.compose.animation.core.snap
 import androidx.compose.foundation.animation.FlingConfig
@@ -892,7 +892,7 @@
         val items by mutableStateOf((1..20).toList())
         val clock = ManualAnimationClock(0L)
         val state = LazyListState(
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = clock
         )
         rule.setContent {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldScrollTest.kt
index 009a24c..0125cd8 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldScrollTest.kt
@@ -17,7 +17,7 @@
 package androidx.compose.foundation.textfield
 
 import android.os.Build
-import androidx.compose.animation.core.ExponentialDecay
+import androidx.compose.animation.core.FloatExponentialDecaySpec
 import androidx.compose.animation.core.ManualAnimationClock
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.InteractionState
@@ -536,7 +536,7 @@
         val textFieldScrollPosition = TextFieldScrollerPosition()
         val scrollerPosition = ScrollState(
             0f,
-            FlingConfig(ExponentialDecay()),
+            FlingConfig(FloatExponentialDecaySpec()),
             ManualAnimationClock(0)
         )
 
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingConfig.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingConfig.kt
index bf17aeb..2ed0f66 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingConfig.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingConfig.kt
@@ -27,7 +27,7 @@
     // but the reference to the returned FlingConfig will not change across calls.
     val density = AmbientDensity.current
     return remember(density.density) {
-        val decayAnimation = AndroidFlingDecaySpec(density)
+        val decayAnimation = FloatAndroidFlingDecaySpec(density)
         FlingConfig(
             decayAnimation = decayAnimation,
             adjustTarget = adjustTarget
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingDecaySpec.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingDecaySpec.kt
index e3d45a3..8ef4e52 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingDecaySpec.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingDecaySpec.kt
@@ -17,14 +17,24 @@
 package androidx.compose.foundation.animation
 
 import android.view.ViewConfiguration
+import androidx.compose.animation.core.DecayAnimationSpec
 import androidx.compose.animation.core.FloatDecayAnimationSpec
+import androidx.compose.animation.core.generateDecayAnimationSpec
 import androidx.compose.ui.unit.Density
 import kotlin.math.sign
 
+@Deprecated(
+    "AndroidFlingDecaySpec has been renamed to FloatAndroidFlingDecaySpec",
+    replaceWith = ReplaceWith("FloatAndroidFlingDecaySpec")
+)
+typealias AndroidFlingDecaySpec = FloatAndroidFlingDecaySpec
+
 /**
  * A native Android fling curve decay.
+ *
+ * @param density density of the display
  */
-class AndroidFlingDecaySpec(density: Density) : FloatDecayAnimationSpec {
+class FloatAndroidFlingDecaySpec(density: Density) : FloatDecayAnimationSpec {
 
     private val flingCalculator = AndroidFlingCalculator(
         ViewConfiguration.getScrollFriction(),
@@ -47,4 +57,13 @@
 
     override fun getVelocity(playTime: Long, start: Float, startVelocity: Float): Float =
         flingCalculator.flingInfo(startVelocity).velocity(playTime)
-}
\ No newline at end of file
+}
+
+/**
+ * Creates a [DecayAnimationSpec] using the native Android fling decay. This can then be used to
+ * animate any type [T].
+ *
+ * @param density density of the display
+ */
+fun <T> androidFlingDecay(density: Density): DecayAnimationSpec<T> =
+    FloatAndroidFlingDecaySpec(density).generateDecayAnimationSpec()
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/animation/FlingConfig.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/animation/FlingConfig.kt
index 5df4eec..d67fb7a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/animation/FlingConfig.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/animation/FlingConfig.kt
@@ -18,8 +18,8 @@
 
 import androidx.compose.animation.core.AnimatedFloat
 import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.ExponentialDecay
 import androidx.compose.animation.core.FloatDecayAnimationSpec
+import androidx.compose.animation.core.FloatExponentialDecaySpec
 import androidx.compose.animation.core.OnAnimationEnd
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.TargetAnimation
@@ -79,7 +79,7 @@
 fun FlingConfig(
     anchors: List<Float>,
     animationSpec: AnimationSpec<Float> = SpringSpec(),
-    decayAnimation: FloatDecayAnimationSpec = ExponentialDecay()
+    decayAnimation: FloatDecayAnimationSpec = FloatExponentialDecaySpec()
 ): FlingConfig {
     val adjustTarget: (Float) -> TargetAnimation? = { target ->
         val point = anchors.minByOrNull { abs(it - target) }
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
index 4ed75e3..214f43e 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.compose.animation.core.AnimationClockObservable
 import androidx.compose.animation.core.AnimationClockObserver
-import androidx.compose.animation.core.ExponentialDecay
+import androidx.compose.animation.core.FloatExponentialDecaySpec
 import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.ScrollableColumn
 import androidx.compose.foundation.animation.FlingConfig
@@ -167,7 +167,7 @@
         val touchSlop = with(rule.density) { TouchSlop.toPx() }
         val scrollState = ScrollState(
             initial = 0f,
-            flingConfig = FlingConfig(ExponentialDecay()),
+            flingConfig = FlingConfig(FloatExponentialDecaySpec()),
             animationClock = object : AnimationClockObservable {
                 // Use a "broken" clock, we just want response to input, not to time
                 override fun subscribe(observer: AnimationClockObserver) {}