• 12 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 01/10/2024

Créez des images, des champs de saisies et des boutons

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ètrepainter, on charge l'imagepodcast_backgrounddepuis les ressources de l'application via la fonction painterResource(id)

  • Le paramètrecontentDescriptionsert à 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 ànullpour que l’élément soit ignoré par le lecteur d’écran.

Notez que les seuls paramètres obligatoires de la fonctionImagesontpainteretcontentDescription. 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 :

  1. Dans le fichierlibs.versions.toml

    • ajoutezcoil = "2.6.0"dans le bloc[versions]et 

    • ajoutezcoil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }  dans le bloc[libraries]

  2. Dans le fichierbuild.gradle.kts, ajoutez :implementation(libs.coil.compose) dans le bloc  dependencies.

  3. Dans le fichierAndroidManifest.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é. 

  • contentDescriptionestnullpour 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 :

  • BasicTextFieldoffre une base de style neutre pour la saisie de texte.

  • TextField etOutlinedTextField utilisentBasicTextFielden 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 sontvalueetonValueChange.

  • valuedéfinit le texte affiché dans le champ de saisie.

  • onValueChangeest 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 :

  • Buttonest l’implémentation de base.

  • OutlinedButtonse 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é.

Trois types de boutons utilisés dans l'interface utilisateur d'une application : un bouton bleu avec le texte 'Button'. Au milieu, un bouton avec un contour gris et un fond blanc, portant l'inscription 'Outlined button'. En bas, un bouton sans contour.
Style des boutons fournis par Jetpack Compose

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 composantButtonrequiert deux paramètres obligatoires :

  • Le paramètreonClickdétermine l'action à exécuter lors du clic de l'utilisateur sur le bouton.

  • Le paramètrecontentspécifie le contenu à afficher dans le bouton.

 En pratique, il est courant de placer le contenu associé au paramètrecontenten 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" />
Une image est indiqué à droite ou à gauche du composant texte du bouton.
Illustration de la structure du composant Button avec le UI Toolkit original

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ètrecontentdu composantButtonde Jetpack Compose dans sa signature :

@Composable
fun Button(
    /* ... */
    content: @Composable RowScope.() -> Unit
)
Le composant button est liée à une lambda générique composable rowscope.()
Illustration de la structure du composant Button avec Jetpack Compose

On remarque que le type decontentest 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 composantIconButtonpour créer facilement ces boutons. Leur implémentation est plus concise que leButtonstandard. En termes d’accessibilité,IconButtonoffre 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 composantIconButtonest similaire à celle du composantButton, avec deux paramètres obligatoiresonClicketcontent

  • Le contenu défini ici est une icône de téléchargement grâce au composantIconqui a deux paramètres :imageVectorpour définir l'icône et contentDescriptionpour 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 fonctionIconfournie 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

  1. Vous avez peut-être remarqué que la classePodcastdispose 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. 

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

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

  4. Remplacez l’action de retour (appelée "IME Action") du clavier par l'actionDone. 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 composableImagepour 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.

  • IconButtonpermet 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.

Exemple de certificat de réussite
Exemple de certificat de réussite