Tocca e premi

Molti composabili hanno il supporto integrato per i tocchi o i clic e includono una lambda onClick. Ad esempio, puoi creare un Surface cliccabile che include tutto il comportamento di Material Design appropriato per l'interazione con le superfici:

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

Tuttavia, i clic non sono l'unico modo in cui un utente può interagire con i componibili. Questa pagina si concentra sui gesti che coinvolgono un singolo puntatore, la cui posizione non è significativa per la gestione dell'evento. La tabella seguente elenca questi tipi di gesti:

Gesto

Descrizione

Tocca (o fai clic su)

Il puntatore si sposta verso il basso e poi verso l'alto

Tocca due volte

Il puntatore si sposta verso il basso, verso l'alto, verso il basso, verso l'alto

Pressione lunga

Il puntatore si sposta verso il basso e viene tenuto premuto per un periodo di tempo più lungo

Stampa

Il puntatore si sposta verso il basso

Rispondi al tocco o al clic

clickable è un modificatore di uso comune che fa reagire un composable ai tocchi o ai clic. Questo modificatore aggiunge anche funzionalità aggiuntive, come il supporto per lo stato attivo, il passaggio del mouse e dello stilo e un'indicazione visiva personalizzabile quando viene premuto. Il modificatore risponde ai "clic" nel senso più ampio del termine, non solo con il mouse o il dito, ma anche agli eventi di clic tramite l'input da tastiera o quando utilizzi i servizi di accessibilità.

Immagina una griglia di immagini, in cui un'immagine viene visualizzata a schermo intero quando un utente ci fa clic sopra:

Per implementare questo comportamento, puoi aggiungere il modificatore clickable a ogni elemento della griglia:

@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 }
        )
    }
}

Il modificatore clickable aggiunge anche un comportamento aggiuntivo:

  • interactionSource e indication, che per impostazione predefinita disegnano un'onda quando un utente tocca il componibile. Scopri come personalizzarli nella pagina Gestione delle interazioni degli utenti.
  • Consente ai servizi di accessibilità di interagire con l'elemento impostando le informazioni semantiche.
  • Supporta l'interazione con la tastiera o il joystick consentendo lo stato attivo e la pressione di Enter o del centro del D-pad per interagire.
  • Rendi l'elemento attivabile al passaggio del mouse, in modo che risponda al passaggio del mouse o dello stilo.

Tieni premuto per visualizzare un menu contestuale

combinedClickable ti consente di aggiungere un comportamento di doppio tocco o pressione prolungata oltre al normale comportamento di clic. Puoi utilizzare combinedClickable per mostrare un menu contestuale quando un utente tocca e tiene premuta un'immagine della griglia:

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 }
    )
}

Come best practice, dovresti includere il feedback aptico quando l'utente tiene premuti gli elementi, motivo per cui lo snippet include la chiamata performHapticFeedback.

Ignora un composable toccando uno scrim

Negli esempi precedenti, clickable e combinedClickable aggiungono funzionalità utili ai tuoi componibili. Mostrano un'indicazione visiva al momento dell'interazione, rispondono al passaggio del mouse e includono il supporto per lo stato attivo, la tastiera e l'accessibilità. Tuttavia, questo comportamento aggiuntivo non è sempre auspicabile.

Diamo un'occhiata alla schermata dei dettagli dell'immagine. Lo sfondo deve essere semitrasparente e l'utente deve poterlo toccare per chiudere la schermata dei dettagli:

In questo caso, lo sfondo non deve avere alcuna indicazione visiva sull'interazione, non deve rispondere al passaggio del mouse, non deve essere attivabile e la sua risposta agli eventi di accessibilità e della tastiera deve essere diversa da quella di un tipico componibile. Anziché provare ad adattare il comportamento di clickable, puoi scendere a un livello di astrazione inferiore e utilizzare direttamente il modificatore pointerInput in combinazione con il metodo 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))
    )
}

Come chiave del modificatore pointerInput, passi la funzione lambda onClose. In questo modo, la funzione lambda viene eseguita di nuovo automaticamente, assicurando che venga richiamato il callback corretto quando l'utente tocca lo scrim.

Tocca due volte per lo zoom

A volte clickable e combinedClickable non includono informazioni sufficienti per rispondere all'interazione nel modo corretto. Ad esempio, i componibili potrebbero aver bisogno di accedere alla posizione all'interno dei limiti del componibile in cui è avvenuta l'interazione.

Diamo un'occhiata di nuovo alla schermata dei dettagli dell'immagine. Una best practice consiste nel consentire lo zoom dell'immagine con un doppio tocco:

Come puoi vedere nel video, lo zoom in avanti avviene intorno alla posizione dell'evento di tocco. Il risultato è diverso quando aumentiamo lo zoom sulla parte sinistra dell'immagine rispetto alla parte destra. Possiamo utilizzare il modificatore pointerInput in combinazione con detectTapGestures per incorporare la posizione del tocco nel nostro calcolo:

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
        }
)