Toque e pressione

Muitos elementos que podem ser compostos têm suporte integrado a toques ou cliques e incluem uma lambda onClick. Por exemplo, você pode criar um Surface clicável que inclua todo o comportamento do Material Design adequado para interação com plataformas:

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

Mas os cliques não são a única maneira de um usuário interagir com 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 abaixo lista esses tipos de gestos:

Gesto

Description

Toque (ou clique)

O ponteiro desce e depois para cima

Tocar duas vezes

O ponteiro desce, para cima, para baixo e para cima

manter pressionado

O ponteiro se desce e é mantido por mais tempo

Imprensa

O ponteiro se desce

Responder a toque ou clique

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 compatibilidade com foco, passagem do mouse e 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 mouse ou dedo, mas também com eventos de clique usando a entrada do teclado ou o uso de serviços de acessibilidade.

Imagine uma grade de imagens, em que uma delas aparece em tela cheia quando um usuário clica nela:

Você pode adicionar o modificador clickable a cada item da 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 outros comportamentos:

  • 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.
  • Tem suporte à interação por teclado ou joystick, permitindo o foco e pressionando Enter ou o centro do botão direcional para interagir.
  • Deixe o elemento flutuante para que ele responda ao cursor do mouse ou da stylus sobre ele.

Toque e mantenha pressionado para mostrar um menu de contexto contextual

A combinedClickable permite adicionar o comportamento de tocar duas vezes ou tocar e manter pressionado, além do comportamento normal de clique. Você pode usar combinedClickable para mostrar um menu de contexto quando um usuário toca e mantém pressionada 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 um retorno tátil quando o usuário mantiver pressionado os elementos. É por isso que o snippet inclui a invocação performHapticFeedback.

Toque em um scrim para dispensar um elemento combinável

Nos exemplos acima, clickable e combinedClickable adicionam funcionalidades úteis aos elementos combináveis. Eles mostram uma indicação visual sobre a 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 segundo plano 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 precisa ter nenhuma indicação visual sobre a interação, não pode responder ao movimento do cursor, não pode ser focalizável e a resposta dele aos eventos de teclado e acessibilidade é diferente da resposta de um elemento combinável típico. Em vez de tentar adaptar o comportamento do clickable, você pode usar uma lista suspensa para um nível de abstração mais baixo e usar diretamente o modificador pointerInput em combinação com o método detectTapGestures:

@OptIn(ExperimentalComposeUiApi::class)
@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 executa automaticamente o lambda, garantindo que o callback correto seja chamado quando o usuário toca no scrim.

Toque duas vezes para aplicar zoom

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

Vamos analisar a tela de detalhes da imagem de novo. Uma prática recomendada é permitir aumentar o zoom na imagem tocando duas vezes:

Como mostrado no vídeo, o zoom é aumentado na 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 detectTapGestures para incorporar a posição de toque no 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
        }
)