Arrastar, deslizar e deslizar

O modificador draggable é o ponto de entrada de alto nível para gestos de arrastar em uma única orientação e informa a distância da ação de arrastar em pixels.

É importante observar que esse modificador é semelhante a scrollable, porque ele detecta apenas o gesto. É necessário manter o estado e representá-lo na tela, por exemplo, movendo o elemento com o modificador offset:

@Composable
private fun DraggableText() {
    var offsetX by remember { mutableStateOf(0f) }
    Text(
        modifier = Modifier
            .offset { IntOffset(offsetX.roundToInt(), 0) }
            .draggable(
                orientation = Orientation.Horizontal,
                state = rememberDraggableState { delta ->
                    offsetX += delta
                }
            ),
        text = "Drag me!"
    )
}

Caso você precise controlar todo o gesto de arrastar, considere usar o detector de gestos de arrastar, com o modificador pointerInput.

@Composable
private fun DraggableTextLowLevel() {
    Box(modifier = Modifier.fillMaxSize()) {
        var offsetX by remember { mutableStateOf(0f) }
        var offsetY by remember { mutableStateOf(0f) }

        Box(
            Modifier
                .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
                .background(Color.Blue)
                .size(50.dp)
                .pointerInput(Unit) {
                    detectDragGestures { change, dragAmount ->
                        change.consume()
                        offsetX += dragAmount.x
                        offsetY += dragAmount.y
                    }
                }
        )
    }
}

Um elemento da IU sendo arrastado por pressionamento da tela

Deslizar

O modificador swipeable permite arrastar elementos que, quando soltos, são animados em direção a dois ou mais pontos de fixação definidos na orientação. Um uso comum para isso é a implementação do padrão "deslizar para dispensar".

Esse modificador não move o elemento, apenas detecta o gesto. É necessário manter o estado e representá-lo na tela, por exemplo, movendo o elemento com o modificador offset.

O estado deslizante é obrigatório no modificador swipeable e pode ser criado e lembrado com rememberSwipeableState(). Esse estado também fornece um conjunto de métodos úteis para inserir animações de forma programática nos pontos fixos (consulte snapTo, animateTo, performFling e performDrag) e as propriedades para observar o progresso da ação de arrastar.

O gesto de deslizar pode ser configurado para ter diferentes tipos de limite, como FixedThreshold(Dp) e FractionalThreshold(Float). Esses limites podem ser diferentes para cada combinação de ponto de partida e chegada dos pontos fixos.

Para ter mais flexibilidade, você pode configurar resistance ao deslizar para além dos limites. Também é possível configurar velocityThreshold, que fará a animação do gesto de deslizar para o próximo estado, mesmo que os thresholds não tenham sido alcançados.

@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun SwipeableSample() {
    val width = 96.dp
    val squareSize = 48.dp

    val swipeableState = rememberSwipeableState(0)
    val sizePx = with(LocalDensity.current) { squareSize.toPx() }
    val anchors = mapOf(0f to 0, sizePx to 1) // Maps anchor points (in px) to states

    Box(
        modifier = Modifier
            .width(width)
            .swipeable(
                state = swipeableState,
                anchors = anchors,
                thresholds = { _, _ -> FractionalThreshold(0.3f) },
                orientation = Orientation.Horizontal
            )
            .background(Color.LightGray)
    ) {
        Box(
            Modifier
                .offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
                .size(squareSize)
                .background(Color.DarkGray)
        )
    }
}

Um elemento da IU respondendo a um gesto de deslizar