Migrar de Swipeable para AnchoredDraggable

A Swipeable é uma API Compose Material que ajuda a criar componentes que podem ser deslizados entre estados distintos, como páginas inferiores, gavetas ou deslizar para dispensar. Para oferecer melhor suporte a casos de uso avançados, como âncoras que dependem do tamanho de um componente, um sucessor foi publicado no Compose-Foundation 1.6.0-alpha01: AnchoredDraggable. A AnchoredDraggable é uma API Foundation para criar componentes arrastáveis com estados ancorados, como páginas inferiores, gavetas ou deslizar para dispensar.

As APIs Swipeable do Material Design foram descontinuadas e substituídas por AnchoredDraggable do Foundation, e elas vão ser removidas em uma versão futura Este guia descreve como migrar das APIs Swipeable para o AnchoredDraggable.

Migrar SwipeableState para AnchoredDraggableState

Comece identificando as mudanças no detentor de estado. AnchoredDraggableState não pode ser herdado, e o deslocamento é representado como Float.NaN antes de ser inicializado.

Atualizar o detentor de estado

AnchoredDraggableState é uma classe final, o que significa que ela não pode ser herdada. Se o componente existente herdar de SwipeableState, atualize o detentor de estado para manter uma referência ao AnchoredDraggableState em vez de herdar dele:

Deslizante

class MySwitchState: SwipeableState()

AnchoredDraggable

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

Como o detentor de estado não herda mais de SwipeableState, talvez seja necessário expor APIs por conta própria. As APIs mais comuns que você pode usar são offset, progress, currentValue e targetValue.

Acessar o deslocamento

Ao contrário de Swipeable, o offset do AnchoredDraggableState é Float.NaN antes de ser inicializado. Em AnchoredDraggable, as âncoras podem ser transmitidas para o construtor do AnchoredDraggableState ou atualizadas usando AnchoredDraggableState#updateAnchors. A transmissão das âncoras para o construtor do AnchoredDraggableState inicializa o deslocamento imediatamente.

Caso suas âncoras dependam do layout ou possam mudar, use AnchoredDraggableState#updateAnchors para evitar recriar o estado quando as âncoras mudarem.

Se você usar updateAnchors, o deslocamento será Float.NaN antes de transmitir as Âncoras para updateAnchors. Para evitar a transmissão acidental de Float.NaN a componentes, use AnchoredDraggableState#requireOffset para exigir que o deslocamento tenha sido inicializado durante a leitura. Isso ajuda você a detectar inconsistências ou possíveis bugs logo no início.

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

Migrar Modifier.swipeable para Modifier.anchoredDraggable

Modifier.anchoredDraggable() substitui Modifier.swipeable. Alguns parâmetros de Modifier.swipeable() foram movidos diretamente para AnchoredDraggableState, conforme descrito nas seções a seguir.

Definir âncoras

Defina as âncoras usando o método do builder DraggableAnchors. Em seguida, transmita-os para o construtor de AnchoredDraggableState#updateAnchors ou AnchoredDraggableState:

Construtor

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

Se as âncoras forem estáticas, transmita-as para o construtor. Se elas dependerem do layout ou não forem estáticas, use updateAnchors.

Definir limites de posicionamento

O tipo e o nome do parâmetro de limites foram alterados. Em vez de ter uma interface ThresholdConfig separada, o AnchoredDraggableState tem um parâmetro positionalThreshold que usa uma função lambda que retorna a posição do limite. Por exemplo, um limite posicional de 50% pode ser expresso como:

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

Um limite de posição de 56dp pode ser expresso assim:

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

Definir limites de velocidade

Os limites de velocidade também são transmitidos para o construtor de AnchoredDraggableState e também expressos como um lambda:

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

Mudanças na superfície da API

Confira abaixo uma visão geral das mudanças na plataforma da 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)

Passado para o construtor AnchoredDraggableState como positionalThreshold.

resistance: ResistanceConfig? = …

Sem suporte no momento. Consulte b/288084801 para ver o status mais recente.

velocityThreshold: Dp = 125.dp

Passado para o construtor AnchoredDraggable.