Swipeable
is a Compose Material API that helps you build components that
can be swiped between discrete states, such as bottom sheets, drawers, or
swipe-to-dismiss. To better support advanced use cases, such as anchors that
depend on the size of a component, a successor was published in
Compose-Foundation 1.6.0-alpha01: AnchoredDraggable
. AnchoredDraggable
is a Foundation API for building draggable components with anchored states, such
as bottom sheets, drawers, or swipe-to-dismiss.
Material's Swipeable
APIs have been deprecated in favor of Foundation's
AnchoredDraggable
and will be removed in a future release. This guide
describes how to migrate from Swipeable
APIs to AnchoredDraggable
.
Migrate SwipeableState
to AnchoredDraggableState
Start by identifying changes to your state holder. AnchoredDraggableState
cannot be inherited from, and the offset is represented as Float.NaN
before it
is initialized.
Update your state holder
AnchoredDraggableState
is a final class, meaning it cannot be inherited
from. If your existing component inherits from SwipeableState
, update
your state holder to hold a reference to the AnchoredDraggableState
instead of
inheriting from it:
Swipeable
class MySwitchState: SwipeableState()
AnchoredDraggable
class MySwitchState {
private val anchoredDraggableState = AnchoredDraggableState(...)
}
Since your state holder does not inherit from SwipeableState
anymore, you
might have to expose APIs yourself. The most common APIs you can use are
offset
, progress
, currentValue
and targetValue
.
Access the offset
Unlike in Swipeable
, AnchoredDraggableState
's offset
is Float.NaN
before
it is initialized. In AnchoredDraggable
, the anchors can be passed to
AnchoredDraggableState
's constructor or updated through
AnchoredDraggableState#updateAnchors
. Passing the anchors to
AnchoredDraggableState
's constructor initializes the offset immediately.
If your anchors depend on layout or could change, use
AnchoredDraggableState#updateAnchors
to avoid recreating the state when the
anchors change.
If you use updateAnchors
, the offset will be Float.NaN
before passing the
anchors to updateAnchors
. To avoid accidentally passing Float.NaN
to
components, use AnchoredDraggableState#requireOffset
to require that the
offset has been initialized when reading it. This helps you catch
inconsistencies or possible bugs early on.
@Composable
fun AnchoredDraggableBox() {
val state = remember { AnchoredDraggableState(...) }
val density = LocalDensity.current
val anchors = remember { DraggableAnchors { ... } }
SideEffect {
state.updateAnchors(anchors)
}
Box(
Modifier.offset { IntOffset(x = state.requireOffset(), y = 0) }
}
}
Migrate Modifier.swipeable
to Modifier.anchoredDraggable
Modifier.anchoredDraggable()
replaces Modifier.swipeable
. Some
of Modifier.swipeable()
's parameters have moved to AnchoredDraggableState
directly, as described in the following sections.
Define anchors
Define the anchors using the DraggableAnchors
builder method. Then, pass
them to AnchoredDraggableState#updateAnchors
or AnchoredDraggableState
's
constructor:
Constructor
enum class DragValue { Start, Center, End }
@Composable
fun AnchoredDraggableBox() {
val anchors = DraggableAnchors {
Start at -100.dp.toPx()
Center at 0f
End at 100.dp.toPx()
}
val state = remember {
AnchoredDraggableState(anchors = anchors)
}
Box(
Modifier.offset { IntOffset(x = state.requireOffset(), y = 0) }
)
}
updateAnchors
enum class DragValue { Start, Center, End }
@Composable
fun AnchoredDraggableBox() {
val state = remember { AnchoredDraggableState(...) }
val density = LocalDensity.current
val anchors = with (density) {
DraggableAnchors {
Start at -100.dp.toPx()
Center at 0f
End at 100.dp.toPx()
}
}
SideEffect {
state.updateAnchors(anchors)
}
Box(
Modifier.offset { IntOffset(x = state.requireOffset(), y = 0) }
)
}
If the anchors are static, pass them to the constructor. If they depend on
layout, or are not static, use updateAnchors
.
Define positional thresholds
The type and name of the thresholds parameter has changed. Instead of having a
separate ThresholdConfig
interface, AnchoredDraggableState
has a
positionalThreshold
parameter that takes a lambda function that returns the
position of the threshold. For example, a positional threshold of 50% could be
expressed as:
val anchoredDraggableState = AnchoredDraggableState(
positionalThreshold = { distance -> distance * 0.5f },
...
)
A positional threshold of 56dp
could be expressed as:
val density = LocalDensity.current
val anchoredDraggableState = AnchoredDraggableState(
positionalThreshold = { with(density) { 56.dp.toPx() } },
...
)
Define velocity thresholds
Velocity thresholds are also passed to AnchoredDraggableState
's constructor,
and also expressed as a lambda:
val density = LocalDensity.current
val anchoredDraggableState = AnchoredDraggableState(
velocityThreshold = { with(density) { 125.dp.toPx() } },
...
)
Changes to the API surface
Find an overview of changes to the API surface below.
AnchoredDraggableState
|
|
---|---|
|
|
|
|
|
|
|
|
|
|
|
N/A |
|
|
|
|
|
|
|
|
|
|
|
Modifier.anchoredDraggable
|
|
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
Passed to |
|
Not yet supported. See b/288084801 for the latest status. |
|
Passed to |