Add FlingBehavior support for AnchoredDraggable

Test: Existing Tests
Relnote: AnchoredDraggable's configuration has been moved from AnchoredDraggableState to Modifier.anchoredDraggable. This means that animation specs and thresholds now need to be passed to Modifier.anchoredDraggable instead of AnchoredDraggableState. Additionally, AnchoredDraggable now supports passing in a FlingBehavior to customize the fling. AnchoredDraggableState#settle does not accept a velocity anymore, it now settles the state at the closest anchor. Please use FlingBehavior#performFling instead.
Change-Id: I91f584c0f26e613cb04a835d09526e181a980f8c
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index c179e32..827a59a 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -392,50 +392,67 @@
     method public void dragTo(float newOffset, optional float lastKnownVelocity);
   }
 
+  @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final class AnchoredDraggableDefaults {
+    method @androidx.compose.runtime.Composable public <T> androidx.compose.foundation.gestures.TargetedFlingBehavior flingBehavior(androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec);
+    method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> getDecayAnimationSpec();
+    method public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getPositionalThreshold();
+    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getSnapAnimationSpec();
+    property public final androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> DecayAnimationSpec;
+    property public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> PositionalThreshold;
+    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> SnapAnimationSpec;
+    field public static final androidx.compose.foundation.gestures.AnchoredDraggableDefaults INSTANCE;
+  }
+
   public final class AnchoredDraggableKt {
+    method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.foundation.gestures.AnchoredDraggableState<T> AnchoredDraggableState(T initialValue, androidx.compose.foundation.gestures.DraggableAnchors<T> anchors, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
+    method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.foundation.gestures.AnchoredDraggableState<T> AnchoredDraggableState(T initialValue, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.foundation.gestures.DraggableAnchors<T> DraggableAnchors(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.gestures.DraggableAnchorsConfig<T>,kotlin.Unit> builder);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateToWithDecay(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateToWithDecay(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, float velocity, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, kotlin.coroutines.Continuation<? super java.lang.Float>);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? snapTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public final class AnchoredDraggableState<T> {
-    ctor @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public AnchoredDraggableState(T initialValue, androidx.compose.foundation.gestures.DraggableAnchors<T> anchors, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
-    ctor public AnchoredDraggableState(T initialValue, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
+    ctor @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public AnchoredDraggableState(T initialValue, androidx.compose.foundation.gestures.DraggableAnchors<T> anchors, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
+    ctor public AnchoredDraggableState(T initialValue, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
     method public suspend Object? anchoredDrag(optional androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.AnchoredDragScope,? super androidx.compose.foundation.gestures.DraggableAnchors<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? anchoredDrag(T targetValue, optional androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function4<? super androidx.compose.foundation.gestures.AnchoredDragScope,? super androidx.compose.foundation.gestures.DraggableAnchors<T>,? super T,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public float dispatchRawDelta(float delta);
     method public androidx.compose.foundation.gestures.DraggableAnchors<T> getAnchors();
     method public T getCurrentValue();
-    method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> getDecayAnimationSpec();
+    method @Deprecated public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> getDecayAnimationSpec();
     method public float getLastVelocity();
     method public float getOffset();
     method @Deprecated @FloatRange(from=0.0, to=1.0) public float getProgress();
     method public T getSettledValue();
-    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getSnapAnimationSpec();
+    method @Deprecated public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getSnapAnimationSpec();
     method public T getTargetValue();
     method public boolean isAnimationRunning();
     method @FloatRange(from=0.0, to=1.0) public float progress(T from, T to);
     method public float requireOffset();
-    method public suspend Object? settle(float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
+    method public suspend Object? settle(androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public suspend Object? settle(float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
     method public void updateAnchors(androidx.compose.foundation.gestures.DraggableAnchors<T> newAnchors, optional T newTarget);
     property public final androidx.compose.foundation.gestures.DraggableAnchors<T> anchors;
     property public final T currentValue;
-    property public final androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec;
+    property @Deprecated public final androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec;
     property public final boolean isAnimationRunning;
     property public final float lastVelocity;
     property public final float offset;
     property @Deprecated @FloatRange(from=0.0, to=1.0) public final float progress;
     property public final T settledValue;
-    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec;
+    property @Deprecated public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec;
     property public final T targetValue;
     field public static final androidx.compose.foundation.gestures.AnchoredDraggableState.Companion Companion;
+    field @Deprecated public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec;
+    field @Deprecated public androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec;
   }
 
   public static final class AnchoredDraggableState.Companion {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public <T> androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.gestures.AnchoredDraggableState<T>,T> Saver(androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
+    method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public <T> androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.gestures.AnchoredDraggableState<T>,T> Saver(androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public <T> androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.gestures.AnchoredDraggableState<T>,T> Saver(optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public interface BringIntoViewSpec {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index ab30fb8..8c55869 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -394,50 +394,67 @@
     method public void dragTo(float newOffset, optional float lastKnownVelocity);
   }
 
+  @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final class AnchoredDraggableDefaults {
+    method @androidx.compose.runtime.Composable public <T> androidx.compose.foundation.gestures.TargetedFlingBehavior flingBehavior(androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec);
+    method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> getDecayAnimationSpec();
+    method public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getPositionalThreshold();
+    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getSnapAnimationSpec();
+    property public final androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> DecayAnimationSpec;
+    property public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> PositionalThreshold;
+    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> SnapAnimationSpec;
+    field public static final androidx.compose.foundation.gestures.AnchoredDraggableDefaults INSTANCE;
+  }
+
   public final class AnchoredDraggableKt {
+    method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.foundation.gestures.AnchoredDraggableState<T> AnchoredDraggableState(T initialValue, androidx.compose.foundation.gestures.DraggableAnchors<T> anchors, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
+    method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.foundation.gestures.AnchoredDraggableState<T> AnchoredDraggableState(T initialValue, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.foundation.gestures.DraggableAnchors<T> DraggableAnchors(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.gestures.DraggableAnchorsConfig<T>,kotlin.Unit> builder);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateToWithDecay(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateToWithDecay(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, float velocity, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, kotlin.coroutines.Continuation<? super java.lang.Float>);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? snapTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public final class AnchoredDraggableState<T> {
-    ctor @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public AnchoredDraggableState(T initialValue, androidx.compose.foundation.gestures.DraggableAnchors<T> anchors, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
-    ctor public AnchoredDraggableState(T initialValue, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
+    ctor @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public AnchoredDraggableState(T initialValue, androidx.compose.foundation.gestures.DraggableAnchors<T> anchors, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
+    ctor public AnchoredDraggableState(T initialValue, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
     method public suspend Object? anchoredDrag(optional androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.AnchoredDragScope,? super androidx.compose.foundation.gestures.DraggableAnchors<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? anchoredDrag(T targetValue, optional androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function4<? super androidx.compose.foundation.gestures.AnchoredDragScope,? super androidx.compose.foundation.gestures.DraggableAnchors<T>,? super T,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public float dispatchRawDelta(float delta);
     method public androidx.compose.foundation.gestures.DraggableAnchors<T> getAnchors();
     method public T getCurrentValue();
-    method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> getDecayAnimationSpec();
+    method @Deprecated public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> getDecayAnimationSpec();
     method public float getLastVelocity();
     method public float getOffset();
     method @Deprecated @FloatRange(from=0.0, to=1.0) public float getProgress();
     method public T getSettledValue();
-    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getSnapAnimationSpec();
+    method @Deprecated public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getSnapAnimationSpec();
     method public T getTargetValue();
     method public boolean isAnimationRunning();
     method @FloatRange(from=0.0, to=1.0) public float progress(T from, T to);
     method public float requireOffset();
-    method public suspend Object? settle(float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
+    method public suspend Object? settle(androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public suspend Object? settle(float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
     method public void updateAnchors(androidx.compose.foundation.gestures.DraggableAnchors<T> newAnchors, optional T newTarget);
     property public final androidx.compose.foundation.gestures.DraggableAnchors<T> anchors;
     property public final T currentValue;
-    property public final androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec;
+    property @Deprecated public final androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec;
     property public final boolean isAnimationRunning;
     property public final float lastVelocity;
     property public final float offset;
     property @Deprecated @FloatRange(from=0.0, to=1.0) public final float progress;
     property public final T settledValue;
-    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec;
+    property @Deprecated public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec;
     property public final T targetValue;
     field public static final androidx.compose.foundation.gestures.AnchoredDraggableState.Companion Companion;
+    field @Deprecated public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec;
+    field @Deprecated public androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec;
   }
 
   public static final class AnchoredDraggableState.Companion {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public <T> androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.gestures.AnchoredDraggableState<T>,T> Saver(androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
+    method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public <T> androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.gestures.AnchoredDraggableState<T>,T> Saver(androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public <T> androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.gestures.AnchoredDraggableState<T>,T> Saver(optional kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmValueChange);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public interface BringIntoViewSpec {
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
index d28943f..735cc00 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
@@ -21,14 +21,17 @@
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.animate
 import androidx.compose.animation.core.tween
-import androidx.compose.animation.rememberSplineBasedDecay
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.gestures.AnchoredDraggableDefaults
 import androidx.compose.foundation.gestures.AnchoredDraggableState
 import androidx.compose.foundation.gestures.DraggableAnchors
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.anchoredDraggable
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.rememberDraggableState
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -46,7 +49,11 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.Offset
@@ -73,28 +80,9 @@
 @Preview
 fun AnchoredDraggableAnchorsFromCompositionSample() {
     val density = LocalDensity.current
-    val snapAnimationSpec = tween<Float>()
-    val decayAnimationSpec = rememberSplineBasedDecay<Float>()
-    val positionalThreshold = { distance: Float -> distance * 0.5f }
-    val velocityThreshold = { with(density) { 125.dp.toPx() } }
     val state =
-        rememberSaveable(
-            density,
-            saver =
-                AnchoredDraggableState.Saver(
-                    snapAnimationSpec,
-                    decayAnimationSpec,
-                    positionalThreshold,
-                    velocityThreshold
-                )
-        ) {
-            AnchoredDraggableState(
-                initialValue = Center,
-                positionalThreshold,
-                velocityThreshold,
-                snapAnimationSpec,
-                decayAnimationSpec
-            )
+        rememberSaveable(saver = AnchoredDraggableState.Saver()) {
+            AnchoredDraggableState(initialValue = Center)
         }
     val draggableWidth = 70.dp
     val containerWidthPx = with(density) { draggableWidth.toPx() }
@@ -113,7 +101,15 @@
         Box(
             Modifier.size(100.dp)
                 .offset { IntOffset(x = state.requireOffset().roundToInt(), y = 0) }
-                .anchoredDraggable(state, Orientation.Horizontal)
+                .anchoredDraggable(
+                    state,
+                    Orientation.Horizontal,
+                    flingBehavior =
+                        AnchoredDraggableDefaults.flingBehavior(
+                            state,
+                            positionalThreshold = { distance -> distance * 0.25f }
+                        )
+                )
                 .background(Color.Red)
         )
     }
@@ -122,29 +118,9 @@
 @Preview
 @Composable
 fun AnchoredDraggableLayoutDependentAnchorsSample() {
-    val density = LocalDensity.current
-    val snapAnimationSpec = tween<Float>()
-    val decayAnimationSpec = rememberSplineBasedDecay<Float>()
-    val positionalThreshold = { distance: Float -> distance * 0.5f }
-    val velocityThreshold = { with(density) { 125.dp.toPx() } }
     val state =
-        rememberSaveable(
-            density,
-            saver =
-                AnchoredDraggableState.Saver(
-                    snapAnimationSpec,
-                    decayAnimationSpec,
-                    positionalThreshold,
-                    velocityThreshold
-                )
-        ) {
-            AnchoredDraggableState(
-                initialValue = Center,
-                positionalThreshold,
-                velocityThreshold,
-                snapAnimationSpec,
-                decayAnimationSpec
-            )
+        rememberSaveable(saver = AnchoredDraggableState.Saver()) {
+            AnchoredDraggableState(initialValue = Center)
         }
     val draggableSize = 60.dp
     val draggableSizePx = with(LocalDensity.current) { draggableSize.toPx() }
@@ -169,7 +145,7 @@
         Box(
             Modifier.size(draggableSize)
                 .offset { IntOffset(x = state.requireOffset().roundToInt(), y = 0) }
-                .anchoredDraggable(state, Orientation.Horizontal)
+                .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
                 .background(Color.Red)
         )
     }
@@ -209,22 +185,13 @@
     // Attempting to press the box while it is settling to one anchor won't stop the box from
     // animating to that anchor. If you want to catch it while it is animating, you need to press
     // the box and drag it past the touchSlop. This is because startDragImmediately is set to false.
-    val density = LocalDensity.current
-    val decayAnimationSpec = rememberSplineBasedDecay<Float>()
-    // Setting the duration of the snapAnimationSpec to 3000ms gives more time to attempt to press
-    // or drag the settling box.
-    val snapAnimationSpec = tween<Float>(durationMillis = 3000)
     val state =
-        AnchoredDraggableState(
-            initialValue = Start,
-            positionalThreshold = { distance: Float -> distance * 0.5f },
-            velocityThreshold = { with(density) { 125.dp.toPx() } },
-            snapAnimationSpec = snapAnimationSpec,
-            decayAnimationSpec = decayAnimationSpec
-        )
-
+        rememberSaveable(saver = AnchoredDraggableState.Saver()) {
+            AnchoredDraggableState(initialValue = Start)
+        }
+    val density = LocalDensity.current
     val draggableSize = 100.dp
-    val draggableSizePx = with(LocalDensity.current) { draggableSize.toPx() }
+    val draggableSizePx = with(density) { draggableSize.toPx() }
     Box(
         Modifier.fillMaxWidth().onSizeChanged { layoutSize ->
             val dragEndPoint = layoutSize.width - draggableSizePx
@@ -239,7 +206,20 @@
         Box(
             Modifier.size(draggableSize)
                 .offset { IntOffset(x = state.requireOffset().roundToInt(), y = 0) }
-                .anchoredDraggable(state, Orientation.Horizontal, startDragImmediately = false)
+                .anchoredDraggable(
+                    state = state,
+                    orientation = Orientation.Horizontal,
+                    startDragImmediately = false,
+                    flingBehavior =
+                        AnchoredDraggableDefaults.flingBehavior(
+                            state,
+                            positionalThreshold = { with(density) { 56.dp.toPx() } },
+                            // Setting the duration of the snapAnimationSpec to 3000ms gives more
+                            // time
+                            // to attempt to press or drag the settling box.
+                            animationSpec = tween(durationMillis = 3000)
+                        )
+                )
                 .background(Color.Red)
         )
     }
@@ -249,34 +229,13 @@
 @Preview
 @Composable
 fun AnchoredDraggableWithOverscrollSample() {
-    val density = LocalDensity.current
-    val draggableSize = 80.dp
-    val draggableSizePx = with(density) { draggableSize.toPx() }
-
-    val animationSpec = tween<Float>()
-    val decayAnimationSpec = rememberSplineBasedDecay<Float>()
-    val positionalThreshold = { distance: Float -> distance * 0.5f }
-    val velocityThreshold = { with(density) { 125.dp.toPx() } }
-    val overscrollEffect = ScrollableDefaults.overscrollEffect()
     val state =
-        rememberSaveable(
-            density,
-            saver =
-                AnchoredDraggableState.Saver(
-                    animationSpec,
-                    decayAnimationSpec,
-                    positionalThreshold,
-                    velocityThreshold,
-                )
-        ) {
-            AnchoredDraggableState(
-                initialValue = Center,
-                positionalThreshold,
-                velocityThreshold,
-                animationSpec,
-                decayAnimationSpec,
-            )
+        rememberSaveable(saver = AnchoredDraggableState.Saver()) {
+            AnchoredDraggableState(initialValue = Center)
         }
+    val draggableSize = 80.dp
+    val draggableSizePx = with(LocalDensity.current) { draggableSize.toPx() }
+    val overscrollEffect = ScrollableDefaults.overscrollEffect()
 
     Box(
         Modifier.fillMaxWidth().onSizeChanged { layoutSize ->
@@ -307,29 +266,9 @@
 
 @Composable
 fun AnchoredDraggableProgressSample() {
-    val density = LocalDensity.current
-    val snapAnimationSpec = tween<Float>()
-    val decayAnimationSpec = rememberSplineBasedDecay<Float>()
-    val positionalThreshold = { distance: Float -> distance * 0.5f }
-    val velocityThreshold = { with(density) { 125.dp.toPx() } }
     val state =
-        rememberSaveable(
-            density,
-            saver =
-                AnchoredDraggableState.Saver(
-                    snapAnimationSpec,
-                    decayAnimationSpec,
-                    positionalThreshold,
-                    velocityThreshold
-                )
-        ) {
-            AnchoredDraggableState(
-                initialValue = Center,
-                positionalThreshold,
-                velocityThreshold,
-                snapAnimationSpec,
-                decayAnimationSpec
-            )
+        rememberSaveable(saver = AnchoredDraggableState.Saver()) {
+            AnchoredDraggableState(initialValue = Center)
         }
     val draggableSize = 60.dp
     val draggableSizePx = with(LocalDensity.current) { draggableSize.toPx() }
@@ -370,6 +309,45 @@
     }
 }
 
+@Preview
+@Composable
+fun DraggableAnchorsSample() {
+    var anchors by remember { mutableStateOf(DraggableAnchors<AnchoredDraggableSampleValue> {}) }
+    var offset by rememberSaveable { mutableFloatStateOf(0f) }
+    val thumbSize = 16.dp
+    val thumbSizePx = with(LocalDensity.current) { thumbSize.toPx() }
+    Box(
+        Modifier.width(100.dp)
+            // Our anchors depend on this box's size, so we obtain the size from onSizeChanged and
+            // use updateAnchors to let the state know about the new anchors
+            .onSizeChanged { layoutSize ->
+                anchors = DraggableAnchors {
+                    Start at 0f
+                    End at layoutSize.width - thumbSizePx
+                }
+            }
+            .border(2.dp, Color.Black)
+    ) {
+        Box(
+            Modifier.size(thumbSize)
+                .offset { IntOffset(x = offset.roundToInt(), y = 0) }
+                .draggable(
+                    state =
+                        rememberDraggableState { delta ->
+                            offset =
+                                (offset + delta).coerceIn(anchors.minAnchor(), anchors.maxAnchor())
+                        },
+                    orientation = Orientation.Horizontal,
+                    onDragStopped = { velocity ->
+                        val closestAnchor = anchors.positionOf(anchors.closestAnchor(offset)!!)
+                        animate(offset, closestAnchor, velocity) { value, _ -> offset = value }
+                    }
+                )
+                .background(Color.Red)
+        )
+    }
+}
+
 /**
  * A [Modifier] that visualizes the anchors attached to an [AnchoredDraggableState] as lines along
  * the cross axis of the layout (start to end for [Orientation.Vertical], top to end for
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDragScopeAsScrollScope.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDragScopeAsScrollScope.kt
new file mode 100644
index 0000000..549f975
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDragScopeAsScrollScope.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 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.foundation.anchoredDraggable
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.AnchoredDragScope
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.ScrollScope
+
+@OptIn(ExperimentalFoundationApi::class)
+internal fun AnchoredDragScope.asScrollScope(state: AnchoredDraggableState<*>) =
+    object : ScrollScope {
+        override fun scrollBy(pixels: Float): Float {
+            val newOffset = state.newOffsetForDelta(pixels)
+            val consumed = newOffset - state.offset
+            dragTo(newOffset)
+            return consumed
+        }
+    }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableBackwardsCompatibleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableBackwardsCompatibleTest.kt
new file mode 100644
index 0000000..9785465
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableBackwardsCompatibleTest.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2024 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.foundation.anchoredDraggable
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.OverscrollEffect
+import androidx.compose.foundation.gestures.AnchoredDraggableDefaults
+import androidx.compose.foundation.gestures.AnchoredDraggableMinFlingVelocity
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.DraggableAnchors
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.gestures.TargetedFlingBehavior
+import androidx.compose.foundation.gestures.anchoredDraggable
+import androidx.compose.foundation.gestures.anchoredDraggableFlingBehavior
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Density
+import org.junit.Rule
+
+/**
+ * Test helper that allows to test either old or new anchored draggable overloads before/after
+ * aosp/3012013.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+abstract class AnchoredDraggableBackwardsCompatibleTest(private val testNewBehavior: Boolean) {
+
+    @get:Rule val rule = createComposeRule()
+
+    fun <T> createStateAndModifier(
+        initialValue: T,
+        orientation: Orientation,
+        anchors: DraggableAnchors<T>? = null,
+        confirmValueChange: (T) -> Boolean = { true },
+        enabled: Boolean = true,
+        reverseDirection: Boolean? = null,
+        interactionSource: MutableInteractionSource? = null,
+        overscrollEffect: OverscrollEffect? = null,
+        startDragImmediately: Boolean? = null,
+        positionalThreshold: (Float) -> Float = AnchoredDraggableDefaults.PositionalThreshold,
+        velocityThreshold: () -> Float = {
+            with(rule.density) { AnchoredDraggableMinFlingVelocity.toPx() }
+        },
+        snapAnimationSpec: AnimationSpec<Float> = AnchoredDraggableDefaults.SnapAnimationSpec,
+        decayAnimationSpec: DecayAnimationSpec<Float> =
+            AnchoredDraggableDefaults.DecayAnimationSpec,
+    ): Pair<AnchoredDraggableState<T>, Modifier> {
+        val state =
+            createAnchoredDraggableState(
+                initialValue = initialValue,
+                anchors = anchors,
+                confirmValueChange = confirmValueChange,
+                positionalThreshold = positionalThreshold,
+                velocityThreshold = velocityThreshold,
+                snapAnimationSpec = snapAnimationSpec,
+                decayAnimationSpec = decayAnimationSpec
+            )
+        // This won't work for snapshot observation but should be ok for tests
+        val resolvedStartDragImmediately = startDragImmediately ?: state.isAnimationRunning
+        val modifier =
+            if (testNewBehavior) {
+                val flingBehavior =
+                    anchoredDraggableFlingBehavior(
+                        state,
+                        density = rule.density,
+                        positionalThreshold = positionalThreshold,
+                        snapAnimationSpec = snapAnimationSpec
+                    )
+                createAnchoredDraggableModifier(
+                    state = state,
+                    reverseDirection = reverseDirection,
+                    orientation = orientation,
+                    enabled = enabled,
+                    interactionSource = interactionSource,
+                    overscrollEffect = overscrollEffect,
+                    startDragImmediately = resolvedStartDragImmediately,
+                    flingBehavior = flingBehavior
+                )
+            } else {
+                createAnchoredDraggableModifier(
+                    state = state,
+                    reverseDirection = reverseDirection,
+                    orientation = orientation,
+                    enabled = enabled,
+                    interactionSource = interactionSource,
+                    overscrollEffect = overscrollEffect,
+                    startDragImmediately = resolvedStartDragImmediately,
+                    flingBehavior = null
+                )
+            }
+        return state to modifier
+    }
+
+    fun <T> createAnchoredDraggableState(
+        initialValue: T,
+        anchors: DraggableAnchors<T>? = null,
+        confirmValueChange: (T) -> Boolean = { true },
+        positionalThreshold: (Float) -> Float = AnchoredDraggableDefaults.PositionalThreshold,
+        velocityThreshold: () -> Float = {
+            with(rule.density) { AnchoredDraggableMinFlingVelocity.toPx() }
+        },
+        snapAnimationSpec: AnimationSpec<Float> = AnchoredDraggableDefaults.SnapAnimationSpec,
+        decayAnimationSpec: DecayAnimationSpec<Float> =
+            AnchoredDraggableDefaults.DecayAnimationSpec,
+    ) =
+        if (testNewBehavior) {
+            val resolvedVelocityThreshold = velocityThreshold()
+            check(
+                resolvedVelocityThreshold ==
+                    with(rule.density) { AnchoredDraggableMinFlingVelocity.toPx() }
+            ) {
+                "The velocity threshold resolved to $resolvedVelocityThreshold, but velocity " +
+                    "thresholds are not configurable with testNewBehavior=true."
+            }
+            when (anchors) {
+                null ->
+                    AnchoredDraggableState(
+                        initialValue,
+                        confirmValueChange,
+                    )
+                else -> AnchoredDraggableState(initialValue, anchors, confirmValueChange)
+            }
+        } else {
+            @Suppress("DEPRECATION")
+            when (anchors) {
+                null ->
+                    AnchoredDraggableState(
+                        initialValue = initialValue,
+                        confirmValueChange = confirmValueChange,
+                        positionalThreshold = positionalThreshold,
+                        velocityThreshold = velocityThreshold,
+                        snapAnimationSpec = snapAnimationSpec,
+                        decayAnimationSpec = decayAnimationSpec
+                    )
+                else ->
+                    AnchoredDraggableState(
+                        initialValue = initialValue,
+                        anchors = anchors,
+                        confirmValueChange = confirmValueChange,
+                        positionalThreshold = positionalThreshold,
+                        velocityThreshold = velocityThreshold,
+                        snapAnimationSpec = snapAnimationSpec,
+                        decayAnimationSpec = decayAnimationSpec
+                    )
+            }
+        }
+
+    internal fun <T> createAnchoredDraggableModifier(
+        state: AnchoredDraggableState<T>,
+        orientation: Orientation,
+        reverseDirection: Boolean? = null,
+        enabled: Boolean = true,
+        interactionSource: MutableInteractionSource? = null,
+        overscrollEffect: OverscrollEffect? = null,
+        startDragImmediately: Boolean = state.isAnimationRunning,
+        flingBehavior: FlingBehavior? = null
+    ) =
+        when (reverseDirection) {
+            null ->
+                Modifier.anchoredDraggable(
+                    state = state,
+                    orientation = orientation,
+                    enabled = enabled,
+                    interactionSource = interactionSource,
+                    overscrollEffect = overscrollEffect,
+                    startDragImmediately = startDragImmediately,
+                    flingBehavior = flingBehavior
+                )
+            else ->
+                Modifier.anchoredDraggable(
+                    state = state,
+                    reverseDirection = reverseDirection,
+                    orientation = orientation,
+                    enabled = enabled,
+                    interactionSource = interactionSource,
+                    overscrollEffect = overscrollEffect,
+                    startDragImmediately = startDragImmediately,
+                    flingBehavior = flingBehavior
+                )
+        }
+
+    /**
+     * Create a [FlingBehavior] with either the old or new behavior, depending on [testNewBehavior].
+     *
+     * @return A [anchoredDraggableFlingBehavior] instance or a [TargetedFlingBehavior] instance
+     *   that calls the deprecated [AnchoredDraggableState.settle] overload.
+     */
+    internal fun <T> createAnchoredDraggableFlingBehavior(
+        state: AnchoredDraggableState<T>,
+        density: Density,
+        positionalThreshold: (totalDistance: Float) -> Float =
+            AnchoredDraggableDefaults.PositionalThreshold,
+        snapAnimationSpec: AnimationSpec<Float> = AnchoredDraggableDefaults.SnapAnimationSpec
+    ) =
+        if (testNewBehavior) {
+            anchoredDraggableFlingBehavior(
+                state = state,
+                density = density,
+                positionalThreshold = positionalThreshold,
+                snapAnimationSpec = snapAnimationSpec
+            )
+        } else {
+            object : TargetedFlingBehavior {
+                override suspend fun ScrollScope.performFling(
+                    initialVelocity: Float,
+                    onRemainingDistanceUpdated: (Float) -> Unit
+                ): Float {
+                    @Suppress("DEPRECATION") return state.settle(initialVelocity)
+                }
+            }
+        }
+
+    internal suspend inline fun performFling(
+        flingBehavior: FlingBehavior,
+        state: AnchoredDraggableState<*>,
+        velocity: Float
+    ) {
+        if (testNewBehavior) {
+            with(flingBehavior) {
+                state.anchoredDrag { this.asScrollScope(state).performFling(velocity) }
+            }
+        } else {
+            @Suppress("DEPRECATION") state.settle(velocity)
+        }
+    }
+
+    internal val AnchoredDraggableMinFlingVelocityPx: Float
+        get() = with(rule.density) { AnchoredDraggableMinFlingVelocity.toPx() }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableDecayAnimationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableDecayAnimationTest.kt
index 6b4220c..5222f99 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableDecayAnimationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableDecayAnimationTest.kt
@@ -20,7 +20,6 @@
 import androidx.compose.animation.core.generateDecayAnimationSpec
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.gestures.AnchoredDraggableState
 import androidx.compose.foundation.gestures.DraggableAnchors
 import androidx.compose.foundation.gestures.animateToWithDecay
 import androidx.compose.runtime.getValue
@@ -29,7 +28,6 @@
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.testutils.createParameterizedComposeTestRule
 import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.abs
@@ -40,13 +38,15 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
 @OptIn(ExperimentalFoundationApi::class)
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
 @LargeTest
-class AnchoredDraggableDecayAnimationTest {
+class AnchoredDraggableDecayAnimationTest(testNewBehavior: Boolean) :
+    AnchoredDraggableBackwardsCompatibleTest(testNewBehavior) {
 
-    @get:Rule val rule = createParameterizedComposeTestRule<Params>()
+    @get:Rule val parameterizedRule = createParameterizedComposeTestRule<Params>()
 
     private val AllAnchorConfigurations =
         listOf(
@@ -67,23 +67,24 @@
     @Test
     fun anchoredDraggable_animateToWithDecay() {
         lateinit var scope: CoroutineScope
-        rule.setContent { scope = rememberCoroutineScope() }
-        rule.forEachParameter(AllAnchorConfigurations) { parameters ->
+        parameterizedRule.setContent { scope = rememberCoroutineScope() }
+        parameterizedRule.forEachParameter(AllAnchorConfigurations) { parameters ->
             val offsets = mutableListOf<Float>()
             val decaySpec =
                 createFakeDecayAnimationSpec(
                     from = parameters.from,
                     to = parameters.to + parameters.directionalOvershoot
                 )
+            val generatedSpec = decaySpec.generateDecayAnimationSpec<Float>()
             val state by
                 mutableStateOf(
-                    AnchoredDraggableState(
+                    createAnchoredDraggableState(
                         initialValue = parameters.anchors.closestAnchor(parameters.from)!!,
                         positionalThreshold = defaultPositionalThreshold,
                         velocityThreshold = defaultVelocityThreshold,
-                        snapAnimationSpec = defaultAnimationSpec,
-                        decayAnimationSpec = decaySpec.generateDecayAnimationSpec(),
-                        anchors = parameters.anchors
+                        anchors = parameters.anchors,
+                        decayAnimationSpec = generatedSpec,
+                        snapAnimationSpec = defaultAnimationSpec
                     )
                 )
 
@@ -97,10 +98,14 @@
             scope.launch {
                 state.animateToWithDecay(
                     targetValue = state.anchors.closestAnchor(parameters.to)!!,
-                    velocity = defaultVelocityThreshold() * 10f * parameters.direction
+                    velocity = defaultVelocityThreshold() * 10f * parameters.direction,
+                    snapAnimationSpec = defaultAnimationSpec,
+                    decayAnimationSpec = generatedSpec
                 )
             }
-            rule.waitForIdle()
+            parameterizedRule.mainClock.advanceTimeUntil {
+                offsets.size == decaySpec.values.toTypedArray().size
+            }
 
             assertThat(offsets).containsExactlyElementsIn(decaySpec.values.toTypedArray())
             assertThat(state.offset).isEqualTo(parameters.to)
@@ -113,7 +118,9 @@
     private val defaultPositionalThreshold: (totalDistance: Float) -> Float = {
         with(rule.density) { 56.dp.toPx() }
     }
-    private val defaultVelocityThreshold: () -> Float = { with(rule.density) { 125.dp.toPx() } }
+    private val defaultVelocityThreshold: () -> Float = {
+        with(parameterizedRule.density) { 125.dp.toPx() }
+    }
     private val defaultAnimationSpec = tween<Float>()
 
     /**
@@ -138,6 +145,12 @@
         Start,
         End
     }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "testNewBehavior={0}")
+        fun params() = listOf(false, true)
+    }
 }
 
 private fun createFakeDecayAnimationSpec(
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
index f15cb51..3db54694 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
@@ -16,26 +16,24 @@
 
 package androidx.compose.foundation.anchoredDraggable
 
-import androidx.compose.animation.SplineBasedFloatDecayAnimationSpec
-import androidx.compose.animation.core.DecayAnimationSpec
-import androidx.compose.animation.core.generateDecayAnimationSpec
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.AtomicLong
 import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.anchoredDraggable.AnchoredDraggableTestValue.A
 import androidx.compose.foundation.anchoredDraggable.AnchoredDraggableTestValue.B
 import androidx.compose.foundation.anchoredDraggable.AnchoredDraggableTestValue.C
 import androidx.compose.foundation.background
-import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.AnchoredDraggableDefaults
 import androidx.compose.foundation.gestures.DraggableAnchors
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.anchoredDraggable
 import androidx.compose.foundation.gestures.animateTo
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.MonotonicFrameClock
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
@@ -48,7 +46,6 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipe
@@ -61,38 +58,30 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.abs
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
 @LargeTest
 @OptIn(ExperimentalFoundationApi::class)
-class AnchoredDraggableGestureTest {
-
-    @get:Rule val rule = createComposeRule()
+class AnchoredDraggableGestureTest(testNewBehavior: Boolean) :
+    AnchoredDraggableBackwardsCompatibleTest(testNewBehavior) {
 
     private val AnchoredDraggableTestTag = "dragbox"
     private val AnchoredDraggableBoxSize = 200.dp
 
     @Test
     fun anchoredDraggable_swipe_horizontal() {
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
-            )
+        val (state, modifier) = createStateAndModifier(initialValue = A, Orientation.Horizontal)
         val anchors = DraggableAnchors {
             A at 0f
             B at AnchoredDraggableBoxSize.value / 2f
@@ -107,10 +96,7 @@
                         Box(
                             Modifier.requiredSize(AnchoredDraggableBoxSize)
                                 .testTag(AnchoredDraggableTestTag)
-                                .anchoredDraggable(
-                                    state = state,
-                                    orientation = Orientation.Horizontal
-                                )
+                                .then(modifier)
                                 .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                                 .background(Color.Red)
                         )
@@ -155,14 +141,8 @@
 
     @Test
     fun anchoredDraggable_swipe_vertical() {
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
-            )
+        val (state, modifier) =
+            createStateAndModifier(initialValue = A, orientation = Orientation.Vertical)
         val anchors = DraggableAnchors {
             A at 0f
             B at AnchoredDraggableBoxSize.value / 2f
@@ -177,10 +157,7 @@
                         Box(
                             Modifier.requiredSize(AnchoredDraggableBoxSize)
                                 .testTag(AnchoredDraggableTestTag)
-                                .anchoredDraggable(
-                                    state = state,
-                                    orientation = Orientation.Vertical
-                                )
+                                .then(modifier)
                                 .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                                 .background(Color.Red)
                         )
@@ -225,13 +202,11 @@
 
     @Test
     fun anchoredDraggable_swipe_disabled_horizontal() {
-        val state =
-            AnchoredDraggableState(
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = A,
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
+                orientation = Orientation.Horizontal,
+                enabled = false
             )
         val anchors = DraggableAnchors {
             A at 0f
@@ -247,11 +222,7 @@
                         Box(
                             Modifier.requiredSize(AnchoredDraggableBoxSize)
                                 .testTag(AnchoredDraggableTestTag)
-                                .anchoredDraggable(
-                                    state = state,
-                                    orientation = Orientation.Horizontal,
-                                    enabled = false
-                                )
+                                .then(modifier)
                                 .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                                 .background(Color.Red)
                         )
@@ -273,13 +244,11 @@
 
     @Test
     fun anchoredDraggable_swipe_disabled_vertical() {
-        val state =
-            AnchoredDraggableState(
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = A,
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
+                orientation = Orientation.Vertical,
+                enabled = false
             )
         val anchors = DraggableAnchors {
             A at 0f
@@ -295,11 +264,7 @@
                         Box(
                             Modifier.requiredSize(AnchoredDraggableBoxSize)
                                 .testTag(AnchoredDraggableTestTag)
-                                .anchoredDraggable(
-                                    state = state,
-                                    orientation = Orientation.Vertical,
-                                    enabled = false
-                                )
+                                .then(modifier)
                                 .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                                 .background(Color.Red)
                         )
@@ -320,226 +285,21 @@
     }
 
     @Test
-    fun anchoredDraggable_negative_offset_targetState() {
-        val positionalThreshold = 0.5f
-        val absThreshold = abs(positionalThreshold)
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = { distance -> distance * positionalThreshold },
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
-            )
-        rule.setContent {
-            Box(Modifier.fillMaxSize()) {
-                Box(
-                    Modifier.requiredSize(AnchoredDraggableBoxSize)
-                        .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
-                        .onSizeChanged { layoutSize ->
-                            val anchors = DraggableAnchors {
-                                A at 0f
-                                B at -layoutSize.width.toFloat()
-                            }
-                            state.updateAnchors(anchors)
-                        }
-                        .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
-                        .background(Color.Red)
-                )
-            }
-        }
-
-        val positionOfA = state.anchors.positionOf(A)
-        val positionOfB = state.anchors.positionOf(B)
-        val distance = abs(positionOfA - positionOfB)
-
-        rule.onNodeWithTag(AnchoredDraggableTestTag).performTouchInput {
-            swipeLeft(startX = right, endX = left)
-        }
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(B)
-        assertThat(state.targetValue).isEqualTo(B)
-
-        state.dispatchRawDelta(distance * (absThreshold * 1.1f))
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(B)
-        assertThat(state.targetValue).isEqualTo(A)
-
-        runBlocking(AutoTestFrameClock()) { state.settle(velocity = 0f) }
-
-        assertThat(state.currentValue).isEqualTo(A)
-        assertThat(state.targetValue).isEqualTo(A)
-    }
-
-    @Test
-    fun anchoredDraggable_positionalThresholds_fractional_targetState() {
-        val positionalThreshold = 0.5f
-        val absThreshold = abs(positionalThreshold)
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
-            )
-        rule.setContent {
-            Box(Modifier.fillMaxSize()) {
-                Box(
-                    Modifier.requiredSize(AnchoredDraggableBoxSize)
-                        .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
-                        .onSizeChanged { layoutSize ->
-                            val anchors = DraggableAnchors {
-                                A at 0f
-                                B at layoutSize.width / 2f
-                                C at layoutSize.width.toFloat()
-                            }
-                            state.updateAnchors(anchors)
-                        }
-                        .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
-                        .background(Color.Red)
-                )
-            }
-        }
-
-        val positionOfA = state.anchors.positionOf(A)
-        val positionOfB = state.anchors.positionOf(B)
-        val distance = abs(positionOfA - positionOfB)
-        state.dispatchRawDelta(positionOfA + distance * (absThreshold * 0.9f))
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(A)
-        assertThat(state.targetValue).isEqualTo(A)
-
-        state.dispatchRawDelta(distance * 0.2f)
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(A)
-        assertThat(state.targetValue).isEqualTo(B)
-
-        runBlocking(AutoTestFrameClock()) { state.settle(velocity = 0f) }
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(B)
-        assertThat(state.targetValue).isEqualTo(B)
-
-        state.dispatchRawDelta(-distance * (absThreshold * 0.9f))
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(B)
-        assertThat(state.targetValue).isEqualTo(B)
-
-        state.dispatchRawDelta(-distance * 0.2f)
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(B)
-        assertThat(state.targetValue).isEqualTo(A)
-
-        runBlocking(AutoTestFrameClock()) { state.settle(velocity = 0f) }
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(A)
-        assertThat(state.targetValue).isEqualTo(A)
-    }
-
-    @Test
-    fun anchoredDraggable_positionalThresholds_fractional_negativeThreshold_targetState() {
-        val positionalThreshold = -0.5f
-        val absThreshold = abs(positionalThreshold)
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
-            )
-        rule.setContent {
-            Box(Modifier.fillMaxSize()) {
-                Box(
-                    Modifier.requiredSize(AnchoredDraggableBoxSize)
-                        .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
-                        .onSizeChanged { layoutSize ->
-                            val anchors = DraggableAnchors {
-                                A at 0f
-                                B at layoutSize.width / 2f
-                                C at layoutSize.width.toFloat()
-                            }
-                            state.updateAnchors(anchors)
-                        }
-                        .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
-                        .background(Color.Red)
-                )
-            }
-        }
-
-        val positionOfA = state.anchors.positionOf(A)
-        val positionOfB = state.anchors.positionOf(B)
-        val distance = abs(positionOfA - positionOfB)
-        state.dispatchRawDelta(positionOfA + distance * (absThreshold * 0.9f))
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(A)
-        assertThat(state.targetValue).isEqualTo(A)
-
-        state.dispatchRawDelta(distance * 0.2f)
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(A)
-        assertThat(state.targetValue).isEqualTo(B)
-
-        runBlocking(AutoTestFrameClock()) { state.settle(velocity = 0f) }
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(B)
-        assertThat(state.targetValue).isEqualTo(B)
-
-        state.dispatchRawDelta(-distance * (absThreshold * 0.9f))
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(B)
-        assertThat(state.targetValue).isEqualTo(B)
-
-        state.dispatchRawDelta(-distance * 0.2f)
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(B)
-        assertThat(state.targetValue).isEqualTo(A)
-
-        runBlocking(AutoTestFrameClock()) { state.settle(velocity = 0f) }
-        rule.waitForIdle()
-
-        assertThat(state.currentValue).isEqualTo(A)
-        assertThat(state.targetValue).isEqualTo(A)
-    }
-
-    @Test
     fun anchoredDraggable_velocityThreshold_settle_velocityHigherThanThreshold_advances() =
         runBlocking(AutoTestFrameClock()) {
-            val velocity = 100.dp
-            val velocityPx = with(rule.density) { velocity.toPx() }
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = A,
-                    positionalThreshold = DefaultPositionalThreshold,
-                    velocityThreshold = { velocityPx / 2f },
-                    snapAnimationSpec = tween(),
-                    decayAnimationSpec = DefaultDecayAnimationSpec
+                    anchors =
+                        DraggableAnchors {
+                            A at 0f
+                            B at 100f
+                            C at 200f
+                        }
                 )
-            state.updateAnchors(
-                DraggableAnchors {
-                    A at 0f
-                    B at 100f
-                    C at 200f
-                }
-            )
+            val flingBehavior = createAnchoredDraggableFlingBehavior(state, rule.density)
             state.dispatchRawDelta(60f)
-            state.settle(velocityPx)
+            performFling(flingBehavior, state, AnchoredDraggableMinFlingVelocityPx + 1)
             rule.waitForIdle()
             assertThat(state.currentValue).isEqualTo(B)
         }
@@ -547,83 +307,63 @@
     @Test
     fun anchoredDraggable_velocityThreshold_settle_velocityLowerThanThreshold_doesntAdvance() =
         runBlocking(AutoTestFrameClock()) {
-            val velocity = 100.dp
-            val velocityPx = with(rule.density) { velocity.toPx() }
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = A,
-                    velocityThreshold = { velocityPx },
-                    positionalThreshold = { Float.POSITIVE_INFINITY },
-                    snapAnimationSpec = tween(),
-                    decayAnimationSpec = DefaultDecayAnimationSpec
+                    DraggableAnchors {
+                        A at 0f
+                        B at 100f
+                        C at 200f
+                    }
                 )
-            state.updateAnchors(
-                DraggableAnchors {
-                    A at 0f
-                    B at 100f
-                    C at 200f
-                }
-            )
-            state.dispatchRawDelta(60f)
-            state.settle(velocityPx / 2)
+            val flingBehavior = createAnchoredDraggableFlingBehavior(state, rule.density)
+
+            state.dispatchRawDelta(40f)
+            performFling(flingBehavior, state, AnchoredDraggableMinFlingVelocityPx * 0.9f)
             assertThat(state.currentValue).isEqualTo(A)
         }
 
     @Test
     fun anchoredDraggable_dragAndSwipeBackWithVelocity_velocityHigherThanThreshold() =
         runBlocking(AutoTestFrameClock()) {
-            val velocity = 100.dp
-            val velocityPx = with(rule.density) { velocity.toPx() }
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = B,
-                    velocityThreshold = { velocityPx },
-                    positionalThreshold = { 0f },
-                    snapAnimationSpec = tween(),
-                    decayAnimationSpec = DefaultDecayAnimationSpec
+                    DraggableAnchors {
+                        A at 0f
+                        B at 200f
+                    }
                 )
-            state.updateAnchors(
-                DraggableAnchors {
-                    A at 0f
-                    B at 200f
-                }
-            )
+            val flingBehavior = createAnchoredDraggableFlingBehavior(state, rule.density)
 
             // starting from anchor B, drag the component to the left and settle with a
             // positive velocity (higher than threshold). Result should be settling back to anchor B
             state.dispatchRawDelta(-60f)
             assertThat(state.requireOffset()).isEqualTo(140)
-            state.settle(velocityPx)
+            performFling(flingBehavior, state, AnchoredDraggableMinFlingVelocityPx + 1)
             assertThat(state.currentValue).isEqualTo(B)
 
-            state.animateTo(A)
+            state.animateTo(A, AnchoredDraggableDefaults.SnapAnimationSpec)
             assertThat(state.currentValue).isEqualTo(A)
 
             // starting from anchor A, drag the component to the right and with a negative velocity
             // (higher than threshold). Result should be settling back to anchor A
             state.dispatchRawDelta(60f)
             assertThat(state.requireOffset()).isEqualTo(60)
-            state.settle(-velocityPx)
+            performFling(flingBehavior, state, -AnchoredDraggableMinFlingVelocityPx)
             assertThat(state.currentValue).isEqualTo(A)
         }
 
     @Test
     fun anchoredDraggable_velocityThreshold_swipe_velocityHigherThanThreshold_advances() {
-        val velocityThreshold = 100.dp
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = { with(rule.density) { velocityThreshold.toPx() } },
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
-            )
+        val (state, modifier) =
+            createStateAndModifier(initialValue = A, orientation = Orientation.Horizontal)
         rule.setContent {
             Box(Modifier.fillMaxSize()) {
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
+                        .then(modifier)
                         .onSizeChanged { layoutSize ->
                             val anchors = DraggableAnchors {
                                 A at 0f
@@ -642,7 +382,7 @@
             swipeWithVelocity(
                 start = Offset(left, 0f),
                 end = Offset(right / 2, 0f),
-                endVelocity = with(rule.density) { velocityThreshold.toPx() } * 1.1f
+                endVelocity = AnchoredDraggableMinFlingVelocityPx * 1.1f
             )
         }
 
@@ -652,21 +392,13 @@
 
     @Test
     fun anchoredDraggable_velocityThreshold_swipe_velocityLowerThanThreshold_doesntAdvance() {
-        val velocityThreshold = 100.dp
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                velocityThreshold = { with(rule.density) { velocityThreshold.toPx() } },
-                positionalThreshold = { Float.POSITIVE_INFINITY },
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
-            )
+        val (state, modifier) = createStateAndModifier(initialValue = A, Orientation.Horizontal)
         rule.setContent {
             Box(Modifier.fillMaxSize()) {
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
+                        .then(modifier)
                         .onSizeChanged { layoutSize ->
                             val anchors = DraggableAnchors {
                                 A at 0f
@@ -685,7 +417,7 @@
             swipeWithVelocity(
                 start = Offset(left, 0f),
                 end = Offset(right / 4, 0f),
-                endVelocity = with(rule.density) { velocityThreshold.toPx() } * 0.9f
+                endVelocity = AnchoredDraggableMinFlingVelocityPx * 0.9f
             )
         }
 
@@ -699,21 +431,14 @@
             A at 0f
             C at 500f
         }
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = { 0f },
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
-            )
+        val (state, modifier) = createStateAndModifier(initialValue = A, Orientation.Horizontal)
         state.updateAnchors(anchors)
         rule.setContent {
             Box(Modifier.fillMaxSize()) {
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
+                        .then(modifier)
                         .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                         .background(Color.Red)
                 )
@@ -739,52 +464,35 @@
     }
 
     @Test
-    fun anchoredDraggable_animationCancelledByDrag_resetsTargetValueToClosest() {
+    fun anchoredDraggable_targetValue_animationCancelledResetsTargetValueToClosest() = runBlocking {
         rule.mainClock.autoAdvance = false
+        lateinit var scope: CoroutineScope
+        rule.setContent { scope = rememberCoroutineScope() }
+
         val anchors = DraggableAnchors {
             A at 0f
             B at 250f
             C at 500f
         }
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = { totalDistance -> totalDistance * 0.5f },
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                anchors = anchors,
-                decayAnimationSpec = DefaultDecayAnimationSpec
-            )
-        lateinit var scope: CoroutineScope
-        rule.setContent {
-            WithTouchSlop(touchSlop = 0f) {
-                scope = rememberCoroutineScope()
-                Box(Modifier.fillMaxSize()) {
-                    Box(
-                        Modifier.requiredSize(AnchoredDraggableBoxSize)
-                            .testTag(AnchoredDraggableTestTag)
-                            .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
-                            .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
-                            .background(Color.Red)
-                    )
-                }
-            }
-        }
+        val state = createAnchoredDraggableState(initialValue = A, anchors = anchors)
 
         assertThat(state.currentValue).isEqualTo(A)
         assertThat(state.targetValue).isEqualTo(A)
 
-        scope.launch { state.animateTo(C) }
+        scope.launch { state.animateTo(C, DefaultSnapAnimationSpec) }
 
-        rule.mainClock.advanceTimeUntil {
-            state.requireOffset() > abs(state.requireOffset() - anchors.positionOf(B))
-        } // Advance until our closest anchor is B
+        // Advance until our closest anchor is B
+        while (state.requireOffset() < anchors.positionOf(B)) {
+            rule.mainClock.advanceTimeByFrame()
+        }
         assertThat(state.targetValue).isEqualTo(C)
 
-        rule.onNodeWithTag(AnchoredDraggableTestTag).performTouchInput { down(Offset.Zero) }
-        rule.waitForIdle()
+        // Take over the state to cancel the ongoing animation
+        state.anchoredDrag {}
+        rule.mainClock.advanceTimeByFrame()
 
-        assertThat(state.targetValue).isEqualTo(B) // B is the closest now so we should target it
+        // B is the closest now so we should target it
+        assertThat(state.targetValue).isEqualTo(B)
     }
 
     @Test
@@ -795,14 +503,12 @@
             B at 250f
             C at 500f
         }
-        val state =
-            AnchoredDraggableState(
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = A,
-                positionalThreshold = { totalDistance -> totalDistance * 0.5f },
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
                 anchors = anchors,
-                decayAnimationSpec = DefaultDecayAnimationSpec
+                orientation = Orientation.Horizontal,
+                startDragImmediately = false,
             )
         lateinit var scope: CoroutineScope
         rule.setContent {
@@ -812,11 +518,7 @@
                     Box(
                         Modifier.requiredSize(AnchoredDraggableBoxSize)
                             .testTag(AnchoredDraggableTestTag)
-                            .anchoredDraggable(
-                                state = state,
-                                orientation = Orientation.Horizontal,
-                                startDragImmediately = false,
-                            )
+                            .then(modifier)
                             .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                             .background(Color.Red)
                     )
@@ -826,7 +528,7 @@
         assertThat(state.currentValue).isEqualTo(A)
         assertThat(state.targetValue).isEqualTo(A)
 
-        scope.launch { state.animateTo(C) }
+        scope.launch { state.animateTo(C, DefaultSnapAnimationSpec) }
 
         rule.mainClock.advanceTimeUntil {
             state.requireOffset() > abs(state.requireOffset() - anchors.positionOf(B))
@@ -841,35 +543,25 @@
 
     @Test
     fun anchoredDraggable_updatesState() {
-        val positionalThreshold = 0.5f
         val state1 =
-            AnchoredDraggableState(
+            createAnchoredDraggableState(
                 initialValue = A,
                 anchors =
                     DraggableAnchors {
                         A at 0f
                         B at 250f
                         C at 500f
-                    },
-                positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
+                    }
             )
         val state2 =
-            AnchoredDraggableState(
+            createAnchoredDraggableState(
                 initialValue = B,
                 anchors =
                     DraggableAnchors {
                         A at 0f
                         B at 250f
-                    },
-                positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
+                    }
             )
-
         var state by mutableStateOf(state1)
 
         rule.setContent {
@@ -878,7 +570,7 @@
                     Box(
                         Modifier.requiredSize(AnchoredDraggableBoxSize)
                             .testTag(AnchoredDraggableTestTag)
-                            .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
+                            .then(createAnchoredDraggableModifier(state, Orientation.Horizontal))
                             .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                             .background(Color.Red)
                     )
@@ -893,7 +585,7 @@
         // dragging across the positional threshold to settle at anchor B
         rule.onNodeWithTag(AnchoredDraggableTestTag).performTouchInput {
             down(Offset(0f, 0f))
-            moveBy(Offset(x = distance * positionalThreshold * 1.1f, y = 0f))
+            moveBy(Offset(x = distance * 0.55f, y = 0f))
             up()
         }
         rule.waitForIdle()
@@ -908,7 +600,7 @@
         // dragging across the positional threshold to settle at anchor A
         rule.onNodeWithTag(AnchoredDraggableTestTag).performTouchInput {
             down(Offset(0f, 0f))
-            moveBy(Offset(x = -distance * positionalThreshold * 1.1f, y = 0f))
+            moveBy(Offset(x = -distance * 0.55f, y = 0f))
             up()
         }
         rule.waitForIdle()
@@ -921,8 +613,8 @@
 
     @Test
     fun anchoredDraggable_reverseDirection_true_reversesDeltas() {
-        val state =
-            AnchoredDraggableState(
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = B,
                 anchors =
                     DraggableAnchors {
@@ -930,10 +622,8 @@
                         B at 250f
                         C at 500f
                     },
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
+                orientation = Orientation.Horizontal,
+                reverseDirection = true
             )
         rule.setContent {
             WithTouchSlop(0f) {
@@ -941,11 +631,7 @@
                     Box(
                         Modifier.requiredSize(AnchoredDraggableBoxSize)
                             .testTag(AnchoredDraggableTestTag)
-                            .anchoredDraggable(
-                                state = state,
-                                orientation = Orientation.Horizontal,
-                                reverseDirection = true
-                            )
+                            .then(modifier)
                             .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                             .background(Color.Red)
                     )
@@ -965,8 +651,8 @@
 
     @Test
     fun anchoredDraggable_reverseDirection_defaultValue_reversesDeltasInRTL() {
-        val state =
-            AnchoredDraggableState(
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = A,
                 anchors =
                     DraggableAnchors {
@@ -974,10 +660,7 @@
                         B at 250f
                         C at 500f
                     },
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
+                orientation = Orientation.Horizontal
             )
         var layoutDirection by mutableStateOf(LayoutDirection.Ltr)
         rule.setContent {
@@ -987,10 +670,7 @@
                         Box(
                             Modifier.requiredSize(AnchoredDraggableBoxSize)
                                 .testTag(AnchoredDraggableTestTag)
-                                .anchoredDraggable(
-                                    state = state,
-                                    orientation = Orientation.Horizontal
-                                )
+                                .then(modifier)
                                 .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                                 .background(Color.Red)
                         )
@@ -1021,14 +701,26 @@
         assertThat(state.offset).isEqualTo(state.anchors.positionOf(C))
     }
 
-    private val DefaultPositionalThreshold: (totalDistance: Float) -> Float = {
-        with(rule.density) { 56.dp.toPx() }
+    private val DefaultSnapAnimationSpec = tween<Float>()
+
+    private class HandPumpTestFrameClock : MonotonicFrameClock {
+        private val frameCh = Channel<Long>(1)
+        private val time = AtomicLong(0)
+
+        suspend fun advanceByFrame() {
+            frameCh.send(time.getAndAdd(16_000_000L))
+        }
+
+        override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
+            return onFrame(frameCh.receive())
+        }
     }
 
-    private val DefaultVelocityThreshold: () -> Float = { with(rule.density) { 125.dp.toPx() } }
-
-    private val DefaultDecayAnimationSpec: DecayAnimationSpec<Float> =
-        SplineBasedFloatDecayAnimationSpec(rule.density).generateDecayAnimationSpec()
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "testNewBehavior={0}")
+        fun params() = listOf(false, true)
+    }
 }
 
 private val NoOpDensity =
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableOverscrollTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableOverscrollTest.kt
index 13fad78..40f2b8f 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableOverscrollTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableOverscrollTest.kt
@@ -17,19 +17,15 @@
 package androidx.compose.foundation.anchoredDraggable
 
 import androidx.compose.animation.SplineBasedFloatDecayAnimationSpec
-import androidx.compose.animation.core.DecayAnimationSpec
 import androidx.compose.animation.core.generateDecayAnimationSpec
-import androidx.compose.animation.core.tween
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.OverscrollEffect
 import androidx.compose.foundation.anchoredDraggable.AnchoredDraggableTestValue.A
 import androidx.compose.foundation.anchoredDraggable.AnchoredDraggableTestValue.B
 import androidx.compose.foundation.anchoredDraggable.AnchoredDraggableTestValue.C
 import androidx.compose.foundation.background
-import androidx.compose.foundation.gestures.AnchoredDraggableState
 import androidx.compose.foundation.gestures.DraggableAnchors
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.anchoredDraggable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
@@ -37,14 +33,12 @@
 import androidx.compose.foundation.overscroll
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.testutils.WithTouchSlop
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeWithVelocity
@@ -52,21 +46,19 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.abs
 import kotlin.math.roundToInt
 import kotlin.test.Test
-import org.junit.Rule
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
 @LargeTest
 @OptIn(ExperimentalFoundationApi::class)
-class AnchoredDraggableOverscrollTest {
-
-    @get:Rule val rule = createComposeRule()
+class AnchoredDraggableOverscrollTest(testNewBehavior: Boolean) :
+    AnchoredDraggableBackwardsCompatibleTest(testNewBehavior) {
 
     private val AnchoredDraggableTestTag = "dragbox"
     private val AnchoredDraggableBoxSize = 200.dp
@@ -74,18 +66,16 @@
     @Test
     fun anchoredDraggable_scrollOutOfBounds_haveDeltaForOverscroll() {
         val overscrollEffect = TestOverscrollEffect()
-        val state =
-            AnchoredDraggableState(
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = A,
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = DefaultVelocityThreshold,
                 anchors =
                     DraggableAnchors {
                         A at 0f
                         B at 250f
                     },
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec
+                orientation = Orientation.Horizontal,
+                overscrollEffect = overscrollEffect
             )
         rule.setContent {
             WithTouchSlop(0f) {
@@ -93,11 +83,7 @@
                     Box(
                         Modifier.requiredSize(AnchoredDraggableBoxSize)
                             .testTag(AnchoredDraggableTestTag)
-                            .anchoredDraggable(
-                                state = state,
-                                orientation = Orientation.Horizontal,
-                                overscrollEffect = overscrollEffect
-                            )
+                            .then(modifier)
                             .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                     )
                 }
@@ -134,18 +120,18 @@
     fun anchoredDraggable_swipeWithVelocity_haveVelocityForOverscroll() {
         val endVelocity = 4000f
         val overscrollEffect = TestOverscrollEffect()
-        val state =
-            AnchoredDraggableState(
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = A,
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec,
                 anchors =
                     DraggableAnchors {
                         A at 0f
                         B at 250f
-                    }
+                    },
+                orientation = Orientation.Horizontal,
+                overscrollEffect = overscrollEffect,
+                decayAnimationSpec =
+                    SplineBasedFloatDecayAnimationSpec(rule.density).generateDecayAnimationSpec()
             )
 
         rule.setContent {
@@ -154,11 +140,7 @@
                     Box(
                         Modifier.requiredSize(AnchoredDraggableBoxSize)
                             .testTag(AnchoredDraggableTestTag)
-                            .anchoredDraggable(
-                                state = state,
-                                orientation = Orientation.Horizontal,
-                                overscrollEffect = overscrollEffect
-                            )
+                            .then(modifier)
                             .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                             .background(Color.Red)
                     )
@@ -199,22 +181,19 @@
 
     @Test
     fun anchoredDraggable_swipeWithVelocity_notAtBounds_noOverscroll() {
-        val positionalThreshold = 0.5f
-        val absThreshold = abs(positionalThreshold)
         val overscrollEffect = TestOverscrollEffect()
-        val state =
-            AnchoredDraggableState(
+
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = A,
-                positionalThreshold = { distance -> absThreshold * distance },
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec,
                 anchors =
                     DraggableAnchors {
                         A at 0f
                         B at 250f
                         C at 500f
-                    }
+                    },
+                orientation = Orientation.Horizontal,
+                overscrollEffect = overscrollEffect
             )
 
         rule.setContent {
@@ -224,11 +203,7 @@
                         Box(
                             Modifier.requiredSize(AnchoredDraggableBoxSize)
                                 .testTag(AnchoredDraggableTestTag)
-                                .anchoredDraggable(
-                                    state = state,
-                                    orientation = Orientation.Horizontal,
-                                    overscrollEffect = overscrollEffect
-                                )
+                                .then(modifier)
                                 .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                                 .background(Color.Red)
                         )
@@ -241,7 +216,7 @@
         val positionB = state.anchors.positionOf(B)
 
         val distance = abs(positionB - positionA)
-        val delta = distance * absThreshold * 1.1f
+        val delta = distance * 0.55f
 
         assertThat(state.currentValue).isEqualTo(A)
         assertThat(state.offset).isEqualTo(positionA)
@@ -263,22 +238,20 @@
         assertThat(abs(overscrollEffect.flingOverscrollVelocity.x)).isWithin(1f).of(0f)
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun anchoredDraggable_swipeWithVelocity_notEnoughVelocityForOverscroll() {
         val overscrollEffect = TestOverscrollEffect()
-        val state =
-            AnchoredDraggableState(
+
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = A,
-                positionalThreshold = DefaultPositionalThreshold,
-                velocityThreshold = DefaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = DefaultDecayAnimationSpec,
                 anchors =
                     DraggableAnchors {
                         A at 0f
                         B at 250f
-                    }
+                    },
+                orientation = Orientation.Horizontal,
+                overscrollEffect = overscrollEffect
             )
 
         rule.setContent {
@@ -287,11 +260,7 @@
                     Box(
                         Modifier.requiredSize(AnchoredDraggableBoxSize)
                             .testTag(AnchoredDraggableTestTag)
-                            .anchoredDraggable(
-                                state = state,
-                                orientation = Orientation.Horizontal,
-                                overscrollEffect = overscrollEffect
-                            )
+                            .then(modifier)
                             .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                             .background(Color.Red)
                     )
@@ -322,14 +291,11 @@
         assertThat(abs(overscrollEffect.flingOverscrollVelocity.x)).isWithin(1f).of(0f)
     }
 
-    private val DefaultPositionalThreshold: (totalDistance: Float) -> Float = {
-        with(rule.density) { 56.dp.toPx() }
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "testNewBehavior={0}")
+        fun params() = listOf(false, true)
     }
-
-    private val DefaultVelocityThreshold: () -> Float = { with(rule.density) { 125.dp.toPx() } }
-
-    private val DefaultDecayAnimationSpec: DecayAnimationSpec<Float> =
-        SplineBasedFloatDecayAnimationSpec(rule.density).generateDecayAnimationSpec()
 }
 
 @OptIn(ExperimentalFoundationApi::class)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
index b874fb0..ced6cfa 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
@@ -35,10 +35,11 @@
 import androidx.compose.foundation.anchoredDraggable.AnchoredDraggableTestValue.B
 import androidx.compose.foundation.anchoredDraggable.AnchoredDraggableTestValue.C
 import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.AnchoredDraggableDefaults
+import androidx.compose.foundation.gestures.AnchoredDraggableMinFlingVelocity
 import androidx.compose.foundation.gestures.AnchoredDraggableState
 import androidx.compose.foundation.gestures.DraggableAnchors
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.anchoredDraggable
 import androidx.compose.foundation.gestures.animateTo
 import androidx.compose.foundation.gestures.animateToWithDecay
 import androidx.compose.foundation.gestures.snapTo
@@ -52,6 +53,7 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.runtime.withFrameNanos
 import androidx.compose.ui.Modifier
@@ -59,14 +61,12 @@
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.StateRestorationTester
-import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeDown
 import androidx.compose.ui.test.swipeUp
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -77,7 +77,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.isActive
@@ -86,36 +85,29 @@
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.test.runTest
 import org.junit.Ignore
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
 @LargeTest
 @OptIn(ExperimentalFoundationApi::class)
-class AnchoredDraggableStateTest {
-
-    @get:Rule val rule = createComposeRule()
+class AnchoredDraggableStateTest(testNewBehavior: Boolean) :
+    AnchoredDraggableBackwardsCompatibleTest(testNewBehavior) {
 
     private val AnchoredDraggableTestTag = "dragbox"
     private val AnchoredDraggableBoxSize = 200.dp
 
     @Test
     fun anchoredDraggable_state_canSkipStateByFling() {
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val (state, modifier) =
+            createStateAndModifier(initialValue = A, orientation = Orientation.Vertical)
         rule.setContent {
             Box(Modifier.fillMaxSize()) {
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Vertical)
+                        .then(modifier)
                         .onSizeChanged { layoutSize ->
                             state.updateAnchors(
                                 DraggableAnchors {
@@ -140,20 +132,14 @@
 
     @Test
     fun anchoredDraggable_targetState_updatedOnSwipe() {
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val (state, modifier) =
+            createStateAndModifier(initialValue = A, orientation = Orientation.Vertical)
         rule.setContent {
             Box(Modifier.fillMaxSize()) {
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Vertical)
+                        .then(modifier)
                         .onSizeChanged { layoutSize ->
                             state.updateAnchors(
                                 DraggableAnchors {
@@ -207,14 +193,15 @@
         rule.mainClock.autoAdvance = false
         val animationDuration = 300
         val frameLengthMillis = 16L
-        val state =
-            AnchoredDraggableState(
+        val snapAnimationSpec = tween<Float>(animationDuration, easing = LinearEasing)
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = A,
-                snapAnimationSpec = tween(animationDuration, easing = LinearEasing),
-                positionalThreshold = { distance -> distance * 0.5f },
-                velocityThreshold = defaultVelocityThreshold,
-                decayAnimationSpec = defaultDecayAnimationSpec
+                orientation = Orientation.Vertical,
+                snapAnimationSpec = snapAnimationSpec,
+                positionalThreshold = { distance -> distance * 0.5f }
             )
+
         lateinit var scope: CoroutineScope
         rule.setContent {
             scope = rememberCoroutineScope()
@@ -222,7 +209,7 @@
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Vertical)
+                        .then(modifier)
                         .onSizeChanged { layoutSize ->
                             state.updateAnchors(
                                 DraggableAnchors {
@@ -238,7 +225,7 @@
             }
         }
 
-        scope.launch { state.animateTo(targetValue = B) }
+        scope.launch { state.animateTo(targetValue = B, snapAnimationSpec) }
         rule.mainClock.advanceTimeBy(1 * frameLengthMillis)
 
         assertWithMessage("Current state").that(state.currentValue).isEqualTo(A)
@@ -254,12 +241,8 @@
     @Test
     fun anchoredDraggable_targetState_updatedWithDeltaDispatch() {
         val state =
-            AnchoredDraggableState(
+            createAnchoredDraggableState(
                 initialValue = A,
-                positionalThreshold = { it / 2f },
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = tween(),
-                decayAnimationSpec = defaultDecayAnimationSpec,
                 anchors =
                     DraggableAnchors {
                         A at 0f
@@ -267,6 +250,7 @@
                         C at 400f
                     }
             )
+        val flingBehavior = createAnchoredDraggableFlingBehavior(state, rule.density)
 
         val initialOffset = state.requireOffset()
 
@@ -283,7 +267,7 @@
         assertThat(state.currentValue).isEqualTo(A)
         assertThat(state.targetValue).isEqualTo(B)
 
-        runBlocking(AutoTestFrameClock()) { state.settle(velocity = 0f) }
+        runBlocking(AutoTestFrameClock()) { performFling(flingBehavior, state, 0f) }
 
         assertThat(state.currentValue).isEqualTo(B)
         assertThat(state.targetValue).isEqualTo(B)
@@ -300,7 +284,7 @@
         assertThat(state.currentValue).isEqualTo(B)
         assertThat(state.targetValue).isEqualTo(A)
 
-        runBlocking(AutoTestFrameClock()) { state.settle(velocity = 0f) }
+        runBlocking(AutoTestFrameClock()) { performFling(flingBehavior, state, 0f) }
 
         assertThat(state.currentValue).isEqualTo(A)
         assertThat(state.targetValue).isEqualTo(A)
@@ -310,12 +294,8 @@
     fun anchoredDraggable_currentValue_updatedWithDeltaDispatch() =
         runBlocking(AutoTestFrameClock()) {
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = A,
-                    positionalThreshold = defaultPositionalThreshold,
-                    velocityThreshold = defaultVelocityThreshold,
-                    snapAnimationSpec = defaultAnimationSpec,
-                    decayAnimationSpec = defaultDecayAnimationSpec,
                     anchors =
                         DraggableAnchors {
                             A at 0f
@@ -336,13 +316,12 @@
         val animationDuration = 320
         val frameLengthMillis = 16
         val amountOfFramesForAnimation = animationDuration / frameLengthMillis
-        val state =
-            AnchoredDraggableState(
+        val snapAnimationSpec = tween<Float>(animationDuration, easing = LinearEasing)
+        val (state, modifier) =
+            createStateAndModifier(
                 initialValue = A,
-                snapAnimationSpec = tween(animationDuration, easing = LinearEasing),
-                positionalThreshold = { distance -> distance * 0.5f },
-                velocityThreshold = defaultVelocityThreshold,
-                decayAnimationSpec = defaultDecayAnimationSpec
+                snapAnimationSpec = snapAnimationSpec,
+                orientation = Orientation.Vertical
             )
         lateinit var scope: CoroutineScope
         rule.setContent {
@@ -351,7 +330,7 @@
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Vertical)
+                        .then(modifier)
                         .onSizeChanged { layoutSize ->
                             state.updateAnchors(
                                 DraggableAnchors {
@@ -371,7 +350,7 @@
         assertThat(state.targetValue).isEqualTo(A)
         assertThat(state.progress(from = A, to = B)).isEqualTo(0f)
 
-        scope.launch { state.animateTo(B) }
+        scope.launch { state.animateTo(B, snapAnimationSpec) }
         rule.mainClock.advanceTimeByFrame() // Start dispatching and running the animation
 
         repeat(amountOfFramesForAnimation) { frame ->
@@ -387,7 +366,7 @@
         rule.waitForIdle()
         rule.mainClock.autoAdvance = false
 
-        scope.launch { state.animateTo(A) }
+        scope.launch { state.animateTo(A, snapAnimationSpec) }
         rule.mainClock.advanceTimeByFrame() // Start dispatching and running the animation
 
         repeat(amountOfFramesForAnimation) { frame ->
@@ -403,20 +382,14 @@
 
     @Test
     fun anchoredDraggable_snapTo_updatesImmediately() = runBlocking {
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val (state, modifier) =
+            createStateAndModifier(initialValue = A, orientation = Orientation.Vertical)
         rule.setContent {
             Box(Modifier.fillMaxSize()) {
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Vertical)
+                        .then(modifier)
                         .onSizeChanged { layoutSize ->
                             state.updateAnchors(
                                 DraggableAnchors {
@@ -437,22 +410,18 @@
     }
 
     @Test
-    fun anchoredDraggable_rememberanchoredDraggableState_restored() {
+    fun anchoredDraggable_saver_restoresCurrentValue() {
         val restorationTester = StateRestorationTester(rule)
 
         val initialState = C
-        val snapAnimationSpec = tween<Float>(durationMillis = 1000)
-        val state =
-            AnchoredDraggableState(
-                initialValue = initialState,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = snapAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        lateinit var state: AnchoredDraggableState<AnchoredDraggableTestValue>
         lateinit var scope: CoroutineScope
 
         restorationTester.setContent {
+            state =
+                rememberSaveable(saver = AnchoredDraggableState.Saver()) {
+                    createAnchoredDraggableState(initialState)
+                }
             SideEffect {
                 state.updateAnchors(
                     DraggableAnchors {
@@ -468,35 +437,31 @@
         restorationTester.emulateSavedInstanceStateRestore()
 
         assertThat(state.currentValue).isEqualTo(initialState)
-        assertThat(state.snapAnimationSpec).isEqualTo(snapAnimationSpec)
 
-        scope.launch { state.animateTo(B) }
+        scope.launch { state.animateTo(B, DefaultSnapAnimationSpec) }
         rule.waitForIdle()
         assertThat(state.currentValue).isEqualTo(B)
 
+        val stateBeforeSavedInstanceStateRestore = state
         restorationTester.emulateSavedInstanceStateRestore()
+        assertThat(stateBeforeSavedInstanceStateRestore).isNotSameInstanceAs(state)
         assertThat(state.currentValue).isEqualTo(B)
     }
 
     @Test
-    fun anchoredDraggable_targetState_accessedInInitialComposition() {
+    fun anchoredDraggable_accessInInitialComposition_targetState() {
         lateinit var targetState: AnchoredDraggableTestValue
         rule.setContent {
-            val state = remember {
-                AnchoredDraggableState(
-                    initialValue = B,
-                    positionalThreshold = defaultPositionalThreshold,
-                    velocityThreshold = defaultVelocityThreshold,
-                    snapAnimationSpec = defaultAnimationSpec,
-                    decayAnimationSpec = defaultDecayAnimationSpec
-                )
-            }
-            LaunchedEffect(state.targetValue) { targetState = state.targetValue }
+            val (state, modifier) =
+                remember {
+                    createStateAndModifier(initialValue = B, orientation = Orientation.Horizontal)
+                }
+            targetState = state.targetValue
             Box(Modifier.fillMaxSize()) {
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
+                        .then(modifier)
                         .onSizeChanged { layoutSize ->
                             state.updateAnchors(
                                 DraggableAnchors {
@@ -519,22 +484,16 @@
     fun anchoredDraggable_progress_accessedInInitialComposition() {
         var progress = Float.NaN
         rule.setContent {
-            val state = remember {
-                AnchoredDraggableState(
-                    initialValue = B,
-                    positionalThreshold = defaultPositionalThreshold,
-                    velocityThreshold = defaultVelocityThreshold,
-                    snapAnimationSpec = defaultAnimationSpec,
-                    decayAnimationSpec = defaultDecayAnimationSpec
-                )
-            }
-            val latestProgress = state.progress(from = A, to = B)
-            LaunchedEffect(latestProgress) { progress = latestProgress }
+            val (state, modifier) =
+                remember {
+                    createStateAndModifier(initialValue = B, orientation = Orientation.Horizontal)
+                }
+            progress = state.progress(from = A, to = B)
             Box(Modifier.fillMaxSize()) {
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
+                        .then(modifier)
                         .onSizeChanged { layoutSize ->
                             state.updateAnchors(
                                 DraggableAnchors {
@@ -553,25 +512,19 @@
         assertThat(progress).isEqualTo(1f)
     }
 
-    @Test
     @Ignore("Todo: Fix differences between tests and real code - this shouldn't work :)")
+    @Test
     fun anchoredDraggable_requireOffset_accessedInInitialComposition_throws() {
         var exception: Throwable? = null
-        val state =
-            AnchoredDraggableState(
-                initialValue = B,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val (state, modifier) =
+            createStateAndModifier(initialValue = B, orientation = Orientation.Horizontal)
         var offset: Float? = null
         rule.setContent {
             Box(Modifier.fillMaxSize()) {
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Horizontal)
+                        .then(modifier)
                         .onSizeChanged { layoutSize ->
                             state.updateAnchors(
                                 DraggableAnchors {
@@ -585,7 +538,9 @@
                         .background(Color.Red)
                 )
             }
-            exception = runCatching { offset = state.requireOffset() }.exceptionOrNull()
+            SideEffect {
+                exception = runCatching { offset = state.requireOffset() }.exceptionOrNull()
+            }
         }
 
         assertThat(state.anchors.size).isNotEqualTo(0)
@@ -601,12 +556,9 @@
         var exception: Throwable? = null
         rule.setContent {
             val state = remember {
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = B,
-                    positionalThreshold = defaultPositionalThreshold,
-                    velocityThreshold = defaultVelocityThreshold,
-                    snapAnimationSpec = defaultAnimationSpec,
-                    decayAnimationSpec = defaultDecayAnimationSpec
+                    anchors = DraggableAnchors { B at 100f }
                 )
             }
             LaunchedEffect(Unit) {
@@ -637,14 +589,8 @@
                 )
                 .let { TimeUnit.NANOSECONDS.toMillis(it) }
 
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = snapAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val (state, modifier) =
+            createStateAndModifier(initialValue = A, orientation = Orientation.Vertical)
         lateinit var scope: CoroutineScope
 
         rule.setContent {
@@ -654,14 +600,14 @@
                 Box(
                     Modifier.requiredSize(AnchoredDraggableBoxSize)
                         .testTag(AnchoredDraggableTestTag)
-                        .anchoredDraggable(state = state, orientation = Orientation.Vertical)
+                        .then(modifier)
                         .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
                         .background(Color.Red)
                 )
             }
         }
 
-        scope.launch { state.animateTo(C) }
+        scope.launch { state.animateTo(C, snapAnimationSpec) }
         var highestOffset = 0f
         for (i in 0..animationDuration step 16) {
             highestOffset = state.requireOffset()
@@ -672,30 +618,16 @@
 
     @Test
     fun anchoredDraggable_targetNotInAnchors_animateTo_updatesCurrentValue() {
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val state = createAnchoredDraggableState(initialValue = A)
         assertThat(state.anchors.size).isEqualTo(0)
         assertThat(state.currentValue).isEqualTo(A)
-        runBlocking { state.animateTo(B) }
+        runBlocking { state.animateTo(B, tween()) }
         assertThat(state.currentValue).isEqualTo(B)
     }
 
     @Test
     fun anchoredDraggable_targetNotInAnchors_snapTo_updatesCurrentValue() {
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val state = createAnchoredDraggableState(initialValue = A)
         assertThat(state.anchors.size).isEqualTo(0)
         assertThat(state.currentValue).isEqualTo(A)
         runBlocking { state.snapTo(B) }
@@ -704,14 +636,7 @@
 
     @Test
     fun anchoredDraggable_updateAnchors_noOngoingDrag_shouldUpdateOffset() {
-        val anchoredDraggableState =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val anchoredDraggableState = createAnchoredDraggableState(initialValue = A)
 
         assertThat(anchoredDraggableState.currentValue).isEqualTo(A)
         assertThat(anchoredDraggableState.targetValue).isEqualTo(A)
@@ -731,18 +656,10 @@
         assertThat(anchoredDraggableState.offset).isEqualTo(offsetAtB)
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun anchoredDraggable_updateAnchors_ongoingDrag_shouldRestartDrag() = runTest {
         // Given an anchored draggable state
-        val anchoredDraggableState =
-            AnchoredDraggableState(
-                initialValue = 1,
-                defaultPositionalThreshold,
-                defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val anchoredDraggableState = createAnchoredDraggableState(initialValue = 1)
 
         val anchorUpdates = Channel<DraggableAnchors<Int>>()
         val dragJob = launch {
@@ -769,18 +686,10 @@
         dragJob.cancel()
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun anchoredDraggable_updateAnchors_anchoredDrag_invokedWithLatestAnchors() = runTest {
         // Given an anchored draggable state
-        val anchoredDraggableState =
-            AnchoredDraggableState(
-                initialValue = 1,
-                defaultPositionalThreshold,
-                defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val anchoredDraggableState = createAnchoredDraggableState(initialValue = 1)
 
         val anchorUpdates = Channel<DraggableAnchors<Int>>()
         val dragJob =
@@ -808,17 +717,9 @@
         dragJob.cancel()
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun anchoredDraggable_updateAnchors_anchoredDrag_invokedWithLatestTarget() = runTest {
-        val anchoredDraggableState =
-            AnchoredDraggableState(
-                initialValue = A,
-                defaultPositionalThreshold,
-                defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val anchoredDraggableState = createAnchoredDraggableState(initialValue = A)
         anchoredDraggableState.updateAnchors(
             DraggableAnchors {
                 A at 0f
@@ -855,17 +756,9 @@
         dragJob.cancel()
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun anchoredDraggable_dragCompletesExceptionally_cleansUp() = runTest {
-        val anchoredDraggableState =
-            AnchoredDraggableState(
-                initialValue = A,
-                defaultPositionalThreshold,
-                defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val anchoredDraggableState = createAnchoredDraggableState(initialValue = A)
         val cancellationSignal = CompletableDeferred(false)
         val anchoredDragUpdates = Channel<Unit>()
         val dragJob = launch {
@@ -886,12 +779,8 @@
     @Test
     fun anchoredDraggable_customDrag_doesNotSnapToClosestAnchor() = runBlocking {
         val state =
-            AnchoredDraggableState(
+            createAnchoredDraggableState(
                 initialValue = A,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec,
                 anchors =
                     DraggableAnchors {
                         A at 0f
@@ -912,14 +801,7 @@
 
     @Test
     fun anchoredDraggable_customDrag_updatesVelocity() = runBlocking {
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val state = createAnchoredDraggableState(initialValue = A)
         val anchors = DraggableAnchors {
             A at 0f
             B at 200f
@@ -936,14 +818,7 @@
         val clock = HandPumpTestFrameClock()
         val dragScope = CoroutineScope(clock)
 
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val state = createAnchoredDraggableState(initialValue = A)
         val anchors = DraggableAnchors {
             A at 0f
             B at 200f
@@ -970,15 +845,7 @@
             A at initialValueOffset
             B at 200f
         }
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                anchors = anchors,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val state = createAnchoredDraggableState(initialValue = A, anchors = anchors)
         assertThat(state.anchors).isEqualTo(anchors)
         assertThat(state.offset).isEqualTo(initialValueOffset)
     }
@@ -986,15 +853,7 @@
     @Test
     fun anchoredDraggable_constructorWithAnchors_initialValueNotInAnchors_updatesCurrentValue() {
         val anchors = DraggableAnchors { B at 200f }
-        val state =
-            AnchoredDraggableState(
-                initialValue = A,
-                anchors = anchors,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec
-            )
+        val state = createAnchoredDraggableState(initialValue = A, anchors = anchors)
         assertThat(state.anchors).isEqualTo(anchors)
         assertThat(state.offset).isNaN()
     }
@@ -1004,16 +863,12 @@
         runBlocking {
             var shouldBlockValueC = false
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = B,
-                    positionalThreshold = defaultPositionalThreshold,
-                    velocityThreshold = defaultVelocityThreshold,
-                    snapAnimationSpec = defaultAnimationSpec,
                     confirmValueChange = {
                         if (shouldBlockValueC) it != C // block state value C
                         else true
                     },
-                    decayAnimationSpec = defaultDecayAnimationSpec
                 )
             val anchors = DraggableAnchors {
                 A at 0f
@@ -1039,37 +894,31 @@
         }
 
     @Test
-    fun anchoredDraggable_animateToTarget_ZeroVelocity() =
+    fun anchoredDraggable_animateToTarget_zeroVelocity_usesSnapAnimationSpec() =
         runBlocking(AutoTestFrameClock()) {
-            val positionalThreshold = 0.5f
             val inspectDecayAnimationSpec =
                 InspectSplineAnimationSpec(SplineBasedFloatDecayAnimationSpec(rule.density))
+            val decayAnimationSpec = inspectDecayAnimationSpec.generateDecayAnimationSpec<Float>()
             val tweenAnimationSpec = InspectSpringAnimationSpec(tween(easing = LinearEasing))
+
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = A,
-                    velocityThreshold = defaultVelocityThreshold,
-                    positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
                     anchors =
                         DraggableAnchors {
                             A at 0f
                             B at 200f
-                        },
-                    snapAnimationSpec = tweenAnimationSpec,
-                    decayAnimationSpec = inspectDecayAnimationSpec.generateDecayAnimationSpec()
+                        }
                 )
-            val positionOfA = state.anchors.positionOf(A)
-            val positionOfB = state.anchors.positionOf(B)
-            val distance = abs(positionOfA - positionOfB)
 
             assertThat(state.currentValue).isEqualTo(A)
 
-            // dragging across the positionalThreshold to settle at anchor B
-            val delta = distance * positionalThreshold * 1.1f
-            state.dispatchRawDelta(delta)
-            assertThat(state.requireOffset()).isEqualTo(positionOfA + delta)
-
-            state.settle(velocity = 0f)
+            state.animateToWithDecay(
+                targetValue = B,
+                velocity = 0f,
+                snapAnimationSpec = tweenAnimationSpec,
+                decayAnimationSpec = decayAnimationSpec
+            )
 
             assertThat(state.currentValue).isEqualTo(B)
 
@@ -1088,17 +937,13 @@
                 inspectDecayAnimationSpec.generateDecayAnimationSpec()
             val tweenAnimationSpec = InspectSpringAnimationSpec(tween(easing = LinearEasing))
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = A,
-                    velocityThreshold = defaultVelocityThreshold,
-                    positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
                     anchors =
                         DraggableAnchors {
                             A at 0f
                             B at 200f
-                        },
-                    snapAnimationSpec = tweenAnimationSpec,
-                    decayAnimationSpec = decayAnimationSpec
+                        }
                 )
 
             val positionOfA = state.anchors.positionOf(A)
@@ -1118,7 +963,7 @@
                 decayAnimationSpec.calculateTargetValue(state.requireOffset(), velocity)
             assertThat(projectedTarget).isLessThan(positionOfB)
 
-            state.settle(velocity)
+            state.animateToWithDecay(B, velocity, tweenAnimationSpec, decayAnimationSpec)
             assertThat(state.currentValue).isEqualTo(B)
 
             // velocity is not enough to perform decay animation, target animation will be used
@@ -1136,17 +981,13 @@
                 inspectDecayAnimationSpec.generateDecayAnimationSpec()
             val tweenAnimationSpec = InspectSpringAnimationSpec(tween(easing = LinearEasing))
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = B,
-                    velocityThreshold = defaultVelocityThreshold,
-                    positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
                     anchors =
                         DraggableAnchors {
                             A at 0f
                             B at 200f
-                        },
-                    snapAnimationSpec = tweenAnimationSpec,
-                    decayAnimationSpec = decayAnimationSpec
+                        }
                 )
 
             val positionOfA = state.anchors.positionOf(A)
@@ -1165,7 +1006,7 @@
             val projectedTarget = decayAnimationSpec.calculateTargetValue(newOffset, velocity)
             assertThat(projectedTarget).isGreaterThan(positionOfA)
 
-            state.settle(velocity)
+            state.animateToWithDecay(A, velocity, tweenAnimationSpec, decayAnimationSpec)
             assertThat(state.currentValue).isEqualTo(A)
 
             // velocity is not enough to perform decay animation, target animation will be used
@@ -1183,17 +1024,13 @@
                 inspectDecayAnimationSpec.generateDecayAnimationSpec()
             val tweenAnimationSpec = InspectSpringAnimationSpec(tween(easing = LinearEasing))
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = A,
-                    velocityThreshold = defaultVelocityThreshold,
-                    positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
                     anchors =
                         DraggableAnchors {
                             A at 0f
                             B at 200f
-                        },
-                    snapAnimationSpec = tweenAnimationSpec,
-                    decayAnimationSpec = decayAnimationSpec
+                        }
                 )
 
             val positionOfA = state.anchors.positionOf(A)
@@ -1212,7 +1049,7 @@
             val projectedTarget = decayAnimationSpec.calculateTargetValue(newOffset, velocity)
             assertThat(projectedTarget).isAtLeast(positionOfB)
 
-            state.settle(velocity)
+            state.animateToWithDecay(B, velocity, tweenAnimationSpec, decayAnimationSpec)
             assertThat(state.currentValue).isEqualTo(B)
 
             // velocity is enough to perform decay animation
@@ -1230,17 +1067,13 @@
                 inspectDecayAnimationSpec.generateDecayAnimationSpec()
             val tweenAnimationSpec = InspectSpringAnimationSpec(tween(easing = LinearEasing))
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = B,
-                    velocityThreshold = defaultVelocityThreshold,
-                    positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
                     anchors =
                         DraggableAnchors {
                             A at 0f
                             B at 200f
-                        },
-                    snapAnimationSpec = tweenAnimationSpec,
-                    decayAnimationSpec = decayAnimationSpec
+                        }
                 )
 
             val positionOfA = state.anchors.positionOf(A)
@@ -1259,7 +1092,7 @@
             val projectedTarget = decayAnimationSpec.calculateTargetValue(newOffset, velocity)
             assertThat(projectedTarget).isAtMost(positionOfA)
 
-            state.settle(velocity)
+            state.animateToWithDecay(A, velocity, tweenAnimationSpec, decayAnimationSpec)
             assertThat(state.currentValue).isEqualTo(A)
 
             // velocity is not enough to perform decay animation
@@ -1268,27 +1101,29 @@
         }
 
     @Test
-    fun anchoredDraggable_dragNotInTheSameDirectionOfTarget_positiveOffset_positiveVelocity() =
+    fun anchoredDraggable_performFling_positiveOffset_positiveVelocity() =
         runBlocking(AutoTestFrameClock()) {
-            val velocityPx = with(rule.density) { 1000.dp.toPx() }
+            val velocityPx = with(rule.density) { AnchoredDraggableMinFlingVelocity.toPx() }
             val positionalThreshold = 0.5f
-            val inspectDecayAnimationSpec =
-                InspectSplineAnimationSpec(SplineBasedFloatDecayAnimationSpec(rule.density))
-            val decayAnimationSpec: DecayAnimationSpec<Float> =
-                inspectDecayAnimationSpec.generateDecayAnimationSpec()
             val tweenAnimationSpec = InspectSpringAnimationSpec(tween(easing = LinearEasing))
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = A,
-                    velocityThreshold = { velocityPx },
-                    positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
                     anchors =
                         DraggableAnchors {
                             A at 0f
                             B at 200f
                         },
-                    snapAnimationSpec = tweenAnimationSpec,
-                    decayAnimationSpec = decayAnimationSpec
+                    positionalThreshold = { it * positionalThreshold },
+                    snapAnimationSpec = tweenAnimationSpec
+                )
+
+            val flingBehavior =
+                createAnchoredDraggableFlingBehavior(
+                    state,
+                    rule.density,
+                    positionalThreshold = { it * positionalThreshold },
+                    snapAnimationSpec = tweenAnimationSpec
                 )
 
             val positionOfA = state.anchors.positionOf(A)
@@ -1303,26 +1138,19 @@
             val newOffset = positionOfA + delta
             assertThat(state.requireOffset()).isEqualTo(newOffset)
 
-            // velocity less than the velocityThreshold but enough to perform decay animation
-            val velocity = velocityPx * 0.9f
-            val projectedTarget = decayAnimationSpec.calculateTargetValue(newOffset, velocity)
-            assertThat(projectedTarget).isAtLeast(positionOfB)
-
             // target anchor did not change (still at A) since velocityThreshold and
             // positionalThreshold were not crossed.
-            state.settle(velocity)
+            performFling(flingBehavior, state, velocityPx * 0.9f)
+
             assertThat(state.currentValue).isEqualTo(A)
 
-            // assert that target animation is used, not decay animation because the target anchor
-            // is not in the same direction of the drag (sign of velocity)
-            assertThat(inspectDecayAnimationSpec.animationWasExecutions).isEqualTo(0)
             assertThat(tweenAnimationSpec.animationWasExecutions).isEqualTo(1)
         }
 
     @Test
-    fun anchoredDraggable_dragNotInTheSameDirectionOfTarget_positiveOffset_negativeVelocity() =
+    fun anchoredDraggable_performFling_positiveOffset_negativeVelocity() =
         runBlocking(AutoTestFrameClock()) {
-            val velocityPx = with(rule.density) { 1000.dp.toPx() }
+            val velocityPx = with(rule.density) { AnchoredDraggableMinFlingVelocity.toPx() }
             val positionalThreshold = 0.5f
             val inspectDecayAnimationSpec =
                 InspectSplineAnimationSpec(SplineBasedFloatDecayAnimationSpec(rule.density))
@@ -1330,17 +1158,23 @@
                 inspectDecayAnimationSpec.generateDecayAnimationSpec()
             val tweenAnimationSpec = InspectSpringAnimationSpec(tween(easing = LinearEasing))
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = B,
-                    velocityThreshold = { velocityPx },
-                    positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
                     anchors =
                         DraggableAnchors {
                             A at 0f
                             B at 200f
                         },
-                    snapAnimationSpec = tweenAnimationSpec,
-                    decayAnimationSpec = decayAnimationSpec
+                    positionalThreshold = { it * positionalThreshold },
+                    decayAnimationSpec = decayAnimationSpec,
+                    snapAnimationSpec = tweenAnimationSpec
+                )
+            val flingBehavior =
+                createAnchoredDraggableFlingBehavior(
+                    state,
+                    density = rule.density,
+                    positionalThreshold = { it * positionalThreshold },
+                    snapAnimationSpec = tweenAnimationSpec
                 )
 
             val positionOfA = state.anchors.positionOf(A)
@@ -1354,14 +1188,10 @@
             val newOffset = positionOfB + delta
             assertThat(state.requireOffset()).isEqualTo(newOffset)
 
-            // velocity less than the velocityThreshold but enough to perform decay animation
-            val velocity = -velocityPx * 0.9f
-            val projectedTarget = decayAnimationSpec.calculateTargetValue(newOffset, velocity)
-            assertThat(projectedTarget).isAtMost(positionOfA)
-
             // target anchor did not change (still at B) since velocityThreshold and
             // positionalThreshold were not crossed.
-            state.settle(velocity)
+            performFling(flingBehavior, state, -velocityPx * 0.9f)
+
             assertThat(state.currentValue).isEqualTo(B)
 
             // assert that target animation is used, not decay animation because the target anchor
@@ -1373,7 +1203,7 @@
     @Test
     fun anchoredDraggable_dragNotInTheSameDirectionOfTarget_negativeOffset_negativeVelocity() =
         runBlocking(AutoTestFrameClock()) {
-            val velocityPx = with(rule.density) { 500.dp.toPx() }
+            val velocityPx = with(rule.density) { AnchoredDraggableMinFlingVelocity.toPx() }
             val positionalThreshold = 0.5f
             val inspectDecayAnimationSpec =
                 InspectSplineAnimationSpec(SplineBasedFloatDecayAnimationSpec(rule.density))
@@ -1381,17 +1211,23 @@
                 inspectDecayAnimationSpec.generateDecayAnimationSpec()
             val tweenAnimationSpec = InspectSpringAnimationSpec(tween(easing = LinearEasing))
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = A,
-                    velocityThreshold = { velocityPx },
-                    positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
                     anchors =
                         DraggableAnchors {
                             A at 0f
                             B at -200f
                         },
-                    snapAnimationSpec = tweenAnimationSpec,
-                    decayAnimationSpec = decayAnimationSpec
+                    positionalThreshold = { it * positionalThreshold },
+                    decayAnimationSpec = decayAnimationSpec,
+                    snapAnimationSpec = tweenAnimationSpec
+                )
+            val flingBehavior =
+                createAnchoredDraggableFlingBehavior(
+                    state,
+                    rule.density,
+                    positionalThreshold = { it * positionalThreshold },
+                    snapAnimationSpec = tweenAnimationSpec
                 )
 
             val positionOfA = state.anchors.positionOf(A)
@@ -1405,14 +1241,10 @@
             val newOffset = positionOfA + delta
             assertThat(state.requireOffset()).isEqualTo(newOffset)
 
-            // velocity less than the velocityThreshold but enough to perform decay animation
-            val velocity = -velocityPx * 0.9f
-            val projectedTarget = decayAnimationSpec.calculateTargetValue(newOffset, velocity)
-            assertThat(projectedTarget).isAtMost(positionOfA)
-
             // target anchor did not change (still at A) since velocityThreshold and
             // positionalThreshold were not crossed.
-            state.settle(velocity)
+            performFling(flingBehavior, state, -velocityPx * 0.9f)
+
             assertThat(state.currentValue).isEqualTo(A)
 
             // assert that target animation is used, not decay animation because the target anchor
@@ -1424,7 +1256,7 @@
     @Test
     fun anchoredDraggable_dragNotInTheSameDirectionOfTarget_negativeOffset_positiveVelocity() =
         runBlocking(AutoTestFrameClock()) {
-            val velocityPx = with(rule.density) { 50.dp.toPx() }
+            val velocityPx = with(rule.density) { AnchoredDraggableMinFlingVelocity.toPx() }
             val positionalThreshold = 0.5f
             val inspectDecayAnimationSpec =
                 InspectSplineAnimationSpec(SplineBasedFloatDecayAnimationSpec(rule.density))
@@ -1432,17 +1264,23 @@
                 inspectDecayAnimationSpec.generateDecayAnimationSpec()
             val tweenAnimationSpec = InspectSpringAnimationSpec(tween(easing = LinearEasing))
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = B,
-                    velocityThreshold = { velocityPx },
-                    positionalThreshold = { totalDistance -> totalDistance * positionalThreshold },
                     anchors =
                         DraggableAnchors {
                             A at 0f
                             B at -200f
                         },
-                    snapAnimationSpec = tweenAnimationSpec,
-                    decayAnimationSpec = decayAnimationSpec
+                    positionalThreshold = { it * positionalThreshold },
+                    decayAnimationSpec = decayAnimationSpec,
+                    snapAnimationSpec = tweenAnimationSpec
+                )
+            val flingBehavior =
+                createAnchoredDraggableFlingBehavior(
+                    state,
+                    rule.density,
+                    positionalThreshold = { it * positionalThreshold },
+                    snapAnimationSpec = tweenAnimationSpec
                 )
 
             val positionOfA = state.anchors.positionOf(A)
@@ -1456,14 +1294,10 @@
             val newOffset = positionOfB + delta
             assertThat(state.requireOffset()).isEqualTo(newOffset)
 
-            // velocity less than the velocityThreshold but enough to perform decay animation
-            val velocity = velocityPx * 0.9f
-            val projectedTarget = decayAnimationSpec.calculateTargetValue(newOffset, velocity)
-            assertThat(projectedTarget).isAtMost(positionOfA)
-
             // target anchor did not change (still at B) since velocityThreshold and
             // positionalThreshold were not crossed.
-            state.settle(velocity)
+            performFling(flingBehavior, state, velocityPx * 0.9f)
+
             assertThat(state.currentValue).isEqualTo(B)
 
             // assert that target animation is used, not decay animation because the target anchor
@@ -1472,27 +1306,26 @@
             assertThat(tweenAnimationSpec.animationWasExecutions).isEqualTo(1)
         }
 
+    @Ignore("Clarify dispatchRawDelta contract b/344568351")
     @Test
     fun anchoredDraggable_settleWhenOffsetEqualsTargetOffset() =
         runBlocking(AutoTestFrameClock()) {
-            val inspectDecayAnimationSpec =
-                InspectSplineAnimationSpec(SplineBasedFloatDecayAnimationSpec(rule.density))
-            val decayAnimationSpec: DecayAnimationSpec<Float> =
-                inspectDecayAnimationSpec.generateDecayAnimationSpec()
             val tweenAnimationSpec = InspectSpringAnimationSpec(tween(easing = LinearEasing))
             val state =
-                AnchoredDraggableState(
+                createAnchoredDraggableState(
                     initialValue = A,
-                    positionalThreshold = defaultPositionalThreshold,
-                    velocityThreshold = defaultVelocityThreshold,
-                    snapAnimationSpec = tweenAnimationSpec,
-                    decayAnimationSpec = decayAnimationSpec,
                     anchors =
                         DraggableAnchors {
                             A at 0f
                             B at 250f
                         }
                 )
+            val flingBehavior =
+                createAnchoredDraggableFlingBehavior(
+                    state,
+                    rule.density,
+                    snapAnimationSpec = tweenAnimationSpec
+                )
 
             val positionA = state.anchors.positionOf(A)
             val positionB = state.anchors.positionOf(B)
@@ -1505,25 +1338,20 @@
 
             assertThat(state.offset).isEqualTo(positionB)
 
-            state.settle(velocity = 1000f)
+            performFling(flingBehavior, state, 1000f)
 
             // Assert that the component settled at positionB (anchor B)
             assertThat(state.offset).isEqualTo(positionB)
 
             // since offset == positionB, decay animation is used
-            assertThat(inspectDecayAnimationSpec.animationWasExecutions).isEqualTo(0)
             assertThat(tweenAnimationSpec.animationWasExecutions).isEqualTo(0)
         }
 
     @Test
     fun anchoredDraggable_animateTo_alreadyAtTarget_noOps() {
         val state =
-            AnchoredDraggableState(
+            createAnchoredDraggableState(
                 initialValue = B,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec,
                 anchors =
                     DraggableAnchors {
                         A at 0f
@@ -1535,7 +1363,7 @@
         val scope = CoroutineScope(clock)
 
         assertThat(state.offset).isEqualTo(200f)
-        scope.launch { state.animateTo(B) }
+        scope.launch { state.animateTo(B, DefaultSnapAnimationSpec) }
         runBlocking { clock.advanceByFrame() } // Advance only one frame, we should be done
         assertThat(state.offset).isEqualTo(200f)
     }
@@ -1543,12 +1371,8 @@
     @Test
     fun anchoredDraggable_animateToWithDecay_alreadyAtTarget_noOps() {
         val state =
-            AnchoredDraggableState(
+            createAnchoredDraggableState(
                 initialValue = B,
-                positionalThreshold = defaultPositionalThreshold,
-                velocityThreshold = defaultVelocityThreshold,
-                snapAnimationSpec = defaultAnimationSpec,
-                decayAnimationSpec = defaultDecayAnimationSpec,
                 anchors =
                     DraggableAnchors {
                         A at 0f
@@ -1560,7 +1384,14 @@
         val scope = CoroutineScope(clock)
 
         assertThat(state.offset).isEqualTo(200f)
-        scope.launch { state.animateToWithDecay(B, velocity = 100f) }
+        scope.launch {
+            state.animateToWithDecay(
+                targetValue = B,
+                velocity = 100f,
+                snapAnimationSpec = DefaultSnapAnimationSpec,
+                decayAnimationSpec = DefaultDecayAnimationSpec
+            )
+        }
         runBlocking { clock.advanceByFrame() } // Advance only one frame, we should be done
         assertThat(state.offset).isEqualTo(200f)
     }
@@ -1586,7 +1417,7 @@
                     .isEqualTo(expectedCurrentValue)
             }
         }
-        settle(0f)
+        settle(AnchoredDraggableDefaults.SnapAnimationSpec)
     }
 
     private suspend fun suspendIndefinitely() = suspendCancellableCoroutine<Unit> {}
@@ -1642,14 +1473,14 @@
         }
     }
 
-    private val defaultPositionalThreshold: (totalDistance: Float) -> Float = {
-        with(rule.density) { 56.dp.toPx() }
-    }
+    private val DefaultSnapAnimationSpec = tween<Float>()
 
-    private val defaultVelocityThreshold: () -> Float = { with(rule.density) { 125.dp.toPx() } }
-
-    private val defaultAnimationSpec = tween<Float>()
-
-    private val defaultDecayAnimationSpec: DecayAnimationSpec<Float> =
+    private val DefaultDecayAnimationSpec: DecayAnimationSpec<Float> =
         SplineBasedFloatDecayAnimationSpec(rule.density).generateDecayAnimationSpec()
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "testNewBehavior={0}")
+        fun params() = listOf(false, true)
+    }
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableTestState.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableTestState.kt
new file mode 100644
index 0000000..794c1ea
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableTestState.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 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.foundation.anchoredDraggable
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.DraggableAnchors
+
+@ExperimentalFoundationApi
+internal fun <T> AnchoredDraggableTestState(
+    initialValue: T,
+    anchors: DraggableAnchors<T>? = null,
+    confirmValueChange: (T) -> Boolean = { true }
+): AnchoredDraggableState<T> =
+    if (anchors != null) {
+        AnchoredDraggableState(
+            initialValue = initialValue,
+            confirmValueChange = confirmValueChange,
+            anchors = anchors
+        )
+    } else {
+        AnchoredDraggableState(
+            initialValue = initialValue,
+            confirmValueChange = confirmValueChange,
+        )
+    }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
index 6d5863c..3a8a68a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
@@ -22,21 +22,29 @@
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.AnimationState
 import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.FloatDecayAnimationSpec
 import androidx.compose.animation.core.animate
 import androidx.compose.animation.core.animateDecay
 import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.animation.core.exponentialDecay
+import androidx.compose.animation.core.generateDecayAnimationSpec
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
 import androidx.compose.foundation.OverscrollEffect
 import androidx.compose.foundation.gestures.DragEvent.DragDelta
+import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
+import androidx.compose.foundation.gestures.snapping.snapFlingBehavior
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.offset
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
@@ -46,11 +54,17 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ObserverModifierNode
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.node.observeReads
 import androidx.compose.ui.node.requireLayoutDirection
 import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.min
@@ -88,6 +102,9 @@
  * @param startDragImmediately when set to false, [draggable] will start dragging only when the
  *   gesture crosses the touchSlop. This is useful to prevent users from "catching" an animating
  *   widget when pressing on it. See [draggable] to learn more about startDragImmediately.
+ * @param flingBehavior Optionally configure how the anchored draggable performs the fling. By
+ *   default (if passing in null), this will snap to the closest anchor considering the velocity
+ *   thresholds and positional thresholds. See [AnchoredDraggableDefaults.flingBehavior].
  */
 @ExperimentalFoundationApi
 fun <T> Modifier.anchoredDraggable(
@@ -97,7 +114,8 @@
     enabled: Boolean = true,
     interactionSource: MutableInteractionSource? = null,
     overscrollEffect: OverscrollEffect? = null,
-    startDragImmediately: Boolean = state.isAnimationRunning
+    startDragImmediately: Boolean = state.isAnimationRunning,
+    flingBehavior: FlingBehavior? = null
 ): Modifier =
     this then
         AnchoredDraggableElement(
@@ -107,7 +125,8 @@
             reverseDirection = reverseDirection,
             interactionSource = interactionSource,
             overscrollEffect = overscrollEffect,
-            startDragImmediately = startDragImmediately
+            startDragImmediately = startDragImmediately,
+            flingBehavior = flingBehavior
         )
 
 /**
@@ -134,6 +153,9 @@
  * @param startDragImmediately when set to false, [draggable] will start dragging only when the
  *   gesture crosses the touchSlop. This is useful to prevent users from "catching" an animating
  *   widget when pressing on it. See [draggable] to learn more about startDragImmediately.
+ * @param flingBehavior Optionally configure how the anchored draggable performs the fling. By
+ *   default (if passing in null), this will snap to the closest anchor considering the velocity
+ *   thresholds and positional thresholds. See [AnchoredDraggableDefaults.flingBehavior].
  */
 @ExperimentalFoundationApi
 fun <T> Modifier.anchoredDraggable(
@@ -142,7 +164,8 @@
     enabled: Boolean = true,
     interactionSource: MutableInteractionSource? = null,
     overscrollEffect: OverscrollEffect? = null,
-    startDragImmediately: Boolean = state.isAnimationRunning
+    startDragImmediately: Boolean = state.isAnimationRunning,
+    flingBehavior: FlingBehavior? = null
 ): Modifier =
     this then
         AnchoredDraggableElement(
@@ -152,7 +175,8 @@
             reverseDirection = null,
             interactionSource = interactionSource,
             overscrollEffect = overscrollEffect,
-            startDragImmediately = startDragImmediately
+            startDragImmediately = startDragImmediately,
+            flingBehavior = flingBehavior
         )
 
 @OptIn(ExperimentalFoundationApi::class)
@@ -164,27 +188,30 @@
     private val interactionSource: MutableInteractionSource?,
     private val startDragImmediately: Boolean,
     private val overscrollEffect: OverscrollEffect?,
+    private val flingBehavior: FlingBehavior? = null,
 ) : ModifierNodeElement<AnchoredDraggableNode<T>>() {
     override fun create() =
         AnchoredDraggableNode(
-            state,
-            orientation,
-            enabled,
-            reverseDirection,
-            interactionSource,
-            overscrollEffect,
-            startDragImmediately,
+            state = state,
+            orientation = orientation,
+            enabled = enabled,
+            reverseDirection = reverseDirection,
+            interactionSource = interactionSource,
+            overscrollEffect = overscrollEffect,
+            startDragImmediately = startDragImmediately,
+            flingBehavior = flingBehavior
         )
 
     override fun update(node: AnchoredDraggableNode<T>) {
         node.update(
-            state,
-            orientation,
-            enabled,
-            reverseDirection,
-            interactionSource,
-            overscrollEffect,
-            startDragImmediately
+            state = state,
+            orientation = orientation,
+            enabled = enabled,
+            reverseDirection = reverseDirection,
+            interactionSource = interactionSource,
+            overscrollEffect = overscrollEffect,
+            startDragImmediately = startDragImmediately,
+            flingBehavior = flingBehavior
         )
     }
 
@@ -196,6 +223,7 @@
         result = 31 * result + interactionSource.hashCode()
         result = 31 * result + startDragImmediately.hashCode()
         result = 31 * result + overscrollEffect.hashCode()
+        result = 31 * result + flingBehavior.hashCode()
         return result
     }
 
@@ -211,6 +239,7 @@
         if (interactionSource != other.interactionSource) return false
         if (startDragImmediately != other.startDragImmediately) return false
         if (overscrollEffect != other.overscrollEffect) return false
+        if (flingBehavior != other.flingBehavior) return false
 
         return true
     }
@@ -224,6 +253,7 @@
         properties["interactionSource"] = interactionSource
         properties["startDragImmediately"] = startDragImmediately
         properties["overscrollEffect"] = overscrollEffect
+        properties["flingBehavior"] = flingBehavior
     }
 }
 
@@ -235,14 +265,19 @@
     private var reverseDirection: Boolean?,
     interactionSource: MutableInteractionSource?,
     private var overscrollEffect: OverscrollEffect?,
-    private var startDragImmediately: Boolean
+    private var startDragImmediately: Boolean,
+    private var flingBehavior: FlingBehavior?
 ) :
     DragGestureNode(
         canDrag = AlwaysDrag,
         enabled = enabled,
         interactionSource = interactionSource,
         orientationLock = orientation
-    ) {
+    ),
+    ObserverModifierNode {
+
+    lateinit var resolvedFlingBehavior: FlingBehavior
+    lateinit var density: Density
 
     private val isReverseDirection: Boolean
         get() =
@@ -253,6 +288,33 @@
                 else -> reverseDirection!!
             }
 
+    override fun onAttach() {
+        updateFlingBehavior(flingBehavior)
+    }
+
+    override fun onObservedReadsChanged() {
+        val newDensity = currentValueOf(LocalDensity)
+        if (density != newDensity) {
+            density = newDensity
+            updateFlingBehavior(flingBehavior)
+        }
+    }
+
+    private fun updateFlingBehavior(newFlingBehavior: FlingBehavior?) {
+        // Fall back to default fling behavior if the new fling behavior is null
+        this.resolvedFlingBehavior =
+            if (newFlingBehavior == null) {
+                // Only register for LocalDensity snapshot updates if we are creating a decay
+                observeReads { density = currentValueOf(LocalDensity) }
+                anchoredDraggableFlingBehavior(
+                    snapAnimationSpec = AnchoredDraggableDefaults.SnapAnimationSpec,
+                    positionalThreshold = AnchoredDraggableDefaults.PositionalThreshold,
+                    density = density,
+                    state = state
+                )
+            } else newFlingBehavior
+    }
+
     override suspend fun drag(forEachDelta: suspend ((dragDelta: DragDelta) -> Unit) -> Unit) {
         state.anchoredDrag {
             forEachDelta { dragDelta ->
@@ -279,17 +341,17 @@
         if (!isAttached) return
         coroutineScope.launch {
             if (overscrollEffect == null) {
-                state.settle(velocity.reverseIfNeeded().toFloat()).toVelocity()
+                fling(velocity.reverseIfNeeded().toFloat())
             } else {
                 overscrollEffect!!.applyToFling(velocity = velocity.reverseIfNeeded()) {
                     availableVelocity ->
-                    val consumed = state.settle(availableVelocity.toFloat()).toVelocity()
+                    val consumed = fling(velocity.reverseIfNeeded().toFloat())
                     val currentOffset = state.requireOffset()
                     val minAnchor = state.anchors.minAnchor()
                     val maxAnchor = state.anchors.maxAnchor()
                     // return consumed velocity only if we are reaching the min/max anchors
                     if (currentOffset >= maxAnchor || currentOffset <= minAnchor) {
-                        consumed
+                        consumed.toVelocity()
                     } else {
                         availableVelocity
                     }
@@ -298,6 +360,28 @@
         }
     }
 
+    private suspend fun fling(velocity: Float): Float =
+        if (state.usePreModifierChangeBehavior) {
+            @Suppress("DEPRECATION") state.settle(velocity)
+        } else {
+            var leftoverVelocity = velocity
+            state.anchoredDrag {
+                val scrollScope =
+                    object : ScrollScope {
+                        override fun scrollBy(pixels: Float): Float {
+                            val newOffset = state.newOffsetForDelta(pixels)
+                            val consumed = newOffset - state.offset
+                            dragTo(newOffset)
+                            return consumed
+                        }
+                    }
+                with(resolvedFlingBehavior) {
+                    leftoverVelocity = scrollScope.performFling(velocity)
+                }
+            }
+            leftoverVelocity
+        }
+
     override fun startDragImmediately(): Boolean = startDragImmediately
 
     fun update(
@@ -307,12 +391,15 @@
         reverseDirection: Boolean?,
         interactionSource: MutableInteractionSource?,
         overscrollEffect: OverscrollEffect?,
-        startDragImmediately: Boolean
+        startDragImmediately: Boolean,
+        flingBehavior: FlingBehavior?,
     ) {
-        var resetPointerInputHandling = false
+        this.flingBehavior = flingBehavior
 
+        var resetPointerInputHandling = false
         if (this.state != state) {
             this.state = state
+            updateFlingBehavior(flingBehavior)
             resetPointerInputHandling = true
         }
         if (this.orientation != orientation) {
@@ -469,7 +556,8 @@
 
 /**
  * State of the [anchoredDraggable] modifier. Use the constructor overload with anchors if the
- * anchors are defined in composition, or update the anchors using [updateAnchors].
+ * anchors are defined in composition, or update the anchors using
+ * [AnchoredDraggableState.updateAnchors].
  *
  * This contains necessary information about any ongoing drag or animation and provides methods to
  * change the state either immediately or by starting an animation.
@@ -483,19 +571,85 @@
  * @param velocityThreshold The velocity threshold (in px per second) that the end velocity has to
  *   exceed in order to animate to the next state, even if the [positionalThreshold] has not been
  *   reached.
+ * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@ExperimentalFoundationApi
+@Deprecated(ConfigurationMovedToModifier, level = DeprecationLevel.WARNING)
+fun <T> AnchoredDraggableState(
+    initialValue: T,
+    positionalThreshold: (totalDistance: Float) -> Float,
+    velocityThreshold: () -> Float,
+    snapAnimationSpec: AnimationSpec<Float>,
+    decayAnimationSpec: DecayAnimationSpec<Float>,
+    confirmValueChange: (newValue: T) -> Boolean = { true }
+): AnchoredDraggableState<T> =
+    AnchoredDraggableState(initialValue, confirmValueChange).apply {
+        this.positionalThreshold = positionalThreshold
+        this.velocityThreshold = velocityThreshold
+        @Suppress("DEPRECATION")
+        this.snapAnimationSpec = snapAnimationSpec
+        @Suppress("DEPRECATION")
+        this.decayAnimationSpec = decayAnimationSpec
+    }
+
+/**
+ * Construct an [AnchoredDraggableState] instance with anchors.
+ *
+ * @param initialValue The initial value of the state.
+ * @param anchors The anchors of the state. Use [AnchoredDraggableState.updateAnchors] to update the
+ *   anchors later.
  * @param snapAnimationSpec The default animation spec that will be used to animate to a new state.
  * @param decayAnimationSpec The animation spec that will be used when flinging with a large enough
  *   velocity to reach or cross the target state.
  * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
+ * @param positionalThreshold The positional threshold, in px, to be used when calculating the
+ *   target state while a drag is in progress and when settling after the drag ends. This is the
+ *   distance from the start of a transition. It will be, depending on the direction of the
+ *   interaction, added or subtracted from/to the origin offset. It should always be a positive
+ *   value.
+ * @param velocityThreshold The velocity threshold (in px per second) that the end velocity has to
+ *   exceed in order to animate to the next state, even if the [positionalThreshold] has not been
+ *   reached.
+ */
+@ExperimentalFoundationApi
+@Deprecated(ConfigurationMovedToModifier, level = DeprecationLevel.WARNING)
+fun <T> AnchoredDraggableState(
+    initialValue: T,
+    anchors: DraggableAnchors<T>,
+    positionalThreshold: (totalDistance: Float) -> Float,
+    velocityThreshold: () -> Float,
+    snapAnimationSpec: AnimationSpec<Float>,
+    decayAnimationSpec: DecayAnimationSpec<Float>,
+    confirmValueChange: (newValue: T) -> Boolean = { true }
+): AnchoredDraggableState<T> =
+    AnchoredDraggableState(
+            initialValue = initialValue,
+            anchors = anchors,
+            confirmValueChange = confirmValueChange
+        )
+        .apply {
+            this.positionalThreshold = positionalThreshold
+            this.velocityThreshold = velocityThreshold
+            @Suppress("DEPRECATION")
+            this.snapAnimationSpec = snapAnimationSpec
+            @Suppress("DEPRECATION")
+            this.decayAnimationSpec = decayAnimationSpec
+        }
+
+/**
+ * State of the [anchoredDraggable] modifier. Use the constructor overload with anchors if the
+ * anchors are defined in composition, or update the anchors using [updateAnchors].
+ *
+ * This contains necessary information about any ongoing drag or animation and provides methods to
+ * change the state either immediately or by starting an animation.
+ *
+ * @param initialValue The initial value of the state.
+ * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
  */
 @Stable
 @ExperimentalFoundationApi
 class AnchoredDraggableState<T>(
     initialValue: T,
-    internal val positionalThreshold: (totalDistance: Float) -> Float,
-    internal val velocityThreshold: () -> Float,
-    val snapAnimationSpec: AnimationSpec<Float>,
-    val decayAnimationSpec: DecayAnimationSpec<Float>,
     internal val confirmValueChange: (newValue: T) -> Boolean = { true }
 ) {
 
@@ -504,42 +658,37 @@
      *
      * @param initialValue The initial value of the state.
      * @param anchors The anchors of the state. Use [updateAnchors] to update the anchors later.
-     * @param snapAnimationSpec The default animation spec that will be used to animate to a new
-     *   state.
-     * @param decayAnimationSpec The animation spec that will be used when flinging with a large
-     *   enough velocity to reach or cross the target state.
      * @param confirmValueChange Optional callback invoked to confirm or veto a pending state
      *   change.
-     * @param positionalThreshold The positional threshold, in px, to be used when calculating the
-     *   target state while a drag is in progress and when settling after the drag ends. This is the
-     *   distance from the start of a transition. It will be, depending on the direction of the
-     *   interaction, added or subtracted from/to the origin offset. It should always be a positive
-     *   value.
-     * @param velocityThreshold The velocity threshold (in px per second) that the end velocity has
-     *   to exceed in order to animate to the next state, even if the [positionalThreshold] has not
-     *   been reached.
      */
     @ExperimentalFoundationApi
     constructor(
         initialValue: T,
         anchors: DraggableAnchors<T>,
-        positionalThreshold: (totalDistance: Float) -> Float,
-        velocityThreshold: () -> Float,
-        snapAnimationSpec: AnimationSpec<Float>,
-        decayAnimationSpec: DecayAnimationSpec<Float>,
         confirmValueChange: (newValue: T) -> Boolean = { true }
-    ) : this(
-        initialValue,
-        positionalThreshold,
-        velocityThreshold,
-        snapAnimationSpec,
-        decayAnimationSpec,
-        confirmValueChange
-    ) {
+    ) : this(initialValue, confirmValueChange) {
         this.anchors = anchors
         trySnapTo(initialValue)
     }
 
+    internal lateinit var positionalThreshold: (totalDistance: Float) -> Float
+    internal lateinit var velocityThreshold: () -> Float
+    @Deprecated(ConfigurationMovedToModifier, level = DeprecationLevel.WARNING)
+    lateinit var snapAnimationSpec: AnimationSpec<Float>
+        internal set
+
+    @Deprecated(ConfigurationMovedToModifier, level = DeprecationLevel.WARNING)
+    lateinit var decayAnimationSpec: DecayAnimationSpec<Float>
+        internal set
+
+    @Suppress("DEPRECATION")
+    internal val usePreModifierChangeBehavior: Boolean
+        get() =
+            ::positionalThreshold.isInitialized &&
+                ::velocityThreshold.isInitialized &&
+                ::snapAnimationSpec.isInitialized &&
+                ::decayAnimationSpec.isInitialized
+
     private val dragMutex = MutatorMutex()
 
     /**
@@ -694,26 +843,51 @@
     }
 
     /**
-     * Find the closest anchor, taking into account the [velocityThreshold] and
-     * [positionalThreshold], and settle at it with an animation.
+     * Find the closest anchor and settle at it with the given [animationSpec].
      *
-     * If the [velocity] is lower than the [velocityThreshold], the closest anchor by distance and
-     * [positionalThreshold] will be the target. If the [velocity] is higher than the
-     * [velocityThreshold], the [positionalThreshold] will <b>not</b> be considered and the next
-     * anchor in the direction indicated by the sign of the [velocity] will be the target.
+     * @param animationSpec The animation spec that will be used to animate to the closest anchor.
+     */
+    suspend fun settle(animationSpec: AnimationSpec<Float>) {
+        val previousValue = this.currentValue
+        val targetValue = anchors.closestAnchor(requireOffset())
+        if (targetValue != null && confirmValueChange(targetValue)) {
+            animateTo(targetValue, animationSpec)
+        } else {
+            // If the user vetoed the state change, rollback to the previous state.
+            animateTo(previousValue, animationSpec)
+        }
+    }
+
+    /**
+     * Find the closest anchor, taking into account the velocityThreshold and positionalThreshold,
+     * and settle at it with an animation.
      *
-     * Based on the [velocity], either [snapAnimationSpec] or [decayAnimationSpec] will be used to
+     * If the [velocity] is lower than the velocityThreshold, the closest anchor by distance and
+     * positionalThreshold will be the target. If the [velocity] is higher than the
+     * velocityThreshold, the positionalThreshold will <b>not</b> be considered and the next anchor
+     * in the direction indicated by the sign of the [velocity] will be the target.
+     *
+     * Based on the [velocity], either snapAnimationSpec or decayAnimationSpec will be used to
      * animate towards the target.
      *
      * @return The velocity consumed in the animation
      */
+    @Deprecated(SettleWithVelocityDeprecated, level = DeprecationLevel.WARNING)
     suspend fun settle(velocity: Float): Float {
+        require(usePreModifierChangeBehavior) {
+            "AnchoredDraggableState was configured through " +
+                "a constructor without providing positional and velocity threshold. This " +
+                "overload of settle has been deprecated. Please refer to " +
+                "AnchoredDraggableState#settle(animationSpec) for more information."
+        }
         val previousValue = this.currentValue
         val targetValue =
-            computeTarget(
-                offset = requireOffset(),
+            anchors.computeTarget(
+                currentOffset = requireOffset(),
                 currentValue = previousValue,
-                velocity = velocity
+                velocity = velocity,
+                positionalThreshold,
+                velocityThreshold
             )
         return if (confirmValueChange(targetValue)) {
             animateToWithDecay(targetValue, velocity)
@@ -723,27 +897,6 @@
         }
     }
 
-    private fun computeTarget(offset: Float, currentValue: T, velocity: Float): T {
-        val currentAnchors = anchors
-        val currentAnchorPosition = currentAnchors.positionOf(currentValue)
-        val velocityThresholdPx = velocityThreshold()
-        return if (currentAnchorPosition == offset || currentAnchorPosition.isNaN()) {
-            currentValue
-        } else {
-            if (abs(velocity) >= abs(velocityThresholdPx)) {
-                currentAnchors.closestAnchor(offset, sign(velocity) > 0)!!
-            } else {
-                val neighborAnchor =
-                    currentAnchors.closestAnchor(offset, offset - currentAnchorPosition > 0)!!
-                val neighborAnchorPosition = currentAnchors.positionOf(neighborAnchor)
-                val distance = abs(currentAnchorPosition - neighborAnchorPosition)
-                val relativeThreshold = abs(positionalThreshold(distance))
-                val relativePosition = abs(currentAnchorPosition - offset)
-                if (relativePosition <= relativeThreshold) currentValue else neighborAnchor
-            }
-        }
-    }
-
     private val anchoredDragScope =
         object : AnchoredDragScope {
             var leftBound: T? = null
@@ -887,6 +1040,12 @@
         }
     }
 
+    /**
+     * Calculate the new offset for a [delta] to ensure it is coerced in the bounds
+     *
+     * @param delta The delta to be added to the [offset]
+     * @return The coerced offset
+     */
     internal fun newOffsetForDelta(delta: Float) =
         ((if (offset.isNaN()) 0f else offset) + delta).coerceIn(
             anchors.minAnchor(),
@@ -928,23 +1087,38 @@
     companion object {
         /** The default [Saver] implementation for [AnchoredDraggableState]. */
         @ExperimentalFoundationApi
+        fun <T : Any> Saver(confirmValueChange: (T) -> Boolean = { true }) =
+            Saver<AnchoredDraggableState<T>, T>(
+                save = { it.currentValue },
+                restore = {
+                    AnchoredDraggableState(
+                        initialValue = it,
+                        confirmValueChange = confirmValueChange,
+                    )
+                }
+            )
+
+        /** The default [Saver] implementation for [AnchoredDraggableState]. */
+        @ExperimentalFoundationApi
+        @Deprecated(ConfigurationMovedToModifier, level = DeprecationLevel.WARNING)
+        @Suppress("DEPRECATION")
         fun <T : Any> Saver(
             snapAnimationSpec: AnimationSpec<Float>,
             decayAnimationSpec: DecayAnimationSpec<Float>,
             positionalThreshold: (distance: Float) -> Float,
             velocityThreshold: () -> Float,
             confirmValueChange: (T) -> Boolean = { true },
-        ) =
-            Saver<AnchoredDraggableState<T>, T>(
+        ): Saver<AnchoredDraggableState<T>, T> =
+            Saver(
                 save = { it.currentValue },
                 restore = {
                     AnchoredDraggableState(
                         initialValue = it,
-                        snapAnimationSpec = snapAnimationSpec,
-                        decayAnimationSpec = decayAnimationSpec,
                         confirmValueChange = confirmValueChange,
                         positionalThreshold = positionalThreshold,
-                        velocityThreshold = velocityThreshold
+                        velocityThreshold = velocityThreshold,
+                        snapAnimationSpec = snapAnimationSpec,
+                        decayAnimationSpec = decayAnimationSpec
                     )
                 }
             )
@@ -973,7 +1147,8 @@
     velocity: Float,
     anchoredDragScope: AnchoredDragScope,
     anchors: DraggableAnchors<T>,
-    latestTarget: T
+    latestTarget: T,
+    snapAnimationSpec: AnimationSpec<Float>,
 ) {
     with(anchoredDragScope) {
         val targetOffset = anchors.positionOf(latestTarget)
@@ -998,27 +1173,38 @@
  * offset.
  *
  * @param targetValue The target value of the animation
+ * @param animationSpec The animation spec used to perform the animation
  * @throws CancellationException if the interaction interrupted by another interaction like a
  *   gesture interaction or another programmatic interaction like a [animateTo] or [snapTo] call.
  */
 @ExperimentalFoundationApi
-suspend fun <T> AnchoredDraggableState<T>.animateTo(targetValue: T) {
+suspend fun <T> AnchoredDraggableState<T>.animateTo(
+    targetValue: T,
+    animationSpec: AnimationSpec<Float> =
+        if (usePreModifierChangeBehavior) {
+            @Suppress("DEPRECATION") this.snapAnimationSpec
+        } else AnchoredDraggableDefaults.SnapAnimationSpec
+) {
     anchoredDrag(targetValue = targetValue) { anchors, latestTarget ->
-        animateTo(lastVelocity, this, anchors, latestTarget)
+        animateTo(lastVelocity, this, anchors, latestTarget, animationSpec)
     }
 }
 
 /**
  * Attempt to animate using decay Animation to a [targetValue]. If the [velocity] is high enough to
- * get to the target offset, we'll use [AnchoredDraggableState.decayAnimationSpec] to get to that
- * offset and return the consumed velocity. If the [velocity] is not high enough, we'll use
- * [AnchoredDraggableState.snapAnimationSpec] to reach the target offset.
+ * get to the target offset, we'll use [decayAnimationSpec] to get to that offset and return the
+ * consumed velocity. If the [velocity] is not high enough, we'll use [snapAnimationSpec] to reach
+ * the target offset.
  *
  * If the [targetValue] is not in the set of anchors, [AnchoredDraggableState.currentValue] will be
  * updated ro the [targetValue] without updating the offset.
  *
  * @param targetValue The target value of the animation
  * @param velocity The velocity the animation should start with
+ * @param snapAnimationSpec The animation spec used if the velocity is not high enough to perform a
+ *   decay to the [targetValue] using the [decayAnimationSpec]
+ * @param decayAnimationSpec The animation spec used if the velocity is high enough to perform a
+ *   decay to the [targetValue]
  * @return The velocity consumed in the animation
  * @throws CancellationException if the interaction interrupted bt another interaction like a
  *   gesture interaction or another programmatic interaction like [animateTo] or [snapTo] call.
@@ -1027,6 +1213,14 @@
 suspend fun <T> AnchoredDraggableState<T>.animateToWithDecay(
     targetValue: T,
     velocity: Float,
+    snapAnimationSpec: AnimationSpec<Float> =
+        if (usePreModifierChangeBehavior) {
+            @Suppress("DEPRECATION") this.snapAnimationSpec
+        } else AnchoredDraggableDefaults.SnapAnimationSpec,
+    decayAnimationSpec: DecayAnimationSpec<Float> =
+        if (usePreModifierChangeBehavior) {
+            @Suppress("DEPRECATION") this.decayAnimationSpec
+        } else AnchoredDraggableDefaults.DecayAnimationSpec
 ): Float {
     var remainingVelocity = velocity
     anchoredDrag(targetValue = targetValue) { anchors, latestTarget ->
@@ -1040,7 +1234,7 @@
                 // will
                 // not consume any velocity.
                 if (velocity * (targetOffset - prev) < 0f || velocity == 0f) {
-                    animateTo(velocity, this, anchors, latestTarget)
+                    animateTo(velocity, this, anchors, latestTarget, snapAnimationSpec)
                     remainingVelocity = 0f
                 } else {
                     val projectedDecayOffset =
@@ -1082,7 +1276,7 @@
                             }
                         }
                     } else {
-                        animateTo(velocity, this, anchors, latestTarget)
+                        animateTo(velocity, this, anchors, latestTarget, snapAnimationSpec)
                         remainingVelocity = 0f
                     }
                 }
@@ -1092,6 +1286,97 @@
     return velocity - remainingVelocity
 }
 
+/**
+ * Compute the target anchor based on the [currentOffset], [velocity] and [positionalThreshold] and
+ * [velocityThreshold].
+ *
+ * @return The suggested target anchor
+ */
+@ExperimentalFoundationApi
+private fun <T> DraggableAnchors<T>.computeTarget(
+    currentOffset: Float,
+    currentValue: T,
+    velocity: Float,
+    positionalThreshold: (totalDistance: Float) -> Float,
+    velocityThreshold: () -> Float
+): T {
+    val currentAnchors = this
+    val currentAnchorPosition = currentAnchors.positionOf(currentValue)
+    val velocityThresholdPx = velocityThreshold()
+    return if (currentAnchorPosition == currentOffset || currentAnchorPosition.isNaN()) {
+        currentValue
+    } else {
+        if (abs(velocity) >= abs(velocityThresholdPx)) {
+            currentAnchors.closestAnchor(currentOffset, sign(velocity) > 0)!!
+        } else {
+            val neighborAnchor =
+                currentAnchors.closestAnchor(
+                    currentOffset,
+                    currentOffset - currentAnchorPosition > 0
+                )!!
+            val neighborAnchorPosition = currentAnchors.positionOf(neighborAnchor)
+            val distance = abs(currentAnchorPosition - neighborAnchorPosition)
+            val relativeThreshold = abs(positionalThreshold(distance))
+            val relativePosition = abs(currentAnchorPosition - currentOffset)
+            if (relativePosition <= relativeThreshold) currentValue else neighborAnchor
+        }
+    }
+}
+
+/**
+ * Contains useful defaults for use with [AnchoredDraggableState] and [Modifier.anchoredDraggable]
+ */
+@ExperimentalFoundationApi
+object AnchoredDraggableDefaults {
+
+    /** The default spec for snapping, a tween spec */
+    val SnapAnimationSpec: AnimationSpec<Float> = tween()
+
+    /** The default positional threshold, 50% of the distance */
+    val PositionalThreshold: (Float) -> Float = { distance -> distance / 2f }
+
+    /** The default spec for decaying, an exponential decay */
+    val DecayAnimationSpec: DecayAnimationSpec<Float> = exponentialDecay()
+
+    /**
+     * Create and remember a [TargetedFlingBehavior] for use with [Modifier.anchoredDraggable] that
+     * will find the target based on the velocity and [positionalThreshold] when performing a fling,
+     * and settle to that target.
+     *
+     * There are two paths: a) If the velocity is bigger than [AnchoredDraggableMinFlingVelocity]
+     * (125 dp/s), the fling behavior will move to the next closest anchor in the fling direction,
+     * determined by the sign of the fling velocity. b) If the velocity is smaller than
+     * [AnchoredDraggableMinFlingVelocity] (125 dp/s), the fling behavior will consider the
+     * positional thresholds, by default [AnchoredDraggableDefaults.PositionalThreshold]. If the
+     * offset has crossed the threshold when performing the fling, the fling behavior will move to
+     * the next anchor in the fling direction. Otherwise, it will move back to the previous anchor.
+     *
+     * @param state The state the fling will be performed on
+     * @param positionalThreshold The positional threshold, in px, to be used when calculating the
+     *   target state while a drag is in progress and when settling after the drag ends. This is the
+     *   distance from the start of a transition. It will be, depending on the direction of the
+     *   interaction, added or subtracted from/to the origin offset. It should always be a positive
+     *   value.
+     * @param animationSpec The animation spec used to perform the settling
+     */
+    @Composable
+    fun <T> flingBehavior(
+        state: AnchoredDraggableState<T>,
+        positionalThreshold: (totalDistance: Float) -> Float = PositionalThreshold,
+        animationSpec: AnimationSpec<Float> = SnapAnimationSpec
+    ): TargetedFlingBehavior {
+        val density = LocalDensity.current
+        return remember(density, state, positionalThreshold, animationSpec) {
+            anchoredDraggableFlingBehavior(
+                state = state,
+                density = density,
+                positionalThreshold = positionalThreshold,
+                snapAnimationSpec = animationSpec
+            )
+        }
+    }
+}
+
 private fun Float.coerceToTarget(target: Float): Float {
     if (target == 0f) return 0f
     return if (target > 0) coerceAtMost(target) else coerceAtLeast(target)
@@ -1206,6 +1491,91 @@
     return maxValue
 }
 
+internal val AnchoredDraggableMinFlingVelocity = 125.dp
+
+private const val ConfigurationMovedToModifier =
+    "This constructor of " +
+        "AnchoredDraggableState has been deprecated. Please pass thresholds and animation specs to " +
+        "anchoredDraggableFlingBehavior(..) instead, which can be passed to Modifier.anchoredDraggable."
+private const val SettleWithVelocityDeprecated =
+    "settle does not accept a velocity anymore. " +
+        "Please use FlingBehavior#performFling instead. See AnchoredDraggableSamples.kt for example " +
+        "usages."
+
+/**
+ * Construct a [FlingBehavior] for use with [Modifier.anchoredDraggable].
+ *
+ * @param state The [AnchoredDraggableState] that will be used for the fling animation
+ * @param positionalThreshold A positional threshold that needs to be crossed in order to reach the
+ *   next anchor when flinging, in pixels. This can be a derived from the distance that the lambda
+ *   is invoked with.
+ * @param snapAnimationSpec The animation spec that will be used to snap to a new state.
+ */
+@ExperimentalFoundationApi
+internal fun <T> anchoredDraggableFlingBehavior(
+    state: AnchoredDraggableState<T>,
+    density: Density,
+    positionalThreshold: (totalDistance: Float) -> Float,
+    snapAnimationSpec: AnimationSpec<Float>
+): TargetedFlingBehavior =
+    snapFlingBehavior(
+        decayAnimationSpec = NoOpDecayAnimationSpec,
+        snapAnimationSpec = snapAnimationSpec,
+        snapLayoutInfoProvider =
+            AnchoredDraggableLayoutInfoProvider(
+                state = state,
+                positionalThreshold = positionalThreshold,
+                velocityThreshold = { with(density) { 125.dp.toPx() } }
+            )
+    )
+
+@OptIn(ExperimentalFoundationApi::class)
+private fun <T> AnchoredDraggableLayoutInfoProvider(
+    state: AnchoredDraggableState<T>,
+    positionalThreshold: (totalDistance: Float) -> Float,
+    velocityThreshold: () -> Float
+): SnapLayoutInfoProvider =
+    object : SnapLayoutInfoProvider {
+
+        // We never decay in AnchoredDraggable's fling
+        override fun calculateApproachOffset(velocity: Float, decayOffset: Float) = 0f
+
+        override fun calculateSnapOffset(velocity: Float): Float {
+            val currentOffset = state.requireOffset()
+            val target =
+                state.anchors.computeTarget(
+                    currentOffset = currentOffset,
+                    currentValue = state.currentValue,
+                    velocity = velocity,
+                    positionalThreshold = positionalThreshold,
+                    velocityThreshold = velocityThreshold
+                )
+            return state.anchors.positionOf(target) - currentOffset
+        }
+    }
+
+private val NoOpDecayAnimationSpec: DecayAnimationSpec<Float> =
+    object : FloatDecayAnimationSpec {
+            override val absVelocityThreshold = 0f
+
+            override fun getValueFromNanos(
+                playTimeNanos: Long,
+                initialValue: Float,
+                initialVelocity: Float
+            ) = 0f
+
+            override fun getDurationNanos(initialValue: Float, initialVelocity: Float) = 0L
+
+            override fun getVelocityFromNanos(
+                playTimeNanos: Long,
+                initialValue: Float,
+                initialVelocity: Float
+            ) = 0f
+
+            override fun getTargetValue(initialValue: Float, initialVelocity: Float) = 0f
+        }
+        .generateDecayAnimationSpec()
+
 private const val DEBUG = false
 
 private inline fun debugLog(generateMsg: () -> String) {