Appuyer et appuyer

De nombreux composables sont compatibles avec les appuis ou les clics et incluent un lambda onClick. Par exemple, vous pouvez créer un élément Surface cliquable qui inclut tous les comportements Material Design adaptés à 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 les composables. Cette page se concentre sur les gestes qui impliquent un seul pointeur, pour lesquels la position de ce pointeur n'est pas significative pour la gestion de cet événement. Le tableau suivant répertorie ces types de gestes:

Geste

Description

Appuyer (ou cliquer)

Le pointeur descend, puis remonte

Appuyer deux fois

Le pointeur permet de faire défiler la page vers le bas, le haut, le bas, le haut

Appuyer de manière prolongée

Le pointeur descend et est maintenu enfoncé plus longtemps

Presse

Le pointeur baisse

Répondre à un appui ou un clic

clickable est un modificateur couramment utilisé qui fait réagir un composable 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, le survol avec la souris et le stylet, et une indication visuelle personnalisable lorsque l'utilisateur appuie dessus. Le modificateur répond aux "clics" au sens le plus large du mot, non seulement avec la souris ou le doigt, mais également avec les é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:

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:

  • 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.
  • Permet l'interaction avec le clavier ou le joystick en permettant la mise au point et en appuyant sur Enter ou le centre du pavé directionnel.
  • Rendez l'élément cliquable pour qu'il réagisse au survol de la souris ou du stylet.

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

combinedClickable vous permet d'ajouter un double appui ou un appui prolongé en plus du comportement normal des clics. 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 }
    )
}

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 inclut l'appel performHapticFeedback.

Fermez un composable en appuyant sur une toile.

Dans les exemples ci-dessus, clickable et combinedClickable ajoutent des fonctionnalités utiles à vos composables. Ils affichent une indication visuelle des interactions, répondent aux survols et incluent la sélection, le clavier et l'accessibilité. Toutefois, ce comportement supplémentaire n'est pas toujours souhaitable.

Regardons 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étaillé:

Dans ce cas, cet arrière-plan ne doit avoir aucune 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 classique. Au lieu d'essayer d'adapter le comportement de clickable, vous pouvez revenir à un niveau d'abstraction inférieur et utiliser directement le modificateur pointerInput en combinaison avec la méthode 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))
    )
}

En tant que touche du modificateur pointerInput, vous transmettez le lambda onClose. Cette action réexécute automatiquement le lambda, garantissant ainsi que le bon rappel est appelé lorsque l'utilisateur appuie sur le fond.

Appuyez 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.

Regardons à nouveau l'écran des détails de l'image. Une bonne pratique consiste à permettre un zoom avant sur l'image en appuyant deux fois:

Comme vous pouvez le voir dans la vidéo, un zoom avant se produit autour de la position de l'événement tactile. Le résultat est différent lorsque nous effectuons 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 d'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
        }
)