Toque e pressione

Muitos elementos combináveis 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 os elementos combináveis. Esta página se concentra em gestos que envolvem um único ponteiro, em que a posição desse ponteiro não é significativa para o processamento desse evento. A tabela a seguir lista esses tipos de gestos:

Gesto

Descrição

Toque (ou clique)

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 cursor desce.

Responder a toques ou cliques

clickable é um modificador usado com frequência que faz com que um elemento combinável reaja a toques ou cliques. Esse modificador também adiciona outros recursos, como suporte a 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 com eventos de clique por entrada de teclado ou ao usar serviços de acessibilidade.

Imagine uma grade de imagens, em que uma imagem aparece em tela cheia quando um usuário clicar 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 personalizar essas informações na página Como lidar com interações do usuário.
  • Permite que os serviços de acessibilidade interajam com o elemento definindo as informações semânticas.
  • Oferece suporte à interação com teclado ou joystick, permitindo o foco e a pressão Enter ou o centro do botão direcional para interagir.
  • Torne o elemento acionável para que ele responda ao mouse ou à stylus que passa por cima dele.

Toque e pressione para mostrar um menu de contexto contextual

combinedClickable permite adicionar o comportamento de toque duplo ou pressionar e manter, além do comportamento de clique normal. É possível usar combinedClickable para mostrar um menu de contexto quando o usuário toca e pressiona uma imagem de 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 o feedback tátil quando o usuário pressionar um elemento por muito tempo. É por isso que o snippet inclui a invocação performHapticFeedback.

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

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 foco, teclado e suporte à 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 poder tocar nele para dispensar a tela de detalhes:

Nesse caso, esse 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 focado e a resposta dele a eventos de teclado e acessibilidade é diferente da de um combinável típico. Em vez de tentar adaptar o comportamento clickable, você pode ir para 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 a chave do modificador pointerInput, você transmite a lambda onClose. Isso reexecuta automaticamente o lambda, garantindo que o callback correto seja chamado quando o usuário tocar na tela escura.

Toque duas vezes para aplicar 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 o zoom na imagem tocando duas vezes:

Como você pode ver no vídeo, o zoom ocorre em torno da posição do evento de toque. O resultado é diferente quando aumentamos o zoom na 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 de toque ao 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
        }
)