Cómo migrar de Swipeable a AnchoredDraggable

Swipeable es una API de Material de Compose que te ayuda a compilar componentes que se pueden deslizar entre estados discretos, como hojas inferiores, paneles laterales o deslizar para descartar. Para admitir mejor los casos de uso avanzados, como los anclajes que dependen del tamaño de un componente, se publicó un sucesor en Compose-Foundation 1.6.0-alpha01: AnchoredDraggable. AnchoredDraggable es una API de Foundation para compilar componentes arrastrables con estados anclados, como hojas inferiores, cajones o la acción de deslizar para descartar.

Las APIs de Swipeable de Material dejaron de estar disponibles en favor de AnchoredDraggable de Foundation y se quitarán en una versión futura. En esta guía, se describe cómo migrar de las APIs de Swipeable a AnchoredDraggable.

Migra SwipeableState a AnchoredDraggableState

Comienza por identificar los cambios en tu contenedor de estado. AnchoredDraggableState no se puede heredar, y el desplazamiento se representa como Float.NaN antes de que se inicialice.

Actualiza tu contenedor de estado

AnchoredDraggableState es una clase final, lo que significa que no se puede heredar de ella. Si tu componente existente hereda de SwipeableState, actualiza tu contenedor de estado para que contenga una referencia a AnchoredDraggableState en lugar de heredar de él:

Deslizable

class MySwitchState: SwipeableState()

AnchoredDraggable

class MySwitchState {
    private val anchoredDraggableState = AnchoredDraggableState(...)
}

Dado que tu contenedor de estado ya no hereda de SwipeableState, es posible que debas exponer APIs por tu cuenta. Las APIs más comunes que puedes usar son offset, progress, currentValue y targetValue.

Cómo acceder al desfase

A diferencia de Swipeable, el offset de AnchoredDraggableState es Float.NaN antes de que se inicialice. En AnchoredDraggable, los anclajes se pueden pasar al constructor de AnchoredDraggableState o actualizarse a través de AnchoredDraggableState#updateAnchors. Si pasas los anclajes al constructor de AnchoredDraggableState, se inicializa el desplazamiento de inmediato.

Si tus anclajes dependen del diseño o podrían cambiar, usa AnchoredDraggableState#updateAnchors para evitar volver a crear el estado cuando cambien los anclajes.

Si usas updateAnchors, el desplazamiento será Float.NaN antes de pasar los anclajes a updateAnchors. Para evitar pasar Float.NaN a los componentes por accidente, usa AnchoredDraggableState#requireOffset para exigir que se haya inicializado el desplazamiento cuando se lea. Esto te ayuda a detectar inconsistencias o posibles errores desde el principio.

@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) }
    }
}

Migra Modifier.swipeable a Modifier.anchoredDraggable

Modifier.anchoredDraggable() reemplaza a Modifier.swipeable. Algunos parámetros de Modifier.swipeable() se movieron directamente a AnchoredDraggableState, como se describe en las siguientes secciones.

Cómo definir anclajes

Define los anclajes con el método de compilador DraggableAnchors. Luego, pásalos al constructor de AnchoredDraggableState#updateAnchors o AnchoredDraggableState:

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) }
    )
}

Si los anclajes son estáticos, pásalos al constructor. Si dependen del diseño o no son estáticos, usa updateAnchors.

Cómo definir umbrales posicionales

Cambiaron el tipo y el nombre del parámetro de umbrales. En lugar de tener una interfaz ThresholdConfig separada, AnchoredDraggableState tiene un parámetro positionalThreshold que toma una función lambda que devuelve la posición del umbral. Por ejemplo, un umbral posicional del 50% se podría expresar de la siguiente manera:

val anchoredDraggableState = AnchoredDraggableState(
    positionalThreshold = { distance -> distance * 0.5f },
    ...
)

Un umbral posicional de 56dp se podría expresar de la siguiente manera:

val density = LocalDensity.current
val anchoredDraggableState = AnchoredDraggableState(
    positionalThreshold = { with(density) { 56.dp.toPx() } },
    ...
)

Cómo definir umbrales de velocidad

Los umbrales de velocidad también se pasan al constructor de AnchoredDraggableState y se expresan como una lambda:

val density = LocalDensity.current
val anchoredDraggableState = AnchoredDraggableState(
    velocityThreshold = { with(density) { 125.dp.toPx() } },
    ...
)

Cambios en la superficie de la API

A continuación, encontrarás una descripción general de los cambios en la superficie de la API.

AnchoredDraggableState

SwipeableState

AnchoredDraggableState

open class SwipeableState(initialValue: T, animationSpec: AnimationSpec = …, confirmStateChange: (T) -> Boolean = …)

class AnchoredDraggableState( initialValue: T, animationSpec: AnimationSpec = …, confirmValueChange: (T) -> Boolean = …, positionalThreshold: Density.(Float) -> Float = …, velocityThreshold: Dp = …)

offset: State

offset: Float
requireOffset()

progress: SwipeProgress

progress: Float [0f..1f]

currentValue: T

currentValue: T

targetValue: T

targetValue: T

direction: Float [-1f, 0f, 1f]

N/A

suspend animateTo(
targetValue: T,
anim: AnimationSpec = …)

suspend animateTo(
targetState: T,
velocity: Float =
lastVelocity)

suspend snapTo(targetValue: T)

suspend snapTo(targetValue: T)

performDrag(delta: Float)

dispatchRawDelta(delta: Float)

suspend performFling(velocity: Float)

suspend settle(velocity: Float)

isAnimationRunning: Boolean

isAnimationRunning: Boolean

lastVelocity: Float

Modifier.anchoredDraggable

Modifier.swipeable

Modifier.anchoredDraggable

state: SwipeableState

state: AnchoredDraggableState

anchors: Map

AnchoredDraggableState#updateAnchors
or

AnchoredDraggableState#constructor

orientation: Orientation

orientation: Orientation

enabled: Boolean = true

enabled: Boolean = true

reverseDirection: Boolean = false

reverseDirection: Boolean = false

interactionSource: MutableInteractionSource? = null

interactionSource: MutableInteractionSource? = null

thresholds: (from: T, to: T) -> ThresholdConfig = FixedThreshold(56.dp)

Se pasa al constructor AnchoredDraggableState como positionalThreshold

resistance: ResistanceConfig? = …

Aún no se admite. Consulta b/288084801 para conocer el estado más reciente.

velocityThreshold: Dp = 125.dp

Se pasa al constructor AnchoredDraggable.