Reprenons l’exploration de la boîte à outils de Jetpack Compose en commençant par un autre élément incontournable au sein d’une application : les images.
Créez des images
Il est courant de manipuler des images dans une application, qu'elles soient fonctionnelles, cliquables ou esthétiques. Pour les afficher, plusieurs options s'offrent à vous.
Affichez une image disponible localement
La manière la plus simple d’afficher une image disponible dans les ressources d’un projet est la suivante :
Image(
painter = painterResource(id = R.drawable.loading),
contentDescription = stringResource(id = R.string.content_description_loading)
)
Au sein du paramètre
painter
, on charge l'imagepodcast_background
depuis les ressources de l'application via la fonctionpainterResource(id)
.Le paramètre
contentDescription
sert à spécifier le texte alternatif. C’est le texte que les lecteurs d’écran utilisent pour décrire le contenu de l’image aux personnes malvoyantes et aveugles. Si l’image est uniquement décorative, il est recommandé de définir ce paramètre ànull
pour que l’élément soit ignoré par le lecteur d’écran.
Notez que les seuls paramètres obligatoires de la fonctionImage
sontpainter
etcontentDescription
. C’est d’ailleurs intéressant que ce dernier paramètre soit obligatoire, car cela nous pousse à penser à l’accessibilité dès que nous manipulons une image. Avec l'ancien UI Toolkit, ce paramètre était facultatif, ce qui augmentait le risque d'oublis et malheureusement d'applications inaccessibles disponibles sur le Play Store.
Affichez une image disponible sur internet
Pour charger une image accessible via une URL et avec Jetpack Compose, il convient d’utiliser une bibliothèque tierce. Heureusement, de nombreuses bibliothèques sont disponibles comme par exemple Glide ou Coil.
Ensemble, nous allons utiliser Coil.
Pour l’utiliser, n’oubliez pas d’ajouter la dépendance Gradle correspondante dans votre projet :
Dans le fichier
libs.versions.toml
,ajoutez
coil = "2.6.0"
dans le bloc[versions]
etajoutez
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
dans le bloc[libraries]
.
Dans le fichier
build.gradle.kts
, ajoutez :implementation(libs.coil.compose)
dans le blocdependencies
.Dans le fichier
AndroidManifest.xml
, ajoutez la permission<uses-permission android:name="android.permission.INTERNET" />
.
La bibliothèque fournit la fonction composableAsyncImage
. Voici un exemple basique que nous pouvons faire avec ce composant.
AsyncImage(
model = "https://picsum.photos/200/200.webp",
contentDescription = null,
modifier = Modifier.size(100.dp),
placeholder = painterResource(id = R.drawable.ic_launcher_background),
)
L’objectif est de charger une image asynchrone depuis une URL.
L'image est redimensionnée à 100x100 dp et utilise une image de remplacement (
placeholder
) tant que le chargement n'est pas terminé.contentDescription
estnull
pour indiquer que l'image est décorative et sera donc ignorée par un lecteur d’écran.
Créez des champs de saisie
Les champs de saisie sont essentiels dans toute application moderne, que ce soit pour des formulaires, des recherches ou des conversations. Pour répondre à ces cas d’usage, Jetpack Compose fournit 3 composants :
BasicTextField
offre une base de style neutre pour la saisie de texte.TextField
etOutlinedTextField
utilisentBasicTextField
en appliquant en plus des styles conformes aux recommandations de Material Design (nous verrons cette notion dans le chapitre suivant "Gérez le style global de l’application grâce aux thèmes").
Voici la manière la plus simple d'implémenter un champ de saisie avec Jetpack Compose avec le composantTextField
:
@Composable
fun SimpleTextField() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = {
text = it
}
)
}
Les paramètres obligatoires du composant sont
value
etonValueChange
.value
définit le texte affiché dans le champ de saisie.onValueChange
est une lambda appelée à chaque nouvelle saisie pour mettre à jour le contenu du champ.
Pour le composant de type champ de saisie sur Jetpack Compose, la grande différence avec XML, c’est que nous devons maintenir nous-même le contenu du champ.
En effet, si l'utilisateur souhaite écrire “Coucou”, il commence à saisir “C”. Avec Jetpack Compose, c’est à nous de sauvegarder la saisie dans une variable. Ensuite, il saisit “o”, et nous concaténons manuellement cette saisie à la variable qui contient déjà “C”. Ainsi de suite.
Avec Jetpack Compose, pour mémoriser l’état du composant à l'intérieur de la fonction, sans passer par ses paramètres, il faut utiliser la syntaxe suivante :
var text by remember { mutableStateOf("")}
Cette syntaxe garantit que les modifications du texte par l'utilisateur persistent et sont correctement reflétées à l'écran.
Avec l’ancien UI Toolkit, nous demandions simplement au champ de saisie ce qu’il contenait en utilisant la commandefindViewById(R.id.myEditText).getText()
. Par exemple, cela nous retournait la valeur courante, soit “Co”.
Mémorisez l’état d’un composant en interne
La syntaxevar text by remember { mutableStateOf("")}
que nous venons d’utiliser met en lumière un point important de Jetpack Compose. Jusqu'à présent, nos fonctions composables étaient dépourvues d'état interne. Leur rendu se mettait à jour uniquement par recomposition lorsque leurs paramètres changeaient. Cependant, il est parfois nécessaire de créer des fonctions composables maintenant un état interne sans utiliser de paramètres. C'est le cas du composantSimpleTextField
, qui utilise une variable internetext
.
Pour bien comprendre l’intérêt de cette déclaration, imaginons un instant si nous avions développéSimpleTextField
sans utiliservar text by remember { mutableStateOf("")}
.
@Composable
fun SimpleTextFieldNotWorking() {
var text = ""
TextField(
value = text,
onValueChange = {
text = it
}
)
}
En utilisant une variable text de typeString
, on peut supposer qu’elle se mette à jour à chaque appel de la lambdaonValueChange
, reflétant ainsi visuellement chaque saisie de l'utilisateur. Cependant, en testant ce composant, vous allez constater que chaque recomposition réinitialise la variabletext
. Ainsi implémenté, le champ de saisie de ce composant ne se met jamais à jour correctement.
Nous aborderons plus en détail la gestion des états et des effets dans le chapitre “Gérez un état ou un événement métier”. À ce stade, retenez que pour maintenir une variable interne stable à travers les recompositions et éviter sa réinitialisation, nous utilisons la fonctionremember{}
.
Créez des boutons
Peut-être que la première idée que vous vous faites d’un bouton est un élément cliquable avec du texte. Cependant, un bouton peut prendre diverses formes. Il peut :
contenir une icône ;
inclure plusieurs éléments comme du texte, des icônes, ou une image ;
être flottant, comme le Floating Action Button (bouton d’action flottant) de Material Design ;
etc.
Jetpack Compose fournit trois composants pour développer un bouton classique en s’appuyant sur Material Design :
Button
est l’implémentation de base.OutlinedButton
se base sur le composantButton
, avec un style Outlined.TextButton
se base sur le composantButton
, avec un style moins prononcé mais appliquant toujours des effets spécifiques aux boutons et une taille de clic minimale respectant les critères d’accessibilité.
Les implémentations de ces trois boutons sont similaires. Attardons-nous donc sur le premier composantButton
.
Utilisez le composantButton
La manière la plus simple d’utiliser le composant Button
est la suivante :
@Composable
fun SimpleButton() {
Button(
onClick = { /*TODO*/ },
content = {
Text(text = "Button")
},
)
}
Dans cet exemple, nous observons que le composantButton
requiert deux paramètres obligatoires :
Le paramètre
onClick
détermine l'action à exécuter lors du clic de l'utilisateur sur le bouton.Le paramètre
content
spécifie le contenu à afficher dans le bouton.
En pratique, il est courant de placer le contenu associé au paramètrecontent
en dehors des parenthèses de la fonction. Cela améliore la lisibilité du code, lui donnant un aspect d’imbrication qui permet de bien distinguer quel composant englobe quoi. Cela se traduit par :
@Composable
fun SimpleButton() {
Button(
onClick = { /*TODO*/ },
) {
Text(text = "Button")
}
}
Personnalisez le composantButton
Dans ce composantButton
, il y a une chose importante à constater. Le contenu du bouton est 100% personnalisable ! Pour comprendre ce que cela signifie, rappelez-vous d’abord ce qu’est un bouton avec l’ancien UI Toolkit :
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cliquez ici"
android:drawableStart="@drawable/ic_icon"
android:drawableEnd="@drawable/ic_arrow"
android:onClick="onButtonClick" />
En XML, un bouton a une structure prédéfinie avec des paramètres commetext
,drawableStart
, drawableEnd
, etc. Ces deux derniers paramètres permettent d’afficher uniquement une icône de petite taille soit avant ou après le texte. Si on souhaite un bouton différent de cette norme, le code devient plus complexe à réaliser. Si nous voulons, par exemple, ajouter un bouton contenant une image, il faut utiliserImageButton
.
Avec Jetpack Compose, c’est beaucoup plus flexible. Examinons à nouveau le paramètrecontent
du composantButton
de Jetpack Compose dans sa signature :
@Composable
fun Button(
/* ... */
content: @Composable RowScope.() -> Unit
)
On remarque que le type decontent
est une lambda générique de typeComposable
. Cela signifie donc que pour définir le contenu d'un bouton, il suffit de lui passer une fonctionComposable
. Cette approche permet une grande flexibilité dans la définition du contenu du bouton. C’est une différence significative par rapport au UI Toolkit original où les possibilités étaient plus limitées et structurées de manière rigide.
Créez des boutons de type icône
Dans les applications mobiles, les boutons sans texte sont courants. C’est visuellement plus attrayant et ça prend moins d’espace à l’écran. Jetpack Compose propose le composantIconButton
pour créer facilement ces boutons. Leur implémentation est plus concise que leButton
standard. En termes d’accessibilité,IconButton
offre une zone de clic minimale de 48 x 48 dp et un effet de clic circulaire.
Voici un exemple d’utilisation de ce composant :
IconButton(
onClick = /*TODO */,
) {
Icon(
imageVector = Icons.Default.Download,
contentDescription = "Télécharger",
)
}
L’utilisation du composant
IconButton
est similaire à celle du composantButton
, avec deux paramètres obligatoiresonClick
etcontent
.Le contenu défini ici est une icône de téléchargement grâce au composant
Icon
qui a deux paramètres :imageVector
pour définir l'icône etcontentDescription
pour définir le texte alternatif.Icons
est une classe fournissant des icônes prédéfinies commeIcons.Default.Download
.
Vous pouvez également utiliser vos propres icônes issues des ressources de votre projet, en utilisant alors l’autre fonctionIcon
fournie par Jetpack Compose et la fonction painterResource()
:
Icon(
painter = painterResource(id = R.drawable.ic_download),
contentDescription = "Télécharger",
)
Cette vidéo montre les étapes clés pour intégrer une image, un champ de saisie et un bouton. L’exemple de développement du composant est concret et lié à notre application BestPodcast.
À vous de jouer !
Contexte
Maintenant que vous en connaissez davantage sur les autres composants de Jetpack Compose, vous devez continuer à coder pour améliorer l’application BestPodcast. Charlie vous a partagé la marche à suivre.
Consignes
Vous avez peut-être remarqué que la classe
Podcast
dispose d’un paramètrelogoUrl
, qui comme son nom l’indique contient une URL pointant vers le logo du podcast. Affichez alors ce logo avec une taille de 64 DP x 64 DP à gauche du titre. Vous pouvez utiliser cette image comme placeholder, si vous le souhaitez.Ne soyez pas surpris si le logo ne s'affiche pas dans la prévisualisation. Pour afficher des données provenant d'Internet, utilisez un émulateur ou un appareil physique. Positionnez votre souris en haut à droite de la prévisualisation ; un boutonRun Preview apparaîtra. Cliquez dessus pour lancer la prévisualisation sur un appareil.
Une fois finalisé, votre écran contiendra une liste de podcasts précédée d’un champ de recherche permettant de filtrer cette liste. Il est encore trop tôt pour vous de concevoir la liste, mais patience, nous y viendrons. Cependant, vous pouvez déjà concevoir le champ de recherche. Nommez ce composant "PodcastSearchField". Pour le moment, vous devez simplement vous soucier du rendu visuel, nous verrons plus tard pour implémenter la logique de filtre.
Remplacez l’action de retour (appelée "IME Action") du clavier par l'action
Done
. Pour ce faire, explorez les autres paramètres du champ de saisie que vous avez utilisés précédemment.
Livrables
Votre projet sur Android Studio doit être conforme aux consignes ci-dessus.
En résumé
Pour afficher une image, on peut utiliser la fonction composable
Image
pour une image stockée localement, ou utiliser une bibliothèque tierce comme Coil pour afficher une image en ligne.Dans Jetpack Compose, nous devons gérer manuellement le maintien de la valeur d'un champ de saisie dans une variable.
Pour conserver manuellement la valeur d'un champ de saisie dans une variable, il est nécessaire de gérer l'état interne au sein de la fonction sans passer par ses paramètres, en utilisant la fonction
remember {}
.Le contenu d’un bouton est entièrement personnalisable dans Jetpack Compose.
IconButton
permet de créer des boutons icônes conformes aux normes d'accessibilité en termes de taille et de réaction au clic.
Maintenant que vous maîtrisez la manipulation des composants standards d'une application, il est temps d’explorer la personnalisation avancée grâce aux thèmes dans Jetpack Compose. Devenez un artiste numérique, comme le peintre qui compose ses toiles, en mêlant couleurs, formes et typographies.