Présentation de Compose pour la télévision

1. Avant de commencer

Compose pour la télévision est le dernier framework d'UI permettant de développer des applications exécutées sur Android TV. Il offre tous les avantages de Jetpack Compose pour les applis TV afin de vous permettre de créer plus facilement des interfaces utilisateur attrayantes et fonctionnelles. Voici quelques avantages spécifiques de Compose pour la télévision :

  • Flexibilité : Compose permet de créer n'importe quel type d'UI, qu'il s'agisse de mises en page simples ou d'animations complexes. Les composants sont prêts à l'emploi, mais vous pouvez aussi les personnaliser et les façons selon les besoins de votre application.
  • Développement accéléré et simplifié : Compose étant compatible avec le code existant, les développeurs peuvent créer des applications avec moins de code.
  • Intuitivité : Compose utilise une syntaxe déclarative qui permet de modifier votre UI de manière intuitive, mais aussi de déboguer, comprendre et examiner votre code.

Parmi les cas d'utilisation courants des applications TV, on retrouve la consommation de contenus multimédias. Les utilisateurs parcourent les catalogues de contenus et sélectionnent ceux qu'ils souhaitent regarder. Il peut s'agir d'un film, d'une série TV ou d'un podcast. Après avoir sélectionné un contenu, l'utilisateur peut souhaiter en savoir plus sur celui-ci, par exemple par le biais d'une brève description, de la durée de la lecture et du nom des créateurs. Dans cet atelier de programmation, vous découvrirez comment implémenter un navigateur de catalogue et un écran d'informations avec Compose pour la télévision.

Conditions préalables

  • Connaissances de la syntaxe du langage Kotlin, y compris les lambdas.
  • Vous disposez d'une expérience de base avec Compose. Si vous ne connaissez pas Compose, suivez l'atelier de programmation Principes de base de Jetpack Compose.
  • Vous disposez de connaissances de base sur les composables et les modificateurs.
  • N'importe lequel des appareils suivants devra exécuter l'application exemple :
    • Un appareil Android TV
    • Un appareil virtuel Android avec un profil dans la catégorie de définition des appareils TV

Objectif de l'atelier

  • Une application de lecteur vidéo avec un navigateur de catalogue et un écran d'informations
  • Un navigateur de catalogue affichant une liste de vidéos que les utilisateurs peuvent choisir. Il se présente comme suit :

Le navigateur de catalogue affiche une sélection de films\nau-dessous d'un carrousel.\nL'écran affiche également une liste de films pour chaque catégorie.

  • Un écran d'informations affichant les métadonnées de la vidéo sélectionnée, comme le titre, la description et la durée. Il se présente comme suit :

L'écran d'informations affiche les métadonnées du film,\ny compris le titre, le studio de production et une brève description.\nLes métadonnées sont affichées sur l'image de fond du film.

Ce dont vous avez besoin

  • La dernière version d'Android Studio
  • Un appareil Android TV ou un appareil virtuel dans la catégorie "Téléviseur"

2. Configuration

Afin d'obtenir le code contenant la thématisation et la configuration de base pour cet atelier de programmation, effectuez l'une des opérations suivantes :

$ git clone https://github.com/android/tv-codelabs.git

La branche main contient le code de démarrage et la branche solution contient le code de solution.

  • Téléchargez le fichier main.zip, qui contient le code de démarrage, et le fichier solution.zip, qui contient le code de solution.

Maintenant que vous avez téléchargé le code, ouvrez le projet IntroductionToComposeForTV dans Android Studio. Vous êtes prêt à commencer.

3. Implémenter l'écran du navigateur de catalogue

Grâce au navigateur, les utilisateurs peuvent parcourir les catalogues de films. Vous allez implémenter le navigateur de catalogue en tant que fonction composable. La fonction composable CatalogBrowser se trouve dans le fichier CatalogBrowser.kt. Vous allez implémenter le navigateur de catalogue dans cette fonction composable.

Le code de démarrage contient une classe ViewModel appelée CatalogBrowserViewModel. Elle comporte plusieurs attributs et méthodes pour récupérer des objets Movie décrivant le contenu du film. Vous allez implémenter un navigateur de catalogue en récupérant des objets Movie.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
}

Afficher les noms des catégories

Vous pouvez accéder à une liste de catégories avec l'attribut catalogBrowserViewModel.categoryList. il s'agit du flux d'une liste Category. Le flux est collecté en tant qu'objet Compose State en appelant la méthode collectAsStateWithLifecycle. Un objet Category possède l'attribut name. Il s'agit d'une valeur String correspondant au nom de la catégorie.

Pour afficher les noms des catégories, procédez comme suit :

  1. Dans Android Studio, ouvrez le fichier CatalogBrowser.kt du code de démarrage, puis ajoutez une fonction composable LazyColumn à la fonction composable CatalogBrowser.
  2. Appelez la méthode catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle() pour collecter le flux en tant qu'objet State.
  3. Déclarez categoryList en tant que propriété déléguée de l'objet State créé à l'étape précédente.
  4. Appelez la fonction items avec la variable categoryList en tant que paramètre.
  5. Appelez la fonction composable Text avec le nom de la catégorie comme paramètre transmis en tant qu'argument du lambda.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(modifier = modifier) {
        items(categoryList) { category ->
            Text(text = category.name)
        }
    }
}

Afficher la liste de contenu pour chaque catégorie

Un objet Category possède un autre attribut appelé movieList. Il s'agit d'une liste d'objets Movie correspondant aux films qui appartiennent à la catégorie.

Pour afficher la liste de contenu de chaque catégorie, procédez comme suit :

  1. Ajoutez la fonction composable LazyRow, puis transmettez-lui un lambda.
  2. Dans le lambda, appelez la fonction items avec category et la valeur d'attribut movieList, puis transmettez-lui un lambda.
  3. Dans le lambda transmis à la fonction items, appelez la fonction composable MovieCard avec un objet Movie.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(modifier = modifier) {
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow {
                items(category.movieList) {movie ->
                    MovieCard(movie = movie)
                }
            }
        }
    }
}

Facultatif : ajuster la mise en page

  1. Pour définir l'écart entre les catégories, transmettez un objet Arrangement à la fonction composable LazyColumn avec le paramètre verticalArrangement. L'objet Arrangement est créé en appelant la méthode Arrangement#spacedBy.
  2. Pour définir l'écart entre les fiches de films, transmettez un objet Arrangement à la fonction composable LazyRow avec le paramètre horizontalArrangement.
  3. Pour définir un retrait au niveau de la colonne, transmettez un objet PaddingValue avec le paramètre contentPadding.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifeCycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie)
                }
            }
        }
    }
}

4. Implémenter l'écran d'informations

L'écran d'informations présente les détails du film sélectionné. Le fichier Details.kt contient une fonction composable Details. Vous allez ajouter du code à cette fonction pour implémenter l'écran d'informations.

Details.kt

@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
}

Afficher le titre, le nom du studio et la description du film

Un objet Movie possède les trois attributs de chaîne de caractères suivants comme métadonnées du film :

  • title : titre du film
  • studio : nom du studio qui a produit le film
  • description : bref résumé du film

Pour afficher ces métadonnées sur l'écran d'informations, procédez comme suit :

  1. Ajoutez une fonction composable Column, puis définissez une marge verticale de 32 dp et une marge horizontale de 48 dp autour de la colonne avec l'objet Modifier créé par la méthode Modifier.padding.
  2. Ajoutez une fonction composable Text pour afficher le titre du film.
  3. Ajoutez une fonction composable Text pour afficher le nom du studio.
  4. Ajoutez une fonction composable Text pour afficher la description du film.

Details.kt

@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Column(
        modifier = Modifier
            .padding(vertical = 32.dp, horizontal = 48.dp)
    ) {
        Text(text = movie.title)
        Text(text = movie.studio)
        Text(text = movie.description)
    }
}

L'objet Modifier spécifié dans le paramètre de la fonction composable Details est utilisé dans la tâche suivante.

Afficher l'image de fond associée à un objet Movie donné

Un objet Movie possède un attribut backgroundImageUrl qui spécifie l'emplacement de l'image de fond du film décrit par l'objet.

Pour afficher l'image de fond d'un film donné, procédez comme suit :

  1. Ajoutez une fonction composable Box en tant que wrapper de la fonction composable Column avec l'objet modifier transmis via la fonction composable Details.
  2. Dans la fonction composable Box, appelez la méthode fillMaxSize de l'objet modifier pour que la fonction composable Box remplisse la taille maximale pouvant être allouée à la fonction composable Details.
  3. Ajoutez une fonction composable AsyncImage avec les paramètres suivants à la fonction composable Box :
  • Définissez la valeur de l'attribut backgroundImageUrl de l'objet Movie donné sur un paramètre model.
  • Transmettez la valeur null à un paramètre contentDescription.
  • Transmettez un objet ContentScale.Crop à un paramètre contentScale. Pour afficher les différentes options ContentScale, consultez la section Échelle de contenu.
  • Transmettez la valeur renvoyée par la méthode Modifier.fillMaxSize au paramètre modifier.

Details.kt

@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxSize()) {
        AsyncImage(
            model = movie.cardImageUrl,
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxSize()
        )
        Column {
            Text(
                text = movie.title,
            )
            Text(
                text = movie.studio,
            )
            Text(text = movie.description)
        }
    }
}

Utiliser l'objet MaterialTheme pour une thématisation cohérente

L'objet MaterialTheme contient des fonctions qui font référence à des valeurs de thème actuelles, comme celles des classes Typography et ColorScheme.

Pour faire référence à l'objet MaterialTheme dans le cadre d'une thématisation cohérente, procédez comme suit :

  1. Définissez la propriété MaterialTheme.typography.displayMedium sur le style de texte du titre du film.
  2. Définissez la propriété MaterialTheme.typography.bodySmall sur le style de texte des deuxièmes fonctions composables Text.
  3. Définissez la propriété MaterialTheme.colorScheme.background sur la couleur d'arrière-plan de la fonction composable Column avec la méthode Modifier.background.

Details.kt

@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxSize()) {
        AsyncImage(
            model = movie.cardImageUrl,
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxSize()
        )
        Column(
            modifier = Modifier
                .background(MaterialTheme.colorScheme.background),
        ) {
            Text(
                text = movie.title,
                style = MaterialTheme.typography.displayMedium,
            )
            Text(
                text = movie.studio,
                style = MaterialTheme.typography.bodySmall,
            )
            Text(text = movie.description)
        }
    }
}

Facultatif : ajuster la mise en page

Pour ajuster la mise en page de la fonction composable Details, procédez comme suit :

  1. Définissez la fonction composable Box pour utiliser la totalité de l'espace disponible avec le modificateur fillMaxSize.
  2. Définissez l'arrière-plan de la fonction composable Box avec le modificateur background pour remplir l'arrière-plan avec un dégradé linéaire créé en appelant la fonction Brush.linearGradient avec une liste d'objets Color contenant les valeurs MaterialTheme.colorScheme.background et Color.Transparent.
  3. Définissez la marge horizontale 48.dp et la marge verticale 24.dp autour de la fonction composable Column avec le modificateur padding.
  4. Définissez la largeur de la fonction composable Column avec le modificateur width créé en appelant la fonction Modifier.width avec la valeur 0.5f.
  5. Ajoutez l'espace 8.dp entre la deuxième fonction composable Text et le troisième composable Text avec Spacer. La hauteur de la fonction composable Spacer est spécifiée avec le modificateur height créé avec la fonction Modifier.height.

Details.kt

@Composable
fun Details(movie: Movie, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxSize()) {
        AsyncImage(
            model = movie.cardImageUrl,
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxSize()
        )
        Box(
            modifier = Modifier
                .background(
                    Brush.linearGradient(
                        listOf(
                            MaterialTheme.colorScheme.background,
                            Color.Transparent
                        )
                    )
                )
                .fillMaxSize()
        ) {
            Column(
                modifier = Modifier
                    .padding(horizontal = 48.dp, vertical = 24.dp)
                    .fillMaxWidth(0.5f)
            ) {
                Text(
                    text = movie.title,
                    style = MaterialTheme.typography.displayMedium,
                )
                Text(
                    text = movie.studio,
                    style = MaterialTheme.typography.bodySmall,
                )
                Spacer(modifier = Modifier.height(8.dp))
                Text(
                    text = movie.description,
                )
            }
        }
    }
}

5. Permettre la navigation entre les écrans

Vous disposez à présent du navigateur de catalogue et de l'écran d'informations. Lorsqu'un utilisateur sélectionne un contenu à partir du navigateur de catalogue, celui-ci doit passer à l'écran d'informations. Pour ce faire, vous utilisez le modificateur clickable pour ajouter un écouteur event à la fonction composable MovieCard. Lorsque vous appuyez sur le bouton central de la croix directionnelle, la méthode CatalogBrowserViewModel#showDetails est appelée avec l'objet Movie associé à la fonction composable MovieCard en tant qu'argument.

  1. Ouvrez le fichier com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Transmettez une fonction lambda à la fonction composable MovieCard avec un paramètre onClick.
  3. Appelez le rappel onMovieSelected avec l'objet Movie associé à la fonction composable MovieCard.

CatalogBrowser.kt

@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}

6. Ajouter un carrousel au navigateur de catalogue pour mettre en avant une sélection de contenus

Le carrousel est un élément d'UI couramment adapté. Il met automatiquement à jour les diapositives après une durée spécifiée. Cette fonctionnalité est généralement utilisée pour mettre en avant des sélections de contenus.

Pour ajouter un carrousel au navigateur de catalogue afin de mettre en avant une sélection de films dans la liste de contenus, procédez comme suit :

  1. Ouvrez le fichier com.example.tvcomposeintroduction.ui.screens.CatalogBrowser.
  2. Appelez la fonction item pour ajouter un élément à la fonction composable LazyColumn.
  3. Déclarez featuredMovieList en tant que propriété déléguée dans le lambda transmis à la fonction item. Définissez ensuite l'objet State comme délégué ; il est collecté à partir de l'attribut catalogBrowserViewModel.featuredMovieList.
  4. Appelez la fonction composable Carousel dans la fonction item, puis transmettez les paramètres suivants :
  • La taille de la variable featuredMovieList via un paramètre slideCount.
  • Un objet Modifier permettant de spécifier la taille du carrousel à l'aide des méthodes Modifier.fillMaxWidth et Modifier.height. La fonction composable Carousel utilise une hauteur de 376 dp lorsqu'elle transmet une valeur 376.dp à la méthode Modifier.height.
  • Un lambda appelé avec une valeur entière qui spécifie l'index de l'élément visible du carrousel.
  1. Récupérez l'objet Movie à partir de la variable featuredMovieList et de la valeur d'index donnée.
  2. Ajoutez une fonction composable Box à la fonction composable Carousel.
  3. Ajoutez une fonction composable Text à la fonction composable Box pour afficher le titre du film.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp)
            ) { indexOfCarouselSlide ->
                val featuredMovie =
                    featuredMovieList[indexOfCarouselSlide]
                Box {
                    Text(text = featuredMovie.title)
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}

Afficher des images de fond

La fonction composable Box place un composant sur un autre. Pour en savoir plus, consultez Principes de base de la mise en page.

Pour afficher des images de fond, procédez comme suit :

  1. Appelez la fonction composable AsyncImage pour charger l'image de fond associée à l'objet Movie avant la fonction composable Text.
  2. Mettez à jour la position et le style du texte de la fonction composable Text pour une meilleure visibilité.
  3. Définissez un espace réservé dans la fonction composable AsyncImage pour éviter un décalage de mise en page. Le code de démarrage contient un espace réservé en tant que drawable auquel vous pouvez faire référence avec R.drawable.placeholder.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                Box{
                    AsyncImage(
                        model = featuredMovie.backgroundImageUrl,
                        contentDescription = null,
                        placeholder = painterResource(
                            id = R.drawable.placeholder
                        ),
                        contentScale = ContentScale.Crop,
                        modifier = Modifier.fillMaxSize(),
                    )
                    Text(text = featuredMovie.title)
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}

Ajouter une transition vers l'écran d'informations

Vous pouvez ajouter un Button au carrousel afin que les utilisateurs puissent déclencher une transition vers l'écran d'informations en cliquant sur le bouton.

Pour permettre aux utilisateurs d'afficher les informations du film dans le carrousel visible sur l'écran d'informations, procédez comme suit :

  1. Appelez la fonction composable Column dans le composable Box du composable Carousel.
  2. Déplacez le composable Text dans Carousel vers la fonction composable Column.
  3. Appelez la fonction composable Button après la fonction composable Text dans la fonction composable Column.
  4. Appelez la fonction composable Text dans la fonction composable Button avec la valeur renvoyée de la fonction stringResource appelée avec R.string.show_details.
  5. Appelez la fonction onMovieSelected avec la variable featuredMovie dans le lambda transmis au paramètre onClick de la fonction composable Button.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by
    catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        item {
            val featuredMovieList by catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()
            Carousel(
                slideCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                Box {
                    AsyncImage(
                        model = featuredMovie.backgroundImageUrl,
                        contentDescription = null,
                        placeholder = painterResource(
                            id = R.drawable.placeholder
                        ),
                        contentScale = ContentScale.Crop,
                        modifier = Modifier.fillMaxSize(),
                    )
                    Column {
                        Text(text = featuredMovie.title)
                        Button(onClick = { onMovieSelected(featuredMovie) }) {
                            Text(text = stringResource(id = R.string.show_details))
                        }
                    }
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(movie = movie, onClick = { onMovieSelected(movie) })
                }
            }
        }
    }
}

Facultatif : ajuster la mise en page

Pour ajuster la mise en page du carrousel, procédez comme suit :

  1. Attribuez la valeur backgroundColor avec la valeur MaterialTheme.colorScheme.background dans la fonction composable Carousel.
  2. Encapsulez la fonction composable Column avec un composable Box.
  3. Transmettez la valeur Alignment.BottomStart au paramètre contentAlignment du composant Box.
  4. Transmettez le modificateur fillMaxSize au paramètre de modificateur de la fonction composable Box. Le modificateur fillMaxSize est créé avec la fonction Modifier.fillMaxSize().
  5. Appelez la méthode drawBehind() sur le modificateur fillMaxSize transmis au composable Box.
  6. Dans le lambda transmis au modificateur drawBehind, attribuez la valeur brush avec un objet Brush créé en appelant la fonction Brush.linearGradient avec une liste de deux objets Color. Cette liste est créée en appelant la fonction listOf avec les valeurs backgroundColor et Color.Transparent.
  7. Appelez drawRect avec l'objet brush dans le lambda transmis au modificateur drawBehind pour créer une couche de contour sur l'image de fond.
  8. Spécifiez la marge intérieure de la fonction composable Column avec le modificateur padding créé en appelant Modifier.padding avec la valeur 20.dp.
  9. Ajoutez une fonction composable Spacer avec une valeur 20.dp entre le composable Text et le composable Button dans la fonction composable Column.

CatalogBrowser.kt

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun CatalogBrowser(
    modifier: Modifier = Modifier,
    catalogBrowserViewModel: CatalogBrowserViewModel = hiltViewModel(),
    onMovieSelected: (Movie) -> Unit = {}
) {
    val categoryList by catalogBrowserViewModel.categoryList.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(32.dp),
        contentPadding = PaddingValues(horizontal = 58.dp, vertical = 36.dp)
    ) {
        item {
            val featuredMovieList by
            catalogBrowserViewModel.featuredMovieList.collectAsStateWithLifecycle()

            Carousel(
                itemCount = featuredMovieList.size,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(376.dp),
            ) { indexOfCarouselItem ->
                val featuredMovie = featuredMovieList[indexOfCarouselItem]
                val backgroundColor = MaterialTheme.colorScheme.background
                
                Box {
                    AsyncImage(
                        model = featuredMovie.backgroundImageUrl,
                        contentDescription = null,
                        placeholder = painterResource(
                            id = R.drawable.placeholder
                        ),
                        contentScale = ContentScale.Crop,
                        modifier = Modifier.fillMaxSize(),
                    )
                    Box(
                        contentAlignment = Alignment.BottomStart,
                        modifier = Modifier
                            .fillMaxSize()
                            .drawBehind {
                                val brush = Brush.horizontalGradient(
                                    listOf(backgroundColor, Color.Transparent)
                                )
                                drawRect(brush)
                            }
                    ) {
                        Column(
                            modifier = Modifier.padding(20.dp)
                        ) {
                            Text(
                                text = featuredMovie.title,
                                style = MaterialTheme.typography.displaySmall
                            )
                            Spacer(modifier = Modifier.height(28.dp))
                            Button(onClick = { onMovieSelected(featuredMovie) }) {
                                Text(text = stringResource(id = R.string.show_details))
                            }
                        }
                    }
                }
            }
        }
        items(categoryList) { category ->
            Text(text = category.name)
            LazyRow(
                horizontalArrangement = Arrangement.spacedBy(16.dp),
                modifier = Modifier.height(200.dp)
            ) {
                items(category.movieList) { movie ->
                    MovieCard(
                        movie,
                        onClick = {
                            onMovieSelected(it)
                        }
                    )
                }
            }
        }
    }
}

7. Télécharger le code de solution

Pour télécharger le code de solution de cet atelier de programmation, effectuez l'une des opérations suivantes :

  • Cliquez sur le bouton suivant pour le télécharger au format .zip, puis décompressez-le et ouvrez-le dans Android Studio.

  • Récupérez-le avec Git :
$ git clone https://github.com/android/tv-codelabs.git
$ cd tv-codelabs
$ git checkout solution
$ cd IntroductionToComposeForTV

8. Félicitations.

Félicitations ! Vous avez appris les principes de base de Compose pour la télévision :

  • Comment implémenter un écran pour afficher une liste de contenu en combinant LazyColumn et LazyLow
  • Comment implémenter un écran de base pour afficher les détails du contenu
  • Comment ajouter des transitions lors du passage d'un écran à un autre