Appuyer et appuyer

De nombreux composables sont compatibles avec les pressions ou les clics et incluent un lambda onClick. Par exemple, vous pouvez créer un Surface cliquable qui inclut tous les comportements Material Design appropriés pour les interactions avec les surfaces:

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

Toutefois, les clics ne sont pas le seul moyen pour un utilisateur d'interagir avec les composables. Cette page se concentre sur les gestes impliquant un seul pointeur, dont la position n'est pas importante pour la gestion de cet événement. Le tableau suivant répertorie ces types de gestes:

Geste

Description

Appuyez (ou cliquez)

Pointeur qui descend, puis remonte

Appuyer deux fois

Le pointeur descend, monte, descend, monte

Appuyer de manière prolongée

Le pointeur descend et est maintenu plus longtemps

Presse

Pointeur vers le bas

Répondre à un appui ou un clic

clickable est un modificateur couramment utilisé qui fait réagir un composable aux pressions ou aux clics. Ce modificateur ajoute également des fonctionnalités supplémentaires, telles que la mise au point, le survol de la souris et du stylet, ainsi qu'une indication visuelle personnalisable lors de l'appui. Le modificateur répond aux "clics" au sens le plus large du terme, non seulement avec la souris ou l'index, mais aussi aux événements de clic via la saisie au clavier ou lors de l'utilisation de services d'accessibilité.

Imaginez une grille d'images, où une image s'affiche en plein écran lorsqu'un utilisateur clique dessus:

Pour implémenter ce comportement, vous pouvez ajouter le modificateur clickable à chaque élément de la grille:

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

Le modificateur clickable ajoute également un comportement supplémentaire:

  • interactionSource et indication, qui dessinent une ondulation par défaut lorsqu'un utilisateur appuie sur le composable. Découvrez comment les personnaliser sur la page Gérer les interactions des utilisateurs.
  • Permet aux services d'accessibilité d'interagir avec l'élément en définissant les informations sémantiques.
  • Compatible avec l'interaction au clavier ou au joystick en permettant de sélectionner un élément et d'appuyer sur Enter ou sur le centre du pavé directionnel pour interagir.
  • Rendez l'élément cliquable afin qu'il réagisse à la souris ou au stylet lorsqu'ils pointent dessus.

Appuyer de manière prolongée pour afficher un menu contextuel

combinedClickable vous permet d'ajouter un comportement de double appui ou de pression prolongée en plus du comportement de clic normal. Vous pouvez utiliser combinedClickable pour afficher un menu contextuel lorsqu'un utilisateur appuie de manière prolongée sur une image de grille:

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

Il est recommandé d'inclure un retour haptique lorsque l'utilisateur appuie de manière prolongée sur des éléments. C'est pourquoi l'extrait inclut l'appel performHapticFeedback.

Ignorer un composable en appuyant sur un écran de masquage

Dans les exemples ci-dessus, clickable et combinedClickable ajoutent des fonctionnalités utiles à vos composables. Elles affichent une indication visuelle sur l'interaction, répondent au survol et incluent la sélection, le clavier et la prise en charge de l'accessibilité. Toutefois, ce comportement supplémentaire n'est pas toujours souhaitable.

Examinons l'écran d'informations détaillées sur l'image. L'arrière-plan doit être semi-transparent et l'utilisateur doit pouvoir appuyer dessus pour fermer l'écran d'informations:

Dans ce cas, cet arrière-plan ne doit pas comporter d'indication visuelle sur l'interaction, ne doit pas répondre au survol, ne doit pas être sélectionnable et sa réponse aux événements de clavier et d'accessibilité diffère de celle d'un composable typique. Au lieu d'essayer d'adapter le comportement de clickable, vous pouvez passer à un niveau d'abstraction inférieur et utiliser directement le modificateur pointerInput en combinaison avec la méthode 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))
    )
}

En tant que clé du modificateur pointerInput, vous transmettez le lambda onClose. Cela réexécute automatiquement le lambda, en veillant à ce que le rappel approprié soit appelé lorsque l'utilisateur appuie sur le masquage.

Appuyer deux fois pour zoomer

Parfois, clickable et combinedClickable n'incluent pas suffisamment d'informations pour répondre correctement à l'interaction. Par exemple, les composables peuvent avoir besoin d'accéder à la position dans les limites du composable où l'interaction s'est produite.

Examinons à nouveau l'écran d'informations sur l'image. Il est recommandé de permettre de faire un zoom avant sur l'image en appuyant deux fois dessus:

Comme vous pouvez le voir dans la vidéo, le zoom avant se produit autour de la position de l'événement de pression. Le résultat est différent lorsque nous faisons un zoom avant sur la partie gauche de l'image par rapport à la partie droite. Nous pouvons utiliser le modificateur pointerInput en combinaison avec detectTapGestures pour intégrer la position du geste de pression dans notre calcul:

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