Interopérabilité des vues dans Compose

1. Avant de commencer

Introduction

À ce stade du cours, vous savez comment créer des applications avec Compose. Vous savez aussi comment créer des applications avec des fichiers XML, des vues, des liaisons de vue et des fragments. Après avoir conçu des applications basées sur des vues, vous avez peut-être trouvé intéressant de pouvoir créer ces mêmes applications avec une UI déclarative, comme Compose. Toutefois, dans certains cas, il est préférable d'utiliser des vues plutôt que Compose. Dans cet atelier de programmation, vous allez apprendre à avoir recours à l'interopérabilité des vues pour ajouter des composants de type "View" à une application Compose moderne.

Au moment de la rédaction de cet atelier de programmation, les composants d'UI que vous allez devoir créer ne sont pas encore disponibles dans Compose. C'est donc le moment idéal d'exploiter l'interopérabilité des vues.

Conditions préalables :

Ce dont vous avez besoin

  • Un ordinateur avec un accès à Internet et Android Studio
  • Un appareil ou un émulateur
  • Le code de démarrage de l'application Juice Tracker

Objectifs de l'atelier

Dans cet atelier de programmation, vous devrez intégrer trois vues dans l'UI de Compose pour terminer l'interface de l'application Juice Tracker : une roue de sélection des couleurs, une barre d'évaluation et une bannière publicitaire. Pour créer ces composants, vous devrez utiliser l'interopérabilité des vues. L'interopérabilité des vues vous permet d'ajouter des vues à votre application en les encapsulant dans un composable.

Écrans d'application avec l'annonce test, la bottom sheet pour saisir les détails du jus de fruits et le jus d'orange ajouté à la liste.

Tutoriel du code

Dans cet atelier de programmation, vous allez utiliser l'application Juice Tracker que vous avez utilisée pour les ateliers de programmation Créer une application Android avec des vues et Ajouter Compose à une application basée sur les vues. La différence avec cette version est que le code de démarrage fourni se trouve entièrement dans Compose. À ce stade, l'application ne permet pas d'indiquer la couleur ni les notes dans la boîte de dialogue de saisie, et elle n'affiche aucune bannière publicitaire en haut de l'écran de liste.

Le répertoire bottomsheet contient tous les composants d'UI associés à la boîte de dialogue de saisie. Ce package devrait aussi contenir les composants d'UI permettant d'indiquer la couleur et la note, lorsqu'ils seront créés.

Le répertoire homescreen contient les composants d'UI hébergés par l'écran d'accueil, y compris la liste JuiceTracker. À terme, ce package devrait aussi contenir la bannière publicitaire.

Les principaux composants d'UI, tels que la bottom sheet et la liste des jus, sont hébergés dans le fichier JuiceTrackerApp.kt.

2. Télécharger le code de démarrage

Pour commencer, téléchargez le code de démarrage :

Vous pouvez également cloner le dépôt GitHub du code :

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout compose-starter
  1. Dans Android Studio, ouvrez le dossier basic-android-kotlin-compose-training-juice-tracker.
  2. Ouvrez le code de l'application Juice Tracker dans Android Studio.

3. Configuration de Gradle

Ajoutez la dépendance des annonces du service Play au fichier build.gradle.kts de l'application.

app/build.gradle.kts

android {
   ...
   dependencies {
      ...
      implementation("com.google.android.gms:play-services-ads:22.2.0")
   }
}

4. Configuration

Ajoutez la valeur suivante au fichier manifeste Android, au-dessus de la balise activity, pour activer la bannière publicitaire à des fins de test :

AndroidManifest.xml

...
<meta-data
   android:name="com.google.android.gms.ads.APPLICATION_ID"
   android:value="ca-app-pub-3940256099942544~3347511713" />

...

5. Terminer la boîte de dialogue de saisie

Dans cette section, vous allez terminer la boîte de dialogue de saisie en créant la roue de sélection des couleurs et la barre d'évaluation. La roue de sélection des couleurs est le composant qui vous permet de choisir une couleur, tandis que la barre d'évaluation vous permet de sélectionner une note pour le jus de fruits. Reportez-vous à la conception ci-dessous :

Roue de sélection des couleurs avec les différentes couleurs répertoriées

Barre d'évaluation avec 4 étoiles sur 5 sélectionnées

Créer la roue de sélection des couleurs

Pour implémenter une roue de sélection des couleurs dans Compose, vous devez utiliser la classe Spinner. Spinner est un composant View, et non un composable. Il doit donc être implémenté à l'aide d'une interopérabilité.

  1. Dans le répertoire bottomsheet, créez un fichier nommé ColorSpinnerRow.kt.
  2. Dans le fichier nommé SpinnerAdapter, créez une classe.
  3. Dans le constructeur de SpinnerAdapter, définissez un paramètre de rappel appelé onColorChange qui utilise un paramètre Int. SpinnerAdapter gère les fonctions de rappel pour le Spinner.

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit){
}
  1. Implémentez l'interface AdapterView.OnItemSelectedListener.

En implémentant cette interface, vous pouvez définir le comportement de clic pour la roue de sélection des couleurs. Vous configurerez cet adaptateur dans un composable ultérieurement.

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
}
  1. Implémentez les fonctions membres AdapterView.OnItemSelectedListener : onItemSelected() et onNothingSelected().

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
   override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        TODO("Not yet implemented")
    }

    override fun onNothingSelected(parent: AdapterView<*>?) {
        TODO("Not yet implemented")
    }
}
  1. Modifiez la fonction onItemSelected() pour qu'elle appelle la fonction de rappel onColorChange(). De cette manière, lorsque vous sélectionnerez une couleur, l'application mettra à jour la valeur sélectionnée dans l'UI.

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
   override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        onColorChange(position)
    }

    override fun onNothingSelected(parent: AdapterView<*>?) {
        TODO("Not yet implemented")
    }
}
  1. Modifiez la fonction onNothingSelected() pour définir la couleur sur 0. Ainsi, lorsque vous ne sélectionnez rien, la couleur par défaut est la première (rouge).

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
   override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        onColorChange(position)
    }

    override fun onNothingSelected(parent: AdapterView<*>?) {
        onColorChange(0)
    }
}

Le SpinnerAdapter, qui définit le comportement de la roue de sélection des couleurs via des fonctions de rappel, existe déjà. Vous devez maintenant créer le contenu de la roue de sélection des couleurs et y insérer des données.

  1. Dans le fichier ColorSpinnerRow.kt, mais en dehors de la classe SpinnerAdapter, créez un composable appelé ColorSpinnerRow.
  2. Dans la signature de la méthode ColorSpinnerRow(), ajoutez un paramètre Int pour la position de la roue de sélection des couleurs, une fonction de rappel qui utilise un paramètre Int et un modificateur.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
}
  1. Dans la fonction, créez un tableau avec les ressources de chaîne de couleurs de jus à l'aide de l'énumération JuiceColor. Ce tableau servira de contenu pour remplir la roue de sélection des couleurs.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   val juiceColorArray =
        JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }

}
  1. Ajoutez un composable InputRow() et transmettez la ressource de chaîne de couleurs pour le libellé d'entrée et un modificateur qui définit la ligne d'entrée où apparaît le Spinner.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   val juiceColorArray =
        JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
   InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
   }
}

Vous allez maintenant créer le Spinner. Comme Spinner est une classe View, l'API d'interopérabilité des vues de Compose doit être utilisée pour l'encapsuler dans un composable. Pour ce faire, servez-vous du composable AndroidView.

  1. Pour utiliser un Spinner dans Compose, créez un composable AndroidView() dans le corps du lambda InputRow. Le composable AndroidView() crée un élément ou une hiérarchie View dans un composable.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   val juiceColorArray =
        JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
   InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
      AndroidView()
   }
}

Le composable AndroidView utilise trois paramètres :

  • Le lambda factory, qui est une fonction créant la vue
  • Le rappel update, qui est appelé lorsque la vue créée dans le factory est gonflée
  • Un composable modifier

575cce0368632b79.png

  1. Pour implémenter l'AndroidView, commencez par transmettre un modificateur et remplir la largeur maximale de l'écran.
  2. Transmettez un lambda pour le paramètre factory.
  3. Le lambda factory utilise Context comme paramètre. Créez une classe Spinner et transmettez le contexte.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      AndroidView(
         modifier = Modifier.fillMaxWidth(),
         factory = { context ->
            Spinner(context)
         }
      )
   }
}

Tout comme un RecyclerView.Adapter fournit des données à un RecyclerView, un ArrayAdapter fournit des données à un Spinner. Le Spinner nécessite un adaptateur pour héberger le tableau de couleurs.

  1. Définissez l'adaptateur à l'aide d'un ArrayAdapter. ArrayAdapter nécessite un contexte, une mise en page XML et un tableau. Transmettez simple_spinner_dropdown_item pour la mise en page. Cette mise en page est fournie par défaut avec Android.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      AndroidView(
         ​​modifier = Modifier.fillMaxWidth(),
         factory = { context ->
             Spinner(context).apply {
                 adapter =
                     ArrayAdapter(
                         context,
                         android.R.layout.simple_spinner_dropdown_item,
                         juiceColorArray
                     )
             }
         }
      )
   }
}

Le rappel factory renvoie une instance de la vue créée dans celle-ci. update est un rappel qui accepte un paramètre du même type renvoyé par le rappel factory. Ce paramètre est une instance de la vue qui est gonflée par le factory. Dans ce cas, comme un Spinner a été créé dans la fabrique, l'instance du Spinner est accessible dans le corps du lambda update.

  1. Ajoutez un rappel update qui transmet un spinner. Utilisez le rappel fourni dans l'update pour appeler la méthode setSelection().

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      //...
         },
         update = { spinner ->
             spinner.setSelection(colorSpinnerPosition)
             spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
         }
      )
   }
}
  1. Utilisez le SpinnerAdapter que vous avez créé précédemment pour définir un rappel onItemSelectedListener() dans l'update.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      AndroidView(
         // ...
         },
         update = { spinner ->
             spinner.setSelection(colorSpinnerPosition)
             spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
         }
      )
   }
}

Le code de la roue de sélection des couleurs est maintenant terminé.

  1. Ajoutez la fonction utilitaire suivante pour obtenir l'index d'énumération JuiceColor. Vous l'utiliserez à l'étape suivante.
private fun findColorIndex(color: String): Int {
   val juiceColor = JuiceColor.valueOf(color)
   return JuiceColor.values().indexOf(juiceColor)
}
  1. Implémentez le ColorSpinnerRow dans le composable SheetForm du fichier EntryBottomSheet.kt. Placez la roue de sélection des couleurs après le texte "Description" et au-dessus des boutons.

bottomsheet/EntryBottomSheet.kt

...
@Composable
fun SheetForm(
   juice: Juice,
   onUpdateJuice: (Juice) -> Unit,
   onCancel: () -> Unit,
   onSubmit: () -> Unit,
   modifier: Modifier = Modifier,
) {
   ...
   TextInputRow(
            inputLabel = stringResource(R.string.juice_description),
            fieldValue = juice.description,
            onValueChange = { description -> onUpdateJuice(juice.copy(description = description)) },
            modifier = Modifier.fillMaxWidth()
        )
        ColorSpinnerRow(
            colorSpinnerPosition = findColorIndex(juice.color),
            onColorChange = { color ->
                onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
            }
        )
   ButtonRow(
            modifier = Modifier
                .align(Alignment.End)
                .padding(bottom = dimensionResource(R.dimen.padding_medium)),
            onCancel = onCancel,
            onSubmit = onSubmit,
            submitButtonEnabled = juice.name.isNotEmpty()
        )
    }
}

Créer le composable de saisie de la note

  1. Créez un fichier appelé RatingInputRow.kt dans le répertoire bottomsheet.
  2. Dans le fichier RatingInputRow.kt, créez un composable appelé RatingInputRow().
  3. Dans la signature de la méthode, transmettez un Int pour la note, un rappel avec un paramètre Int afin de gérer un changement de sélection, ainsi qu'un modificateur.

bottomsheet/RatingInputRow.kt

@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
}
  1. Comme pour le ColorSpinnerRow, ajoutez un InputRow au composable contenant un AndroidView, tel qu'illustré dans l'exemple de code suivant.

bottomsheet/RatingInputRow.kt

@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
    InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
        AndroidView(
            factory = {},
            update = {}
        )
    }
}
  1. Dans le corps du lambda factory, créez une instance de la classe RatingBar, qui fournit le type de barre d'évaluation nécessaire à cette conception. Définissez la valeur stepSize sur 1f pour que la note ne puisse être qu'un nombre entier.

bottomsheet/RatingInputRow.kt

@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
    InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
        AndroidView(
            factory = { context ->
                RatingBar(context).apply {
                    stepSize = 1f
                }
            },
            update = {}
        )
    }
}

Lorsque la vue sera gonflée, la note sera définie. Rappelez-vous que le factory renvoie l'instance RatingBar au rappel de mise à jour.

  1. Utilisez la note transmise au composable pour définir la note de l'instance RatingBar dans le corps du lambda update.
  2. Lorsqu'une nouvelle note est définie, utilisez le rappel RatingBar pour appeler la fonction de rappel onRatingChange() afin de mettre à jour la note dans l'UI.

bottomsheet/RatingInputRow.kt

@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
    InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
        AndroidView(
            factory = { context ->
                RatingBar(context).apply {
                    stepSize = 1f
                }
            },
            update = { ratingBar ->
                ratingBar.rating = rating.toFloat()
                ratingBar.setOnRatingBarChangeListener { _, _, _ ->
                    onRatingChange(ratingBar.rating.toInt())
                }
            }
        )
    }
}

Le composable de saisie de la note est maintenant terminé.

  1. Utilisez le composable RatingInputRow() dans l'EntryBottomSheet. Placez-le après la roue de sélection des couleurs et au-dessus des boutons.

bottomsheet/EntryBottomSheet.kt

@Composable
fun SheetForm(
    juice: Juice,
    onUpdateJuice: (Juice) -> Unit,
    onCancel: () -> Unit,
    onSubmit: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Column(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(4.dp)
    ) {
        ...
        ColorSpinnerRow(
            colorSpinnerPosition = findColorIndex(juice.color),
            onColorChange = { color ->
                onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
            }
        )
        RatingInputRow(
            rating = juice.rating,
            onRatingChange = { rating -> onUpdateJuice(juice.copy(rating = rating)) }
        )
        ButtonRow(
            modifier = Modifier.align(Alignment.CenterHorizontally),
            onCancel = onCancel,
            onSubmit = onSubmit,
            submitButtonEnabled = juice.name.isNotEmpty()
        )
    }
}

Créer la bannière publicitaire

  1. Dans le package homescreen, créez un fichier appelé AdBanner.kt.
  2. Dans le fichier AdBanner.kt, créez un composable appelé AdBanner().

Contrairement aux composables précédents que vous avez créés, l'AdBanner ne nécessite aucune entrée. Par conséquent, vous n'avez pas besoin de l'encapsuler dans un composable InputRow. Toutefois, un AndroidView est nécessaire.

  1. Essayez de créer la bannière vous-même à l'aide de la classe AdView. Assurez-vous de définir la taille d'annonce sur AdSize.BANNER et l'ID de bloc d'annonces sur "ca-app-pub-3940256099942544/6300978111".
  2. En cas de gonflement de l'AdView, chargez une annonce en utilisant l'AdRequest Builder.

homescreen/AdBanner.kt

@Composable
fun AdBanner(modifier: Modifier = Modifier) {
    AndroidView(
        modifier = modifier,
        factory = { context ->
            AdView(context).apply {
                setAdSize(AdSize.BANNER)
                // Use test ad unit ID
                adUnitId = "ca-app-pub-3940256099942544/6300978111"
            }
        },
        update = { adView ->
            adView.loadAd(AdRequest.Builder().build())
        }
    )
}
  1. Placez l'AdBanner avant le JuiceTrackerList dans le JuiceTrackerApp. Le JuiceTrackerList est déclaré à la ligne 83.

ui/JuiceTrackerApp.kt

...
AdBanner(
   Modifier
       .fillMaxWidth()
       .padding(
           top = dimensionResource(R.dimen.padding_medium),
           bottom = dimensionResource(R.dimen.padding_small)
       )
)

JuiceTrackerList(
    juices = trackerState,
    onDelete = { juice -> juiceTrackerViewModel.deleteJuice(juice) },
    onUpdate = { juice ->
        juiceTrackerViewModel.updateCurrentJuice(juice)
        scope.launch {
            bottomSheetScaffoldState.bottomSheetState.expand()
        }
     },
)

6. Télécharger le code de solution

Pour télécharger le code de cet atelier de programmation terminé, utilisez les commandes Git suivantes :

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout compose-with-views

Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.

Si vous souhaitez voir le code de solution, affichez-le sur GitHub.

7. En savoir plus

8. Conclusion

Ce cours se termine ici, mais ce n'est que le début de votre parcours dans le développement d'applications Android !

Vous avez appris à créer des applications à l'aide de Jetpack Compose, un kit d'UI moderne permettant de concevoir des applications Android natives. Tout au long de ce cours, vous avez créé des applications avec des listes, avec un ou plusieurs écrans, et vous avez navigué entre leurs composants. Vous avez appris à concevoir des applications interactives, à faire en sorte qu'elles répondent aux entrées utilisateur et à mettre à jour l'UI. Vous avez appliqué Material Design et avez utilisé des couleurs, des formes et la typographie pour personnaliser votre application. Vous vous êtes également servi de Jetpack et d'autres bibliothèques tierces pour planifier des tâches, récupérer des données à partir de serveurs distants, les conserver en local et plus encore.

Grâce à ce cours, vous savez non seulement comment créer des applications attrayantes et responsives à l'aide de Jetpack Compose, mais vous disposez aussi des connaissances et des compétences nécessaires pour concevoir des applications Android efficaces, faciles à gérer et visuellement attrayantes. Ces principes de base vous aideront à continuer à développer vos compétences dans le domaine du Modern Android Development et de Compose.

Nous vous remercions d'avoir suivi et terminé ce cours. Nous encourageons chacun d'entre vous à poursuivre votre apprentissage et à vous perfectionner grâce à des ressources supplémentaires telles que la documentation pour les développeurs Android, le cours Jetpack Compose pour les développeurs Android, le document sur l'architecture moderne des applications Android, le blog des développeurs Android, d'autres ateliers de programmation ainsi que des exemples de projets.

Enfin, n'oubliez pas de partager ce que vous avez créé sur les réseaux sociaux et d'utiliser le hashtag #AndroidBasics pour que le reste de la communauté des développeurs Android et nous-mêmes puissions suivre votre évolution.

À vos claviers !