Gestos

O Compose fornece diversas APIs para ajudar a detectar gestos originados de interações do usuário. As APIs abrangem uma grande variedade de casos de uso:

  • Algumas delas são de alto nível e foram projetadas para abranger os gestos mais usados. Por exemplo, o modificador clickable facilita a detecção de cliques e também fornece recursos de acessibilidade e exibe indicadores visuais quando tocado (como ondulações).

  • Também existem detectores de gestos menos usados, que oferecem mais flexibilidade em um nível inferior, como PointerInputScope.detectTapGestures ou PointerInputScope.detectDragGestures, mas não incluem os recursos complementares.

Tocar e pressionar

O modificador clickable permite que os apps detectem cliques no elemento em que ele é aplicado.

@Composable
fun ClickableSample() {
    val count = remember { mutableStateOf(0) }
    // content that you want to make clickable
    Text(
        text = count.value.toString(),
        modifier = Modifier.clickable { count.value += 1 }
    )
}

Exemplo de um elemento de IU respondendo a toques

Quando for necessário ter mais flexibilidade, você poderá fornecer um detector de gestos de toque usando o modificador pointerInput:

Modifier.pointerInput(Unit) {
    detectTapGestures(
        onPress = { /* Called when the gesture starts */ },
        onDoubleTap = { /* Called on Double Tap */ },
        onLongPress = { /* Called on Long Press */ },
        onTap = { /* Called on Tap */ }
    )
}

Rolagem

Modificadores de rolagem

Os modificadores verticalScroll e horizontalScroll oferecem a forma mais simples de permitir que o usuário role um elemento quando os limites do conteúdo são maiores que as restrições de tamanho máximo. Com os modificadores verticalScroll e horizontalScroll, não é necessário transladar nem deslocar o conteúdo.

@Composable
fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Lista vertical simples respondendo a gestos de rolagem

O ScrollState permite mudar a posição de rolagem ou descobrir o estado atual. Para criá-lo com parâmetros padrão, use rememberScrollState().

@Composable
private fun ScrollBoxesSmooth() {

    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Modificador scrollable

O modificador scrollable é diferente dos modificadores de rolagem, porque scrollable detecta os gestos de rolagem, mas não desloca o conteúdo. Um ScrollableController é necessário para que esse modificador funcione corretamente. Ao criar ScrollableController, é necessário fornecer uma função consumeScrollDelta, que será invocada em cada etapa de rolagem (por entrada de gestos, rolagem suave ou rolagem rápida) com o delta em pixels. O total de distância de rolagem consumido precisa ser retornado dessa função para garantir a propagação adequada do evento.

O snippet a seguir detecta os gestos e exibe um valor numérico para o deslocamento, mas não desloca os elementos:

@Composable
fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

Elemento da IU que detecta o pressionamento da tela e exibe o valor numérico referente ao local de pressionamento

Rolagem aninhada

O Compose é compatível com a rolagem aninhada, em que vários elementos reagem a um único gesto de rolagem. Um exemplo típico de rolagem aninhada é uma lista dentro de outra e um caso mais complexo é uma barra de ferramentas recolhível (link em inglês).

Rolagem aninhada automática

Nenhuma ação é necessária para a rolagem aninhada simples. Os gestos que iniciam uma ação de rolagem são propagados automaticamente para os pais. Assim, quando o elemento filho não consegue rolar mais, o gesto é processado pelo pai.

O exemplo a seguir mostra elementos com um modificador verticalScroll aplicado em um contêiner que também tem um modificador verticalScroll aplicado a ele.

val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White)
Box(
    modifier = Modifier
        .background(Color.LightGray)
        .verticalScroll(rememberScrollState())
        .padding(32.dp)
) {
    Column {
        repeat(6) {
            Box(
                modifier = Modifier
                    .height(128.dp)
                    .verticalScroll(rememberScrollState())
            ) {
                Text(
                    "Scroll here",
                    modifier = Modifier
                        .border(12.dp, Color.DarkGray)
                        .background(brush = gradient)
                        .padding(24.dp)
                        .height(150.dp)
                )
            }
        }
    }
}

Dois elementos de IU de rolagem vertical aninhados, respondendo a gestos dentro e fora do elemento interno

Como usar o modificador NestedScroll

Caso você precise criar uma rolagem coordenada avançada entre vários elementos, o modificador nestedScroll oferece mais flexibilidade, definindo uma hierarquia de rolagem aninhada.

Arrastar

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:

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.

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.consumeAllChanges()
                    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 swipable 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 (consultesnapTo, 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.

@Composable
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

Multitoque: panorâmica, zoom, rotação

Para detectar gestos multitoque usados para colocar na panorâmica, aplicar zoom e girar, use o modificador transformable. Ele não transforma os elementos por si só, apenas detecta os gestos.

@Composable
fun TransformableSample() {
    // set up all transformation states
    var scale by remember { mutableStateOf(1f) }
    var rotation by remember { mutableStateOf(0f) }
    var offset by remember { mutableStateOf(Offset.Zero) }
    val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
        scale *= zoomChange
        rotation += rotationChange
        offset += offsetChange
    }
    Box(
        Modifier
            // apply other transformations like rotation and zoom
            // on the pizza slice emoji
            .graphicsLayer(
                scaleX = scale,
                scaleY = scale,
                rotationZ = rotation,
                translationX = offset.x,
                translationY = offset.y
            )
            // add transformable to listen to multitouch transformation events
            // after offset
            .transformable(state = state)
            .background(Color.Blue)
            .fillMaxSize()
    )
}

Um elemento da IU respondendo a gestos multitoque: panorâmica, zoom e rotação

Caso você precise combinar zoom, panorâmica e rotação com outros gestos, use o detector PointerInputScope.detectTransformGestures.