AnchoredDraggableState


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.

Summary

Public companion functions

Saver<AnchoredDraggableState<T>, T>
<T : Any> Saver()

The default Saver implementation for AnchoredDraggableState.

Cmn
Saver<AnchoredDraggableState<T>, T>
<T : Any> Saver(confirmValueChange: (T) -> Boolean)

This function is deprecated. confirmValueChange is deprecated without replacement.

Cmn
Saver<AnchoredDraggableState<T>, T>
<T : Any> Saver(
    snapAnimationSpec: AnimationSpec<Float>,
    decayAnimationSpec: DecayAnimationSpec<Float>,
    positionalThreshold: (distance: Float) -> Float,
    velocityThreshold: () -> Float,
    confirmValueChange: (T) -> Boolean
)

This function is deprecated. This constructor of AnchoredDraggableState has been deprecated.

Cmn

Public constructors

<T : Any?> AnchoredDraggableState(initialValue: T)
Cmn
<T : Any?> AnchoredDraggableState(
    initialValue: T,
    anchors: DraggableAnchors<T>
)

Construct an AnchoredDraggableState instance with anchors.

Cmn
<T : Any?> AnchoredDraggableState(
    initialValue: T,
    confirmValueChange: (newValue) -> Boolean
)

This function is deprecated. confirmValueChange is deprecated without replacement.

Cmn
<T : Any?> AnchoredDraggableState(
    initialValue: T,
    anchors: DraggableAnchors<T>,
    confirmValueChange: (newValue) -> Boolean
)

This function is deprecated. confirmValueChange is deprecated without replacement.

Cmn

Public functions

suspend Unit
anchoredDrag(
    dragPriority: MutatePriority,
    block: suspend AnchoredDragScope.(anchors: DraggableAnchors<T>) -> Unit
)

Call this function to take control of drag logic and perform anchored drag with the latest anchors.

Cmn
suspend Unit
anchoredDrag(
    targetValue: T,
    dragPriority: MutatePriority,
    block: suspend AnchoredDragScope.(anchor: DraggableAnchors<T>, targetValue) -> Unit
)

Call this function to take control of drag logic and perform anchored drag with the latest anchors and target.

Cmn
Float

Drag by the delta, coerce it in the bounds and dispatch it to the AnchoredDraggableState.

Cmn
@FloatRange(from = 0.0, to = 1.0) Float
progress(from: T, to: T)

The fraction of the offset between from and to, as a fraction between 0f..1f, or 1f if from is equal to to.

Cmn
Float

Require the current offset.

Cmn
suspend Unit
settle(animationSpec: AnimationSpec<Float>)

Find the closest anchor and settle at it with the given animationSpec.

Cmn
suspend Float
settle(velocity: Float)

This function is deprecated. settle does not accept a velocity anymore.

Cmn
Unit
updateAnchors(newAnchors: DraggableAnchors<T>, newTarget: T)

Update the anchors.

Cmn

Public properties

DraggableAnchors<T>
Cmn
T

The current value of the AnchoredDraggableState.

Cmn
lateinit DecayAnimationSpec<Float>

This property is deprecated. This constructor of AnchoredDraggableState has been deprecated.

Cmn
Boolean

Whether an animation is currently in progress.

Cmn
Float

The velocity of the last known animation.

Cmn
Float

The current offset, or Float.NaN if it has not been initialized yet.

Cmn
Float

This property is deprecated. Use the progress function to query the progress between two specified anchors.

Cmn
T

The value the AnchoredDraggableState is currently settled at.

Cmn
lateinit AnimationSpec<Float>

This property is deprecated. This constructor of AnchoredDraggableState has been deprecated.

Cmn
T

The target value.

Cmn

Extension functions

suspend Unit
<T : Any?> AnchoredDraggableState<T>.animateTo(
    targetValue: T,
    animationSpec: AnimationSpec<Float>
)

Animate to a targetValue.

Cmn
suspend Float
<T : Any?> AnchoredDraggableState<T>.animateToWithDecay(
    targetValue: T,
    velocity: Float,
    snapAnimationSpec: AnimationSpec<Float>,
    decayAnimationSpec: DecayAnimationSpec<Float>
)

Attempt to animate using decay Animation to a targetValue.

Cmn
suspend Unit
<T : Any?> AnchoredDraggableState<T>.snapTo(targetValue: T)

Snap to a targetValue without any animation.

Cmn

Public companion functions

Saver

fun <T : Any> Saver(): Saver<AnchoredDraggableState<T>, T>

The default Saver implementation for AnchoredDraggableState.

Saver

fun <T : Any> Saver(confirmValueChange: (T) -> Boolean = { true }): Saver<AnchoredDraggableState<T>, T>

The default Saver implementation for AnchoredDraggableState.

Saver

fun <T : Any> Saver(
    snapAnimationSpec: AnimationSpec<Float>,
    decayAnimationSpec: DecayAnimationSpec<Float>,
    positionalThreshold: (distance: Float) -> Float,
    velocityThreshold: () -> Float,
    confirmValueChange: (T) -> Boolean = { true }
): Saver<AnchoredDraggableState<T>, T>

The default Saver implementation for AnchoredDraggableState.

Public constructors

AnchoredDraggableState

<T : Any?> AnchoredDraggableState(initialValue: T)
Parameters
initialValue: T

The initial value of the state.

AnchoredDraggableState

<T : Any?> AnchoredDraggableState(
    initialValue: T,
    anchors: DraggableAnchors<T>
)

Construct an AnchoredDraggableState instance with anchors.

Parameters
initialValue: T

The initial value of the state.

anchors: DraggableAnchors<T>

The anchors of the state. Use updateAnchors to update the anchors later.

AnchoredDraggableState

<T : Any?> AnchoredDraggableState(
    initialValue: T,
    confirmValueChange: (newValue) -> Boolean
)

Construct an AnchoredDraggableState instance with anchors.

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.offset
import androidx.compose.foundation.layout.width
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset

val open = "Open"
val closed = "Closed"

@Composable
fun DrawerLayout(
    state: AnchoredDraggableState<String>,
    activePositions: List<String> = listOf(open, closed),
    modifier: Modifier = Modifier,
    drawerContent: @Composable () -> Unit,
    content: @Composable () -> Unit
) {
    Box(modifier) {
        Box(Modifier.anchoredDraggable(state, Orientation.Horizontal)) { content() }
        Box(
            Modifier.onSizeChanged { measuredSize ->
                    state.updateAnchors(
                        DraggableAnchors {
                            if (closed in activePositions) {
                                closed at -measuredSize.width.toFloat()
                            }
                            if (open in activePositions) {
                                open at 0f
                            }
                        }
                    )
                }
                .offset { IntOffset(x = state.requireOffset().roundToInt(), y = 0) }
        ) {
            drawerContent()
        }
    }
}

val state =
    rememberSaveable(saver = AnchoredDraggableState.Saver()) {
        AnchoredDraggableState(initialValue = closed)
    }
val activePositions = remember { mutableStateListOf(open, closed) }
DrawerLayout(
    state,
    activePositions,
    drawerContent = {
        Button(
            onClick = {
                if (closed in activePositions) {
                    activePositions.remove(closed)
                } else {
                    activePositions.add(closed)
                }
            }
        ) {
            val text =
                if (closed in activePositions) {
                    "Click to disallow closing drawer"
                } else {
                    "Click to allow closing"
                }
            Text(text)
        }
    },
) {
    Text("Swipe to expand Drawer")
}

For an example of using dynamic anchors to replace confirmValueChange.

Parameters
initialValue: T

The initial value of the state.

confirmValueChange: (newValue) -> Boolean

Optional callback invoked to confirm or veto a pending state change.

AnchoredDraggableState

<T : Any?> AnchoredDraggableState(
    initialValue: T,
    anchors: DraggableAnchors<T>,
    confirmValueChange: (newValue) -> Boolean = { true }
)

Construct an AnchoredDraggableState instance with anchors.

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.offset
import androidx.compose.foundation.layout.width
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset

val open = "Open"
val closed = "Closed"

@Composable
fun DrawerLayout(
    state: AnchoredDraggableState<String>,
    activePositions: List<String> = listOf(open, closed),
    modifier: Modifier = Modifier,
    drawerContent: @Composable () -> Unit,
    content: @Composable () -> Unit
) {
    Box(modifier) {
        Box(Modifier.anchoredDraggable(state, Orientation.Horizontal)) { content() }
        Box(
            Modifier.onSizeChanged { measuredSize ->
                    state.updateAnchors(
                        DraggableAnchors {
                            if (closed in activePositions) {
                                closed at -measuredSize.width.toFloat()
                            }
                            if (open in activePositions) {
                                open at 0f
                            }
                        }
                    )
                }
                .offset { IntOffset(x = state.requireOffset().roundToInt(), y = 0) }
        ) {
            drawerContent()
        }
    }
}

val state =
    rememberSaveable(saver = AnchoredDraggableState.Saver()) {
        AnchoredDraggableState(initialValue = closed)
    }
val activePositions = remember { mutableStateListOf(open, closed) }
DrawerLayout(
    state,
    activePositions,
    drawerContent = {
        Button(
            onClick = {
                if (closed in activePositions) {
                    activePositions.remove(closed)
                } else {
                    activePositions.add(closed)
                }
            }
        ) {
            val text =
                if (closed in activePositions) {
                    "Click to disallow closing drawer"
                } else {
                    "Click to allow closing"
                }
            Text(text)
        }
    },
) {
    Text("Swipe to expand Drawer")
}

For an example of using dynamic anchors to replace confirmValueChange.

Parameters
initialValue: T

The initial value of the state.

anchors: DraggableAnchors<T>

The anchors of the state. Use updateAnchors to update the anchors later.

confirmValueChange: (newValue) -> Boolean = { true }

Optional callback invoked to confirm or veto a pending state change.

Public functions

anchoredDrag

suspend fun anchoredDrag(
    dragPriority: MutatePriority = MutatePriority.Default,
    block: suspend AnchoredDragScope.(anchors: DraggableAnchors<T>) -> Unit
): Unit

Call this function to take control of drag logic and perform anchored drag with the latest anchors.

All actions that change the offset of this AnchoredDraggableState must be performed within an anchoredDrag block (even if they don't call any other methods on this object) in order to guarantee that mutual exclusion is enforced.

If anchoredDrag is called from elsewhere with the dragPriority higher or equal to ongoing drag, the ongoing drag will be cancelled.

If the anchors change while the block is being executed, it will be cancelled and re-executed with the latest anchors and target. This allows you to target the correct state.

Parameters
dragPriority: MutatePriority = MutatePriority.Default

of the drag operation

block: suspend AnchoredDragScope.(anchors: DraggableAnchors<T>) -> Unit

perform anchored drag given the current anchor provided

anchoredDrag

suspend fun anchoredDrag(
    targetValue: T,
    dragPriority: MutatePriority = MutatePriority.Default,
    block: suspend AnchoredDragScope.(anchor: DraggableAnchors<T>, targetValue) -> Unit
): Unit

Call this function to take control of drag logic and perform anchored drag with the latest anchors and target.

All actions that change the offset of this AnchoredDraggableState must be performed within an anchoredDrag block (even if they don't call any other methods on this object) in order to guarantee that mutual exclusion is enforced.

This overload allows the caller to hint the target value that this anchoredDrag is intended to arrive to. This will set AnchoredDraggableState.targetValue to provided value so consumers can reflect it in their UIs.

If the anchors or AnchoredDraggableState.targetValue change while the block is being executed, it will be cancelled and re-executed with the latest anchors and target. This allows you to target the correct state.

If anchoredDrag is called from elsewhere with the dragPriority higher or equal to ongoing drag, the ongoing drag will be cancelled.

Parameters
targetValue: T

hint the target value that this anchoredDrag is intended to arrive to

dragPriority: MutatePriority = MutatePriority.Default

of the drag operation

block: suspend AnchoredDragScope.(anchor: DraggableAnchors<T>, targetValue) -> Unit

perform anchored drag given the current anchor provided

dispatchRawDelta

fun dispatchRawDelta(delta: Float): Float

Drag by the delta, coerce it in the bounds and dispatch it to the AnchoredDraggableState.

Returns
Float

The delta the consumed by the AnchoredDraggableState

progress

fun progress(from: T, to: T): @FloatRange(from = 0.0, to = 1.0) Float

The fraction of the offset between from and to, as a fraction between 0f..1f, or 1f if from is equal to to.

Parameters
from: T

The starting value used to calculate the distance

to: T

The end value used to calculate the distance

requireOffset

fun requireOffset(): Float

Require the current offset.

Throws
kotlin.IllegalStateException

If the offset has not been initialized yet

See also
offset

settle

suspend fun settle(animationSpec: AnimationSpec<Float>): Unit

Find the closest anchor and settle at it with the given animationSpec.

Parameters
animationSpec: AnimationSpec<Float>

The animation spec that will be used to animate to the closest anchor.

settle

suspend fun settle(velocity: Float): Float

Find the closest anchor, taking into account the velocityThreshold and positionalThreshold, and settle at it with an animation.

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 not 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.

Returns
Float

The velocity consumed in the animation

updateAnchors

fun updateAnchors(
    newAnchors: DraggableAnchors<T>,
    newTarget: T = if (!offset.isNaN()) { newAnchors.closestAnchor(offset) ?: targetValue } else targetValue
): Unit

Update the anchors. If there is no ongoing anchoredDrag operation, snap to the newTarget, otherwise restart the ongoing anchoredDrag operation (e.g. an animation) with the new anchors.

If your anchors depend on the size of the layout, updateAnchors should be called in the layout (placement) phase, e.g. through Modifier.onSizeChanged. This ensures that the state is set up within the same frame. For static anchors, or anchors with different data dependencies, updateAnchors is safe to be called from side effects or layout.

Parameters
newAnchors: DraggableAnchors<T>

The new anchors.

newTarget: T = if (!offset.isNaN()) { newAnchors.closestAnchor(offset) ?: targetValue } else targetValue

The new target, by default the closest anchor or the current target if there are no anchors.

Public properties

anchors

val anchorsDraggableAnchors<T>

currentValue

val currentValue: T

The current value of the AnchoredDraggableState.

That is the closest anchor point that the state has passed through.

decayAnimationSpec

lateinit val decayAnimationSpecDecayAnimationSpec<Float>

isAnimationRunning

val isAnimationRunningBoolean

Whether an animation is currently in progress.

lastVelocity

val lastVelocityFloat

The velocity of the last known animation. Gets reset to 0f when an animation completes successfully, but does not get reset when an animation gets interrupted. You can use this value to provide smooth reconciliation behavior when re-targeting an animation.

offset

val offsetFloat

The current offset, or Float.NaN if it has not been initialized yet.

The offset will be initialized when the anchors are first set through updateAnchors.

Strongly consider using requireOffset which will throw if the offset is read before it is initialized. This helps catch issues early in your workflow.

progress

val progressFloat

The fraction of the progress going from settledValue to targetValue, within 0f..1f bounds, or 1f if the AnchoredDraggableState is in a settled state.

settledValue

val settledValue: T

The value the AnchoredDraggableState is currently settled at.

When progressing through multiple anchors, e.g. A -> B -> C, settledValue will stay the same until settled at an anchor, while currentValue will update to the closest anchor.

snapAnimationSpec

lateinit val snapAnimationSpecAnimationSpec<Float>

targetValue

val targetValue: T

The target value. This is the closest value to the current offset. If no interactions like animations or drags are in progress, this will be the current value.

Extension functions

suspend fun <T : Any?> AnchoredDraggableState<T>.animateTo(
    targetValue: T,
    animationSpec: AnimationSpec<Float> = if (usePreModifierChangeBehavior) { @Suppress("DEPRECATION") this.snapAnimationSpec } else AnchoredDraggableDefaults.SnapAnimationSpec
): Unit

Animate to a targetValue. If the targetValue is not in the set of anchors, the AnchoredDraggableState.currentValue will be updated to the targetValue without updating the offset.

Parameters
targetValue: T

The target value of the animation

animationSpec: AnimationSpec<Float> = if (usePreModifierChangeBehavior) { @Suppress("DEPRECATION") this.snapAnimationSpec } else AnchoredDraggableDefaults.SnapAnimationSpec

The animation spec used to perform the animation

Throws
kotlinx.coroutines.CancellationException

if the interaction interrupted by another interaction like a gesture interaction or another programmatic interaction like a animateTo or snapTo call.

animateToWithDecay

suspend fun <T : Any?> 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

Attempt to animate using decay Animation to a targetValue. If the velocity is high enough to 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.

Parameters
targetValue: T

The target value of the animation

velocity: Float

The velocity the animation should start with, in px/s

snapAnimationSpec: AnimationSpec<Float> = if (usePreModifierChangeBehavior) { @Suppress("DEPRECATION") this.snapAnimationSpec } else AnchoredDraggableDefaults.SnapAnimationSpec

The animation spec used if the velocity is not high enough to perform a decay to the targetValue using the decayAnimationSpec

decayAnimationSpec: DecayAnimationSpec<Float> = if (usePreModifierChangeBehavior) { @Suppress("DEPRECATION") this.decayAnimationSpec } else AnchoredDraggableDefaults.DecayAnimationSpec

The animation spec used if the velocity is high enough to perform a decay to the targetValue

Returns
Float

The velocity consumed in the animation

Throws
kotlinx.coroutines.CancellationException

if the interaction interrupted bt another interaction like a gesture interaction or another programmatic interaction like animateTo or snapTo call.

suspend fun <T : Any?> AnchoredDraggableState<T>.snapTo(targetValue: T): Unit

Snap to a targetValue without any animation. If the targetValue is not in the set of anchors, the AnchoredDraggableState.currentValue will be updated to the targetValue without updating the offset.

Parameters
targetValue: T

The target value of the animation

Throws
kotlinx.coroutines.CancellationException

if the interaction interrupted by another interaction like a gesture interaction or another programmatic interaction like a animateTo or snapTo call.