Toque e pressione

Muitos elementos de composição têm suporte integrado para toques ou cliques e incluem uma lambda onClick. Por exemplo, é possível criar um Surface clicável que inclua todo o comportamento do Material Design adequado para interação com superfícies:

Surface(onClick = { /* handle click */ }) {
    Text("Click me!", Modifier.padding(24.dp))
}

No entanto, os cliques não são a única maneira de um usuário interagir com elementos que podem ser compostos. Esta página se concentra em gestos que envolvem um único ponteiro, em que a posição dele não é significativa para o processamento do evento. A tabela a seguir lista esses tipos de gestos:

Gesto

Descrição

Toque (ou clique) em

O ponteiro desce e depois sobe

Tocar duas vezes

O ponteiro vai para baixo, para cima, para baixo, para cima

Manter pressionado

O ponteiro desce e é mantido por mais tempo

Imprensa

O ponteiro desce

Responder ao toque ou clique

clickable é um modificador usado com frequência que faz um elemento combinável reagir a toques ou cliques. Esse modificador também adiciona outros recursos, como suporte para foco, passagem do mouse e da stylus e uma indicação visual personalizável quando pressionado. O modificador responde a "cliques" no sentido mais amplo da palavra, não apenas com o mouse ou o dedo, mas também a eventos de clique por entrada de teclado ou ao usar serviços de acessibilidade.

Imagine uma grade de imagens, em que uma imagem é mostrada em tela cheia quando um usuário clica nela:

É possível adicionar o modificador clickable a cada item na grade para implementar esse comportamento:

@Composable
private fun ImageGrid(photos: List<Photo>) {
    var activePhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
    LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
        items(photos, { it.id }) { photo ->
            ImageItem(
                photo,
                Modifier.clickable { activePhotoId = photo.id }
            )
        }
    }
    if (activePhotoId != null) {
        FullScreenImage(
            photo = photos.first { it.id == activePhotoId },
            onDismiss = { activePhotoId = null }
        )
    }
}

O modificador clickable também adiciona outro comportamento:

  • interactionSource e indication, que desenham uma ondulação por padrão quando um usuário toca no elemento combinável. Saiba como personalizá-los na página Como processar interações do usuário.
  • Permite que os serviços de acessibilidade interajam com o elemento definindo as informações de semântica.
  • Oferece suporte à interação com teclado ou joystick, permitindo o foco e o pressionamento de Enter ou do centro do botão direcional para interagir.
  • Torne o elemento flutuante para que ele responda ao mouse ou à stylus passando por cima dele.

Toque e pressione para mostrar um menu de contexto

O combinedClickable permite adicionar o comportamento de tocar duas vezes ou tocar e manter pressionado, além do comportamento de clique normal. Use combinedClickable para mostrar um menu de contexto quando um usuário tocar e manter pressionada uma imagem da grade:

var contextMenuPhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
val haptics = LocalHapticFeedback.current
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
    items(photos, { it.id }) { photo ->
        ImageItem(
            photo,
            Modifier
                .combinedClickable(
                    onClick = { activePhotoId = photo.id },
                    onLongClick = {
                        haptics.performHapticFeedback(HapticFeedbackType.LongPress)
                        contextMenuPhotoId = photo.id
                    },
                    onLongClickLabel = stringResource(R.string.open_context_menu)
                )
        )
    }
}
if (contextMenuPhotoId != null) {
    PhotoActionsSheet(
        photo = photos.first { it.id == contextMenuPhotoId },
        onDismissSheet = { contextMenuPhotoId = null }
    )
}

Como prática recomendada, inclua uma resposta tátil quando o usuário tocar e manter pressionados os elementos. Por isso, o snippet inclui a invocação performHapticFeedback.

Dispensar um elemento combinável tocando em uma tela de fundo

Nos exemplos acima, clickable e combinedClickable adicionam funcionalidades úteis aos elementos combináveis. Eles mostram uma indicação visual na interação, respondem ao passar o cursor e incluem suporte para foco, teclado e acessibilidade. No entanto, esse comportamento extra nem sempre é desejável.

Vamos analisar a tela de detalhes da imagem. O plano de fundo precisa ser semitransparente, e o usuário precisa conseguir tocar nele para dispensar a tela de detalhes:

Nesse caso, o plano de fundo não pode ter nenhuma indicação visual na interação, não pode responder ao passar o cursor, não pode ser focalizável e a resposta a eventos de teclado e acessibilidade é diferente da de um combinável típico. Em vez de tentar adaptar o comportamento de clickable, você pode usar um nível de abstração mais baixo e usar diretamente o modificador pointerInput em combinação com o método detectTapGestures:

@Composable
private fun Scrim(onClose: () -> Unit, modifier: Modifier = Modifier) {
    val strClose = stringResource(R.string.close)
    Box(
        modifier
            // handle pointer input
            .pointerInput(onClose) { detectTapGestures { onClose() } }
            // handle accessibility services
            .semantics(mergeDescendants = true) {
                contentDescription = strClose
                onClick {
                    onClose()
                    true
                }
            }
            // handle physical keyboard input
            .onKeyEvent {
                if (it.key == Key.Escape) {
                    onClose()
                    true
                } else {
                    false
                }
            }
            // draw scrim
            .background(Color.DarkGray.copy(alpha = 0.75f))
    )
}

Como chave do modificador pointerInput, transmita a lambda onClose. Isso reexecuta automaticamente o lambda, garantindo que o callback correto seja chamado quando o usuário tocar na tela.

Toque duas vezes para aumentar o zoom

Às vezes, clickable e combinedClickable não incluem informações suficientes para responder à interação da maneira correta. Por exemplo, os elementos combináveis podem precisar de acesso à posição dentro dos limites do elemento combinável em que a interação ocorreu.

Vamos analisar a tela de detalhes da imagem novamente. Uma prática recomendada é permitir que o usuário amplie a imagem com um toque duplo:

Como você pode ver no vídeo, o zoom ocorre ao redor da posição do evento de toque. O resultado é diferente quando ampliamos a parte esquerda da imagem em comparação com a parte direita. Podemos usar o modificador pointerInput em combinação com o detectTapGestures para incorporar a posição do toque ao nosso cálculo:

var zoomed by remember { mutableStateOf(false) }
var zoomOffset by remember { mutableStateOf(Offset.Zero) }
Image(
    painter = rememberAsyncImagePainter(model = photo.highResUrl),
    contentDescription = null,
    modifier = modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onDoubleTap = { tapOffset ->
                    zoomOffset = if (zoomed) Offset.Zero else
                        calculateOffset(tapOffset, size)
                    zoomed = !zoomed
                }
            )
        }
        .graphicsLayer {
            scaleX = if (zoomed) 2f else 1f
            scaleY = if (zoomed) 2f else 1f
            translationX = zoomOffset.x
            translationY = zoomOffset.y
        }
)