Tocca e premi

Molti componibili hanno un supporto integrato per tocchi o clic e includono una funzione lambda onClick. Ad esempio, puoi creare un elemento Surface cliccabile che includa tutti i comportamenti di Material Design appropriati per l'interazione con le piattaforme:

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 è incentrata sui gesti che prevedono un singolo puntatore, in cui la posizione del puntatore non è significativa per la gestione dell'evento. Nella tabella che segue sono elencati questi tipi di gesti:

Gesto

Descrizione

Toccare (o fare clic)

Il puntatore scende e poi verso l'alto

Toccare due volte

Il puntatore del mouse va in basso, in alto, in basso, in alto

Pressione lunga

Il puntatore diminuisce e viene tenuto per più tempo

Interviste

Puntatore abbassato

Rispondere al tocco o al clic

clickable è un modificatore di uso comune che genera una reazione componibile a tocchi o 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 al tocco. Il modificatore risponde ai "clic" nel senso più ampio del termine, non solo con il mouse o il dito, ma anche con i clic tramite l'input da tastiera o quando si utilizzano i servizi di accessibilità.

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

Puoi aggiungere il modificatore clickable a ogni elemento della griglia per implementare questo 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 }
        )
    }
}

Il modificatore clickable aggiunge anche un comportamento aggiuntivo:

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

Tieni premuto per visualizzare un menu contestuale contestuale

combinedClickable ti consente di aggiungere il comportamento del doppio tocco o della pressione prolungata oltre al normale comportamento del clic. Puoi usare 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 preme a lungo gli elementi, motivo per cui lo snippet include la chiamata a performHapticFeedback.

Ignorare un componibile toccando una tela

Negli esempi precedenti, clickable e combinedClickable aggiungono funzionalità utili ai tuoi componibili. Mostrano indicazioni visive sull'interazione, rispondono al passaggio del mouse e includono lo stato attivo, la tastiera e il supporto per 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 essere in grado di toccarlo per chiudere la schermata dei dettagli:

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

Come tasto del modificatore pointerInput passi la lambda onClose. Questo riesegui automaticamente il lambda, assicurandosi che venga richiamato il callback corretto quando l'utente tocca lo scrim.

Tocca due volte per eseguire lo zoom

A volte clickable e combinedClickable non includono informazioni sufficienti per rispondere all'interazione nel modo corretto. Ad esempio, gli elementi componibili potrebbero richiedere l'accesso alla posizione entro i relativi limiti in cui si è verificata l'interazione.

Diamo di nuovo un'occhiata alla schermata dei dettagli dell'immagine. Una best practice consiste nel consentire di aumentare lo zoom sull'immagine toccando due volte:

Come puoi vedere nel video, viene aumentato lo zoom intorno alla posizione dell'evento tocco. Il risultato è diverso quando aumentiamo lo zoom sulla parte sinistra dell'immagine e su quella destra. Possiamo utilizzare il modificatore pointerInput in combinazione con detectTapGestures per incorporare la posizione del tocco nel 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
        }
)