Lorsque j’ai écrit mon dernier article sur Jetpack Compose, j’ai indiqué que Jetpack Compose manquait de quelques composants de base, selon moi, et l’un d’eux était le tooltip.
À l’époque, il n’existait aucun composable intégré pour afficher les tooltips et plusieurs solutions alternatives circulaient en ligne. Le problème avec ces solutions était que lorsque de nouvelles versions de Jetpack Compose étaient publiées, elles pourraient s’effondrer. Ainsi, cela n’était pas idéal et la communauté se déplaçait en espérant que dans un avenir proche, on ajouterait le support pour les tooltips.
Je suis heureux de dire que depuis la version 1.1.0 de Compose Material 3, nous disposons maintenant d’un support de tooltip intégré. 👏
Bien que cela soit déjà excellent, plus d’un an s’est écoulé depuis que cette version a été publiée. Et avec les versions suivantes, l’API liée aux tooltips a également changé drastiquement.
Si vous examinez le journal des modifications, vous verrez comment les API publiques et internes ont changé. Donc, prenez en compte que lorsque vous lisez cet article, les choses ont peut-être continué à changer car tout ce qui concerne les tooltips est encore annoté avec l’annotation ExperimentalMaterial3Api::class.
❗️ La version de Material 3 utilisée pour cet article est la 1.2.1, publiée le 6 mars 2024
Types de tooltips
Nous disposons maintenant du support pour deux types de tooltips différents :
-
Tooltip simple
-
Infobulle riche
Outiltip simple
Vous pouvez utiliser le premier type pour fournir des informations sur un bouton icône qui ne serait pas clair autrement. Par exemple, vous pouvez utiliser une infobulle simple pour indiquer à l’utilisateur ce que représente le bouton icône.
Pour ajouter une infobulle à votre application, vous utilisez la composable TooltipBox. Cette composable prend plusieurs arguments:
fun TooltipBox(
positionProvider: PopupPositionProvider,
tooltip: @Composable TooltipScope.() -> Unit,
state: TooltipState,
modifier: Modifier = Modifier,
focusable: Boolean = true,
enableUserInput: Boolean = true,
content: @Composable () -> Unit,
)
Certains d’entre eux devraient vous être familiers si vous avez déjà utilisé des Composables. J’insisterai sur ceux qui ont un cas d’utilisation spécifique ici:
-
positionProvider – De type PopupPositionProvider et utilisé pour calculer la position de l’infobulle.
-
tooltip – C’est ici que vous pouvez concevoir l’interface utilisateur de l’infobulle.
-
state – Il contient l’état associé à une instance spécifique d’Infobulle. Il expose des méthodes telles que l’affichage/la suppression de l’infobulle et lors de l’instanciation d’une instance, vous pouvez déclarer si l’infobulle doit être persistante ou non (c’est-à-dire si elle doit rester affichée à l’écran jusqu’à ce qu’un utilisateur effectue une action de clic à l’extérieur de l’infobulle).
-
contenu – C’est l’UI que la tooltip affichera au-dessus ou en-dessous.
Voici un exemple d’instanciation d’une BasicTooltipBox avec tous les arguments pertinents remplis :
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun BasicTooltip() {
val tooltipPosition = TooltipDefaults.rememberPlainTooltipPositionProvider()
val tooltipState = rememberBasicTooltipState(isPersistent = false)
BasicTooltipBox(positionProvider = tooltipPosition,
tooltip = { Text("Hello World") } ,
state = tooltipState) {
IconButton(onClick = { }) {
Icon(imageVector = Icons.Filled.Favorite,
contentDescription = "Your icon's description")
}
}
}
Jetpack Compose possède une classe intégrée appelée TooltipDefaults. Vous pouvez utiliser cette classe pour vous aider à instancier les arguments qui composent une boîte de tooltip. Par exemple, vous pourriez utiliser TooltipDefaults.rememberPlainTooltipPositionProvider pour positionner correctement la tooltip par rapport à l’élément d’ancrage.
Boîte de tooltip riches
Une boîte de tooltip multimédia prend plus d’espace qu’une boîte de tooltip simple et peut être utilisée pour fournir davantage de contextes sur la fonctionnalité d’un bouton d’icône. Lorsque la tooltip est affichée, vous pouvez y ajouter des boutons et des liens pour fournir des explications supplémentaires ou des définitions.
Elle est instanciée de manière similaire à une tooltip simple, à l’intérieur d’une boîte de tooltip, mais vous utilisez la composable RichTooltip.
TooltipBox(positionProvider = tooltipPosition,
tooltip = {
RichTooltip(
title = { Text("RichTooltip") },
caretSize = caretSize,
action = {
TextButton(onClick = {
scope.launch {
tooltipState.dismiss()
tooltipState.onDispose()
}
}) {
Text("Dismiss")
}
}
) {
Text("This is where a description would go.")
}
},
state = tooltipState) {
IconButton(onClick = {
/* Événement de clic du bouton d'icône */
}) {
Icon(imageVector = tooltipIcon,
contentDescription = "Your icon's description",
tint = iconColor)
}
}
Quelques choses à remarquer sur une tooltip riches :
-
Une tooltip riches est compatible avec un carré.
-
Vous pouvez ajouter une action (c’est-à-dire un bouton) dans l’infobulle pour donner aux utilisateurs la possibilité d’en apprendre davantage.
-
Vous pouvez ajouter une logique pour fermer l’infobulle.
Cas-limites
Lorsque vous choisissez de marquer votre état de l’infobulle comme persistant, cela signifie qu’une fois que l’utilisateur interagit avec l’interface utilisateur affichant votre infobulle, elle restera visible jusqu’à ce que l’utilisateur appuie n’importe où ailleurs sur l’écran.
Si vous avez regardé l’exemple d’une infobulle riche ci-dessus, vous avez peut-être remarqué que nous avons ajouté un bouton pour fermer l’infobulle une fois cliqué.
Il y a un problème qui se produit une fois que l’utilisateur appuie sur ce bouton. Comme l’action de fermeture est effectuée sur l’infobulle, si un utilisateur veut effectuer un autre appui long sur l’élément d’interface qui invoque cette infobulle, l’infobulle ne s’affichera plus. Cela signifie que l’état de l’infobulle est persistant une fois fermée. Alors, comment régler cela?
Pour « réinitialiser » l’état de l’infobulle, nous devons appeler la méthode onDispose exposée par l’état de l’infobulle. Une fois que nous l’avons fait, l’état de l’infobulle est réinitialisé et l’infobulle s’affichera à nouveau lorsque l’utilisateur effectue un appui long sur l’élément d’interface.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RichTooltip() {
val tooltipPosition = TooltipDefaults.rememberRichTooltipPositionProvider()
val tooltipState = rememberTooltipState(isPersistent = true)
val scope = rememberCoroutineScope()
TooltipBox(positionProvider = tooltipPosition,
tooltip = {
RichTooltip(
title = { Text("RichTooltip") },
caretSize = TooltipDefaults.caretSize,
action = {
TextButton(onClick = {
scope.launch {
tooltipState.dismiss()
tooltipState.onDispose() /// <---- ICI
}
}) {
Text("Dismiss")
}
}
) {
}
},
state = tooltipState) {
IconButton(onClick = { }) {
Icon(imageVector = Icons.Filled.Call, contentDescription = "Your icon's description")
}
}
}
Un autre scénario où l’état de l’info-bulle ne se réinitialise pas est lorsqu’à la place de nous appeler nous-mêmes pour la méthode de rejet par une action de l’utilisateur, l’utilisateur clique en dehors de l’info-bulle, ce qui la fait disparaître. Cela appelle la méthode de rejet en arrière-plan et l’état de l’info-bulle est défini sur « rejeté ». Appuyer longuement sur l’élément UI pour revoir notre info-bulle ne produira rien.
Notre logique qui appelle la méthode onDispose de l’info-bulle n’est pas déclenchée, donc comment peut-on réinitialiser l’état de l’info-bulle ?
Actuellement, je n’ai pas réussi à résoudre cela. Cela pourrait être lié au MutatorMutex de l’info-bulle. Peut-être qu’avec les prochaines versions, il y aura une API pour cela. J’ai remarqué que si d’autres info-bulles sont présentes à l’écran et qu’elles sont pressées, cela réinitialise l’info-bulle précédemment cliquée.
Si vous souhaitez voir le code présenté ici, vous pouvez vous rendre dans ce dépôt GitHub
Si vous souhaitez voir des info-bulles dans une application, vous pouvez le vérifier ici.
Références
Source:
https://www.freecodecamp.org/news/how-to-use-tooltips-in-jetpack-compose/