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.>
@@ -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,
+ delta: Float ->
+ // Snap to new drag position
+ animScroll.snapTo(animScroll.value + delta)
+ },
- override fun onStop(velocity: Offset) {
+ 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(
>
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) {}