Appuyer et appuyer

De nombreux composables sont compatibles avec les gestes d'appui 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 l'interaction 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 des 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 liste ces types de gestes :

Geste

Description

Appuyez (ou cliquez)

Le pointeur descend, puis remonte

Appuyer deux fois

Le pointeur descend, monte, descend, monte

Appuyer de manière prolongée

Le pointeur descend et reste enfoncé plus longtemps

Presse

Le pointeur descend

Répondre à un appui ou à un clic

clickable est un modificateur couramment utilisé qui permet à un composable de réagir aux appuis ou aux clics. Ce modificateur ajoute également des fonctionnalités supplémentaires, telles que la prise en charge de la mise au point, du pointage de la souris et du stylet, ainsi qu'une indication visuelle personnalisable lorsque l'utilisateur appuie sur l'écran. Le modificateur répond aux "clics" au sens le plus large du terme, non seulement avec la souris ou le doigt, mais aussi aux événements de clic via une saisie au clavier ou lors de l'utilisation des services d'accessibilité.

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

Vous pouvez ajouter le modificateur clickable à chaque élément de la grille pour implémenter ce comportement :

@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 un effet d'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.
  • Permet d'interagir avec un clavier ou un 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 sensible au pointage pour qu'il réagisse au passage de la souris ou du stylet.

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

combinedClickable vous permet d'ajouter un comportement d'appui double ou d'appui de manière 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 la 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 }
    )
}

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

Ignorer un composable en appuyant sur un scrim

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

Examinons l'écran des détails de 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, l'arrière-plan ne doit pas comporter d'indication visuelle lors de l'interaction, ne doit pas répondre au pointage, ne doit pas être sélectionnable, et sa réponse aux événements de clavier et d'accessibilité doit être différente de celle d'un composable classique. 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))
    )
}

Vous transmettez le lambda onClose comme clé du modificateur pointerInput. Cela réexécute automatiquement le lambda, ce qui garantit que le bon rappel est appelé lorsque l'utilisateur appuie sur le scrim.

Appuyer deux fois pour zoomer

Il arrive que clickable et combinedClickable ne contiennent 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 a eu lieu.

Revenons à l'écran des détails de l'image. Une bonne pratique consiste à permettre de zoomer sur l'image en appuyant deux fois dessus :

Comme vous pouvez le voir dans la vidéo, le zoom avant s'effectue autour de la position de l'événement de pression. Le résultat est différent lorsque nous zoomons 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 de l'appui 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
        }
)