Créer et tester une application à utiliser à l'arrêt pour Android Automotive OS

1. Avant de commencer

Ce que cet atelier n'est pas

  • Un guide expliquant comment créer des applications multimédias (audio, par exemple musique, radio, podcasts) pour Android Auto et Android Automotive OS. Consultez la page Créer des applications multimédias pour voitures pour découvrir comment créer de telles applis.

Ce dont vous avez besoin

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez apprendre à migrer une application mobile de streaming vidéo existante, Road Reels, vers Android Automotive OS.

Version de départ de l'application exécutée sur un téléphone

Version terminée de l'application exécutée sur un émulateur Android Automotive OS avec une encoche.

Version de départ de l'application exécutée sur un téléphone

Version terminée de l'application exécutée sur un émulateur Android Automotive OS avec une encoche.

Points abordés

  • Comment utiliser l'émulateur Android Automotive OS.
  • Comment effectuer les modifications nécessaires pour créer un build Android Automotive OS.
  • Hypothèses courantes faites lors du développement d'applications mobiles qui peuvent se révéler fausses sur Android Automotive OS.
  • Les différents niveaux de qualité concernant les applications dans les voitures.
  • Comment permettre à d'autres applications de contrôler la lecture de votre application à l'aide d'une session multimédia.
  • Différences possibles entre les appareils Android Automotive OS et les appareils mobiles en matière d'UI du système et d'encart de fenêtre.

2. Configuration

Obtenir le code

  1. Le code pour cet atelier de programmation se trouve dans le répertoire build-a-parked-app au sein du dépôt GitHub car-codelabs. Pour le cloner, exécutez la commande suivante :
git clone https://github.com/android/car-codelabs.git
  1. Vous pouvez aussi télécharger le dépôt sous la forme d'un fichier ZIP :

Télécharger le fichier ZIP

Ouvrir le projet

  • Après avoir démarré Android Studio, importez le projet en sélectionnant uniquement le répertoire build-a-parked-app/start. Le répertoire build-a-parked-app/end contient le code de solution, que vous pouvez consulter à tout moment en cas de difficulté ou pour avoir un aperçu du projet dans son ensemble.

Vous familiariser avec le code

  • Après avoir ouvert le projet dans Android Studio, prenez le temps d'examiner le code de départ.

3. En savoir plus sur les applications à utiliser à l'arrêt pour Android Automotive OS

Les applications à utiliser à l'arrêt constituent un sous-ensemble des catégories d'applications compatibles avec Android Automotive OS. Au moment de la rédaction de cet atelier, il s'agit d'applications de streaming vidéo, de navigateurs Web et de jeux. Ces applications sont idéales pour les voitures, compte tenu du matériel présent dans les véhicules avec Google intégré et de la prévalence croissante des véhicules électriques, dont le temps de recharge représente une excellente opportunité pour les conducteurs et les passagers d'utiliser ce type d'applications.

À bien des égards, les voitures sont semblables à d'autres appareils à grand écran, comme les tablettes et les appareils pliables. Elles sont équipées d'écrans tactiles de taille, résolution et format similaires, en mode Portrait ou Paysage (mais, contrairement aux tablettes, leur orientation est fixe). Ce sont également des appareils connectés qui peuvent se connecter et se déconnecter du réseau. En tenant compte de tous ces éléments, il n'est pas surprenant que les applications déjà optimisées pour les grands écrans nécessitent souvent très peu de travail supplémentaire pour offrir une expérience utilisateur optimale dans une voiture.

Comme pour les grands écrans, il existe également des niveaux de qualité pour les applications dans les voitures :

  • Niveau 3 - Adaptée aux voitures : votre application est compatible avec les grands écrans et peut être utilisée lorsque la voiture est à l'arrêt. Même si elle ne dispose pas de fonctionnalités optimisées pour les voitures, les utilisateurs peuvent l'utiliser comme sur n'importe quel autre appareil Android à grand écran. Les applications mobiles qui répondent à ces exigences peuvent être distribuées telles quelles aux voitures via le programme Applications mobiles adaptées aux voitures.
  • Niveau 2 - Optimisée pour les voitures : votre application offre une expérience utilisateur optimale sur l'écran central de la voiture. Pour ce faire, votre application doit être conçue spécifiquement pour la voiture, afin d'inclure des fonctionnalités utilisables en mode conduite ou à l'arrêt, selon la catégorie de votre application.
  • Niveau 1 - Différenciée pour les voitures : votre application est conçue pour fonctionner sur différents types de matériel dans les voitures et peut adapter son expérience en mode conduite ou à l'arrêt. Offrant la meilleure expérience utilisateur, elle est conçue pour les différents écrans des voitures, tels que la console centrale, le cluster d'instruments et les écrans supplémentaires (comme les écrans panoramiques que l'on trouve dans de nombreuses voitures haut de gamme).

4. Exécuter l'application dans l'émulateur Android Automotive OS

Installer les images système Automotive avec Play Store

  1. Pour commencer, ouvrez SDK Manager dans Android Studio et sélectionnez l'onglet SDK Platforms (Plates-formes de SDK) si ce n'est pas déjà fait. En bas à droite de la fenêtre SDK Manager, assurez-vous que la case Show package details (Afficher les détails du package) est cochée.
  2. Installez l'une des images d'émulateur Automotive avec Play Store répertoriées dans Ajouter des images système génériques. Les images ne s'exécutent que sur les machines disposant de la même architecture (x86/ARM).

Créer un appareil virtuel Android pour Android Automotive OS

  1. Après avoir ouvert le gestionnaire d'appareils, sélectionnezAutomotive (Automobile) dans la colonne Category (Catégorie) à gauche de la fenêtre. Sélectionnez ensuite le profil matériel groupé Automotive (1024p landscape) (Automobile (paysage 1024p)) dans la liste, puis cliquez sur Next (Suivant).

L'assistant "Virtual Device Configuration" (Configuration des appareils virtuels) affiche le profil matériel "Automotive (1024p landscape)" (Automobile (paysage 1024p)) sélectionné.

  1. Sur la page suivante, sélectionnez l'image système de l'étape précédente. Cliquez sur Next (Suivant), sélectionnez les options avancées de votre choix, puis créez l'AVD en cliquant sur Finish (Terminer). Remarque : Si vous avez choisi l'image de l'API 30, elle peut se trouver dans un onglet autre que l'onglet Recommended (Recommandé).

Exécuter l'application

Exécutez l'application sur l'émulateur que vous venez de créer à l'aide de la configuration d'exécution app existante. Testez l'application pour parcourir les différents écrans et comparer son comportement à celui observé sur un émulateur de téléphone ou de tablette.

301e6c0d3675e937.png

5. Créer un build Android Automotive OS

Bien que l'application "fonctionne", vous devez lui apporter quelques petites modifications pour qu'elle s'adapte parfaitement à Android Automotive OS et réponde aux exigences de publication sur le Play Store. Il n'est pas toujours judicieux d'inclure toutes ces modifications dans la version mobile de l'application. Vous allez donc commencer par créer une variante de compilation Android Automotive OS.

Ajouter un groupe de types de facteurs de forme

Pour commencer, ajoutez un groupe de types pour le facteur de forme ciblé par le build en modifiant flavorDimensions dans le fichier build.gradle.kts. Ajoutez ensuite un bloc productFlavors et des types pour chaque facteur de forme (mobile et automotive).

Pour en savoir plus, consultez Configurer les types de produit.

build.gradle.kts (Module :app)

android {
    ...
    flavorDimensions += "formFactor"
    productFlavors {
        create("mobile") {
            // Inform Android Studio to use this flavor as the default (e.g. in the Build Variants tool window)
            isDefault = true
            // Since there is only one flavor dimension, this is optional
            dimension = "formFactor"
        }
        create("automotive") {
            // Since there is only one flavor dimension, this is optional
            dimension = "formFactor"
            // Adding a suffix makes it easier to differentiate builds (e.g. in the Play Console)
            versionNameSuffix = "-automotive"
        }
    }
    ...
}

Une fois que vous avez mis à jour le fichier build.gradle.kts, une bannière d'information s'affiche en haut du fichier : "Gradle files have changed since the last project sync. A project sync may be necessary for the IDE to work properly" (Les fichiers Gradle ont changé depuis la dernière synchronisation du projet. Une synchronisation du projet peut être nécessaire pour que l'IDE fonctionne correctement). Cliquez sur le bouton Sync Now (Synchroniser) dans cette bannière pour qu'Android Studio puisse importer ces modifications de configuration de compilation.

8685bcde6b21901f.png

Ouvrez ensuite la fenêtre d'outil Build Variants (Variantes de compilation) à partir de l'élément de menu Build > Select Build Variant… (Compilation > Sélectionner une variante de compilation), puis sélectionnez la variante automotiveDebug. Cela vous permet de voir les fichiers de l'ensemble de sources automotive dans la fenêtre Projet et de vous assurer que cette variante de compilation est utilisée lorsque vous exécutez l'application via Android Studio.

19e4aa8135553f62.png

Créer un fichier manifeste Android Automotive OS

Vous allez maintenant créer un fichier AndroidManifest.xml pour l'ensemble de sources automotive. Ce fichier contient les éléments nécessaires aux applications Android Automotive OS.

  1. Dans la fenêtre Project (Projet), effectuez un clic droit sur le module app. Dans le menu déroulant qui s'affiche, sélectionnez New > Other > Android Manifest File (Nouveau > Autre > Fichier manifeste Android).
  2. Dans la fenêtre New Android Component (Nouveau composant Android) qui s'ouvre, sélectionnez automotive comme ensemble de sources cible pour le nouveau fichier. Cliquez sur Finish (Terminer) pour créer le fichier.

3fe290685a1026f5.png

  1. Dans le fichier AndroidManifest.xml qui vient d'être créé (sous le chemin d'accès app/src/automotive/AndroidManifest.xml), ajoutez ceci :

AndroidManifest.xml (automotive)

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!--  https://developer.android.com/training/cars/parked#required-features  -->
    <uses-feature
        android:name="android.hardware.type.automotive"
        android:required="true" />
    <uses-feature
        android:name="android.hardware.wifi"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.screen.portrait"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.screen.landscape"
        android:required="false" />
</manifest>

La première déclaration est requise pour importer l'artefact de compilation dans le canal Android Automotive OS de la Play Console. La présence de cette fonctionnalité permet à Google Play de distribuer l'application uniquement sur les appareils disposant de la fonctionnalité android.hardware.type.automotive (c.-à-d. les voitures).

Les autres déclarations sont nécessaires pour garantir que l'application peut être installée sur les différentes configurations matérielles présentes dans les voitures. Pour en savoir plus, consultez Fonctionnalités requises pour Android Automotive OS.

Identifier l'application comme une application vidéo

Le dernier élément de métadonnées à ajouter est le fichier automotive_app_desc.xml. Ce fichier permet de déclarer la catégorie de votre application dans le contexte d'Android for Cars. Cette catégorie est indépendante de la catégorie que vous sélectionnez pour votre application dans la Play Console.

  1. Effectuez un clic droit sur le module app, sélectionnez l'option New > Android Resource File (Nouveau > Fichier de ressources Android), puis saisissez les valeurs suivantes avant de cliquer sur OK :
  • File name (Nom du fichier) : automotive_app_desc.xml
  • Resource type (Type de ressource) : XML
  • Root element (Élément racine) : automotiveApp
  • Source set (Ensemble de ressources) : automotive
  • Directory name (Nom du répertoire) : xml

47ac6bf76ef8ad45.png

  1. Dans ce fichier, ajoutez l'élément <uses> suivant pour indiquer que votre application est une application vidéo.

automotive_app_desc.xml

<?xml version="1.0" encoding="utf-8"?>
<automotiveApp xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses
        name="video"
        tools:ignore="InvalidUsesTagAttribute" />
</automotiveApp>
  1. Dans le fichier AndroidManifest.xml de l'ensemble de sources automotive (celui où vous venez d'ajouter les éléments <uses-feature>), ajoutez un élément <application> vide. Ajoutez-y l'élément <meta-data> suivant qui fait référence au fichier automotive_app_desc.xml que vous venez de créer.

AndroidManifest.xml (automotive)

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...

    <application>
        <meta-data
            android:name="com.android.automotive"
            android:resource="@xml/automotive_app_desc" />
    </application>
</manifest>

En suivant ces étapes, vous avez apporté toutes les modifications nécessaires pour créer une version Android Automotive OS de l'application.

6. Respecter les exigences de qualité d'Android Automotive OS : navigabilité

Bien que la création d'une variante de compilation Android Automotive OS soit une étape nécessaire pour intégrer votre application à des véhicules, vous devez également vous assurer que votre application est utilisable en toute sécurité.

Ajouter des affordances de navigation

Lorsque vous avez exécuté l'application dans l'émulateur Android Automotive OS, vous avez peut-être remarqué qu'il n'était pas possible de revenir de l'écran de détails à l'écran principal, ou de l'écran du lecteur à l'écran de détails. Contrairement à d'autres facteurs de forme, qui peuvent nécessiter un bouton "Retour" ou un geste tactile pour activer le retour en arrière, il n'existe aucune exigence de ce type pour les appareils Android Automotive OS. Par conséquent, les applications doivent fournir des affordances de navigation dans leur interface utilisateur pour que les utilisateurs puissent naviguer sans rester bloqués sur un écran de l'application. Cette exigence est codifiée dans la consigne de qualité AN-1.

Pour permettre le retour en arrière de l'écran de détails vers l'écran principal, ajoutez un paramètre navigationIcon supplémentaire pour l'élément CenterAlignedTopAppBar de l'écran de détails, comme suit :

RoadReelsApp.kt

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton

...

navigationIcon = {
    IconButton(onClick = { navController.popBackStack() }) {
        Icon(
            Icons.AutoMirrored.Filled.ArrowBack,
            contentDescription = null
        )
    }
}

Pour permettre le retour en arrière depuis l'écran du lecteur vers l'écran principal, procédez comme suit :

  1. Mettez à jour le composable TopControls pour qu'il accepte un paramètre de rappel appelé onClose et ajoutez un IconButton qui l'appelle lorsque l'utilisateur clique dessus.

PlayerControls.kt

@Composable
fun TopControls(
    title: String?,
    onClose: () -> Unit,
    modifier: Modifier = Modifier
) {
    Box(modifier) {
        IconButton(
            modifier = Modifier
                .align(Alignment.TopStart),
            onClick = onClose
        ) {
            Icon(
                Icons.TwoTone.Close,
                contentDescription = "Close player",
                tint = Color.White
            )
        }

        if (title != null) { ... }
    }
}
  1. Mettez à jour le composable PlayerControls pour qu'il accepte également un paramètre de rappel onClose et le transmette à TopControls.

PlayerControls.kt

fun PlayerControls(
    visible: Boolean,
    playerState: PlayerState,
    onClose: () -> Unit,
    onPlayPause: () -> Unit,
    onSeek: (seekToMillis: Long) -> Unit,
    modifier: Modifier = Modifier,
) {
    AnimatedVisibility(
        visible = visible,
        enter = fadeIn(),
        exit = fadeOut()
    ) {
        Box(modifier = modifier.background(Color.Black.copy(alpha = .5f))) {
            TopControls(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(dimensionResource(R.dimen.screen_edge_padding))
                    .align(Alignment.TopCenter),
                title = playerState.mediaMetadata.title?.toString(),
                onClose = onClose
            )
            ...
        }
    }
}
  1. Mettez ensuite à jour le composable PlayerScreen pour qu'il accepte le même paramètre et le transmette à son élément PlayerControls.

PlayerScreen.kt

@Composable
fun PlayerScreen(
    onClose: () -> Unit,
    modifier: Modifier = Modifier,
) {
    ...

    PlayerControls(
        modifier = Modifier
            .fillMaxSize(),
        visible = isShowingControls,
        playerState = playerState,
        onClose = onClose,
        onPlayPause = { if (playerState.isPlaying) player.pause() else player.play() },
        onSeek = { player.seekTo(it) }
    )
}
  1. Enfin, dans RoadReelsNavHost, fournissez l'implémentation qui est transmise à PlayerScreen :

RoadReelsNavHost.kt

composable(route = Screen.Player.name) {
    PlayerScreen(onClose = { navController.popBackStack() })
}

Parfait ! L'utilisateur peut désormais passer d'un écran à l'autre sans se retrouver bloqué. L'expérience utilisateur peut même être améliorée sur d'autres facteurs de forme. Par exemple, sur un téléphone grand format, lorsque la main de l'utilisateur se trouve déjà près du haut de l'écran, l'utilisateur peut plus facilement naviguer dans l'application sans avoir à déplacer l'appareil dans sa main.

43122e716eeeeb20.gif

S'adapter à la prise en charge de l'orientation de l'écran

Contrairement à la grande majorité des appareils mobiles, la plupart des écrans de voiture ont une orientation fixe. Autrement dit, comme ils ne peuvent pas pivoter, ces écrans sont compatibles soit avec le mode Paysage, soit avec le mode Portrait. Pour cette raison, les applis doivent éviter de partir du principe que les deux orientations sont prises en charge.

Dans la section Créer un fichier manifeste Android Automotive OS, vous avez ajouté deux éléments <uses-feature> pour les fonctionnalités android.hardware.screen.portrait et android.hardware.screen.landscape, avec l'attribut required défini sur false. Cela garantit qu'aucune dépendance implicite d'une fonctionnalité par rapport à l'orientation de l'écran ne peut empêcher la distribution de l'application aux voitures. Toutefois, ces éléments du fichier manifeste ne modifient pas le comportement de l'application, mais seulement sa distribution.

Actuellement, l'application dispose d'une fonctionnalité utile qui définit automatiquement l'orientation de l'activité en mode Paysage lorsque le lecteur vidéo s'ouvre. Ainsi, les utilisateurs de téléphones n'ont pas à manipuler leur appareil pour changer son orientation s'il n'est pas déjà en mode Paysage.

Malheureusement, ce comportement peut entraîner une boucle de clignotement ou une mise au format letterbox sur les appareils dont l'orientation est fixe en mode Portrait, comme c'est le cas sur l'écran de nombreux véhicules aujourd'hui.

Pour résoudre ce problème, vous pouvez ajouter une vérification des orientations d'écran compatibles avec l'appareil actuel.

  1. Pour simplifier l'implémentation, commencez par ajouter ce qui suit dans Extensions.kt :

Extensions.kt

import android.content.Context
import android.content.pm.PackageManager

...

enum class SupportedOrientation {
    Landscape,
    Portrait,
}

fun Context.supportedOrientations(): List<SupportedOrientation> {
    return when (Pair(
        packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE),
        packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)
    )) {
        Pair(true, false) -> listOf(SupportedOrientation.Landscape)
        Pair(false, true) -> listOf(SupportedOrientation.Portrait)
        // For backwards compat, if neither feature is declared, both can be assumed to be supported
        else -> listOf(SupportedOrientation.Landscape, SupportedOrientation.Portrait)
    }
}
  1. Protégez ensuite l'appel pour définir l'orientation demandée. Étant donné que les applications peuvent rencontrer un problème similaire en mode multifenêtre sur les appareils mobiles, vous pouvez également inclure une vérification afin de ne pas définir l'orientation de manière dynamique dans ce cas non plus.

PlayerScreen.kt

import com.example.android.cars.roadreels.SupportedOrientation
import com.example.android.cars.roadreels.supportedOrientations

...

LaunchedEffect(Unit) {
    ...

    // Only automatically set the orientation to landscape if the device supports landscape.
    // On devices that are portrait only, the activity may enter a compat mode and won't get to
    // use the full window available if so. The same applies if the app's window is portrait
    // in multi-window mode.
    if (context.supportedOrientations().contains(SupportedOrientation.Landscape)
        && !context.isInMultiWindowMode
    ) {
        context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
    }

    ...
}

Avant l'ajout de la vérification, l'écran du lecteur entre dans une boucle de clignotement sur l'émulateur Polestar 2 (lorsque l'activité ne gère pas les modifications de configuration de l'orientation).

Avant l'ajout de la vérification, l'écran du lecteur est mis au format letterbox sur l'émulateur Polestar 2 (lorsque l'activité gère les modifications de configuration de l'orientation).

Après l'ajout de la vérification, l'écran du lecteur n'est pas mis au format letterbox sur l'émulateur Polestar 2.

Avant l'ajout de la vérification, l'écran du lecteur entre dans une boucle de clignotement sur l'émulateur Polestar 2 (lorsque l'activité ne gère pas les modifications de configuration de l'orientation).

Avant l'ajout de la vérification, l'écran du lecteur est mis au format letterbox sur l'émulateur Polestar 2 (lorsque l'activité gère les modifications de configuration de l'orientation).

Après l'ajout de la vérification, l'écran du lecteur n'est pas mis au format letterbox sur l'émulateur Polestar 2.

Comme il s'agit du seul emplacement de l'application qui définit l'orientation de l'écran, l'application évite désormais la mise au format letterbox. Dans votre propre application, recherchez les attributs screenOrientation ou les appels setRequestedOrientation qui ne sont destinés qu'aux orientations Paysage ou Portrait (y compris la rotation du capteur, la rotation inversée et les variantes utilisateur de chacune d'elles), puis supprimez-les ou protégez-les si nécessaire pour limiter la mise au format letterbox. Pour en savoir plus, consultez Mode de compatibilité avec les appareils.

Adaptation à la contrôlabilité de la barre système

Malheureusement, bien que la modification précédente garantisse que l'application ne génère pas de clignotement ni de letterbox, elle met aussi en évidence une autre hypothèse qui a été contredite, à savoir que les barres système peuvent toujours être masquées. Les besoins des utilisateurs sont différents lorsqu'ils utilisent leur voiture (par rapport à leur téléphone ou leur tablette). Les OEM peuvent donc empêcher les applications de masquer les barres système afin de s'assurer que les commandes du véhicule, telles que la climatisation, sont toujours accessibles à l'écran.

Par conséquent, lorsque les applications s'affichent en mode immersif, il est possible qu'elles apparaissent derrière les barres système en pensant que les barres sont masquées. Vous pouvez le constater à l'étape précédente : les commandes du lecteur en haut et en bas ne sont plus visibles lorsque l'application n'est pas au format letterbox. Dans ce cas précis, il n'est plus possible de naviguer dans l'application, car le bouton permettant de fermer le lecteur est masqué. De plus, la fonctionnalité de l'application est entravée, car la barre de recherche ne peut pas être utilisée.

La solution la plus simple consiste à appliquer la marge intérieure des encarts de fenêtre systemBars au lecteur, comme suit :

PlayerScreen.kt

import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding

...

Box(
    modifier = Modifier
        .fillMaxSize()
        .windowInsetsPadding(WindowInsets.systemBars)
) {
    PlayerView(...)
    PlayerControls(...)
}

Toutefois, cette solution n'est pas idéale, car elle entraîne le saut des éléments d'interface utilisateur lorsque les barres système disparaissent.

9c51956e2093820a.gif

Pour améliorer l'expérience utilisateur, vous pouvez mettre à jour l'application afin de garder une trace des encarts pouvant être contrôlés et appliquer une marge intérieure uniquement aux encarts qui ne peuvent pas être contrôlés.

  1. Étant donné que le contrôle des encarts de fenêtre peut-être utile dans d'autres écrans de l'application, il est judicieux de transmettre les encarts contrôlables en tant qu'élément CompositionLocal. Créez un fichier LocalControllableInsets.kt dans le package com.example.android.cars.roadreels et ajoutez les éléments suivants :

LocalControllableInsets.kt

import androidx.compose.runtime.compositionLocalOf

// Assume that no insets can be controlled by default
const val DEFAULT_CONTROLLABLE_INSETS = 0
val LocalControllableInsets = compositionLocalOf { DEFAULT_CONTROLLABLE_INSETS }
  1. Configurez un OnControllableInsetsChangedListener pour écouter les modifications.

MainActivity.kt

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat.OnControllableInsetsChangedListener

...

class MainActivity : ComponentActivity() {
    private lateinit var onControllableInsetsChangedListener: OnControllableInsetsChangedListener

    @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        enableEdgeToEdge()

        setContent {
            var controllableInsetsTypeMask by remember { mutableIntStateOf(DEFAULT_CONTROLLABLE_INSETS) }

            onControllableInsetsChangedListener =
                OnControllableInsetsChangedListener { _, typeMask ->
                    if (controllableInsetsTypeMask != typeMask) {
                        controllableInsetsTypeMask = typeMask
                    }
                }

            WindowCompat.getInsetsController(window, window.decorView)
                .addOnControllableInsetsChangedListener(onControllableInsetsChangedListener)

            RoadReelsTheme {
                RoadReelsApp(calculateWindowSizeClass(this))
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()

        WindowCompat.getInsetsController(window, window.decorView)
            .removeOnControllableInsetsChangedListener(onControllableInsetsChangedListener)
    }
}
  1. Ajoutez un CompositionLocalProvider de premier niveau contenant les composables du thème et de l'application, et qui lie les valeurs à LocalControllableInsets.

MainActivity.kt

import androidx.compose.runtime.CompositionLocalProvider

...

CompositionLocalProvider(LocalControllableInsets provides controllableInsetsTypeMask) {
    RoadReelsTheme {
        RoadReelsApp(calculateWindowSizeClass(this))
    }
}
  1. Dans le lecteur, lisez la valeur actuelle et utilisez-la pour déterminer les encarts à utiliser pour la marge intérieure.

PlayerScreen.kt

import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.union
import androidx.compose.ui.unit.dp
import com.example.android.cars.roadreels.LocalControllableInsets

...

val controllableInsetsTypeMask = LocalControllableInsets.current

// When the system bars can be hidden, ignore them when applying padding to the player and
// controls so they don't jump around as the system bars disappear. If they can't be hidden
// include them so nothing renders behind the system bars
var windowInsetsForPadding = WindowInsets(0.dp)
if (controllableInsetsTypeMask.and(WindowInsetsCompat.Type.statusBars()) == 0) {
    windowInsetsForPadding = windowInsetsForPadding.union(WindowInsets.statusBars)
}
if (controllableInsetsTypeMask.and(WindowInsetsCompat.Type.navigationBars()) == 0) {
    windowInsetsForPadding = windowInsetsForPadding.union(WindowInsets.navigationBars)
}

Box(
    modifier = Modifier
        .fillMaxSize()
        .windowInsetsPadding(windowInsetsForPadding)
) {
    PlayerView(...)
    PlayerControls(...)
}

Le contenu ne saute pas lorsque les barres système peuvent être masquées.

Le contenu reste visible lorsque les barres système ne peuvent pas être masquées.

Le contenu ne saute pas lorsque les barres système peuvent être masquées.

Le contenu reste visible lorsque les barres système ne peuvent pas être masquées.

C'est beaucoup mieux ! Le contenu ne saute pas, et les commandes sont entièrement visibles, même dans les voitures où les barres système ne peuvent pas être contrôlées.

7. Respecter les exigences de qualité d'Android Automotive OS : distraction du conducteur

Enfin, il existe une différence majeure entre les voitures et les autres facteurs de forme : les voitures sont utilisées pour conduire ! Il est donc très important de limiter les distractions au volant. Toutes les applications à utiliser à l'arrêt pour Android Automotive OS doivent mettre en pause la lecture lorsque la conduite commence. Une superposition système s'affiche lorsque la conduite commence et l'événement de cycle de vie onPause est alors appelé pour l'application sur laquelle la superposition est appliquée. C'est au cours de cet appel que les applications doivent suspendre la lecture.

Simuler la conduite

Accédez à la vue du lecteur dans l'émulateur et commencez à lire du contenu. Suivez ensuite la procédure pour simuler la conduite. Vous remarquerez alors que, même si l'interface utilisateur de l'application est masquée par le système, la lecture n'est pas interrompue. Cela ne respecte pas la consigne DD-2 relative à la qualité des applications pour voitures.

839af1382c1f10ca.png

Mettre en pause la lecture lorsque la conduite démarre

  1. Ajoutez une dépendance à l'artefact androidx.lifecycle:lifecycle-runtime-compose, qui contient le LifecycleEventEffect qui permet d'exécuter du code lorsque des événements de cycle de vie se produisent.

libs.version.toml

androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }

Build.gradle.kts (Module :app)

implementation(libs.androidx.lifecycle.runtime.compose)
  1. Après avoir synchronisé le projet pour télécharger la dépendance, ajoutez un LifecycleEventEffect qui s'exécute lors de l'événement ON_PAUSE pour mettre en pause la lecture.

PlayerScreen.kt

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect

...

@Composable
fun PlayerScreen(...) {
    ...
    LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) {
        player.pause()
    }

    LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
        player.play()
    }
    ...
}

Une fois le correctif implémenté, suivez la même procédure que précédemment pour simuler la conduite alors qu'une lecture est en cours. Vous remarquerez que la lecture s'arrête, ce qui répond à l'exigence DD-2.

8. Tester l'application dans l'émulateur d'écran distant

Dans les voitures, une nouvelle configuration à deux écrans commence à apparaître, avec un écran principal dans la console centrale et un écran secondaire en haut du tableau de bord, près du pare-brise. Les applications peuvent être déplacées de l'écran central vers l'écran secondaire et inversement, afin de proposer davantage de choix aux conducteurs et aux passagers.

Installer l'image Automotive Distant Display (Écran distant automobile)

  1. Pour commencer, ouvrez SDK Manager dans Android Studio et sélectionnez l'onglet SDK Platforms (Plates-formes de SDK) si ce n'est pas déjà fait. En bas à droite de la fenêtre SDK Manager, assurez-vous que la case Show package details (Afficher les détails du package) est cochée.
  2. Installez l'image de l'émulateur Automotive Distant Display with Google APIs (Écran distant automobile avec Google APIs) correspondant à l'architecture de votre ordinateur (x86/ARM).

Créer un appareil virtuel Android pour Android Automotive OS

  1. Après avoir ouvert le gestionnaire d'appareils, sélectionnezAutomotive (Automobile) dans la colonne Category (Catégorie) à gauche de la fenêtre. Sélectionnez ensuite le profil matériel groupé Automotive Distant Display (Écran distant automobile) dans la liste, puis cliquez sur Next (Suivant).
  2. Sur la page suivante, sélectionnez l'image système de l'étape précédente. Cliquez sur Next (Suivant), sélectionnez les options avancées de votre choix, puis créez l'AVD en cliquant sur Finish (Terminer).

Exécuter l'application

Exécutez l'application sur l'émulateur que vous venez de créer à l'aide de la configuration d'exécution app existante. Suivez les instructions de la page Utiliser l'émulateur d'écran distant pour déplacer l'application vers et depuis l'écran distant. Testez le déplacement de l'application lorsqu'elle se trouve sur l'écran principal/de détails et sur l'écran du lecteur, et essayez d'interagir avec l'application sur les deux écrans.

b277bd18a94e9c1b.png

9. Améliorer l'expérience dans l'application sur l'écran distant

Lorsque vous avez utilisé l'application sur l'écran distant, vous avez peut-être remarqué deux choses :

  1. La lecture redémarre lorsque l'application est déplacée vers ou depuis l'écran distant.
  2. Vous ne pouvez pas interagir avec l'application lorsqu'elle est affichée sur l'écran distant, même pour modifier l'état de lecture.

Améliorer la continuité des applications

Le problème de redémarrage de la lecture est dû au fait que l'activité est recréée suite à une modification de configuration. Étant donné que l'application est écrite à l'aide de Compose et que la configuration modifiée est liée à la taille, vous pouvez facilement laisser Compose gérer les modifications de configuration à votre place en limitant la recréation d'activité pour les modifications de configuration en fonction de la taille. La transition entre les écrans est ainsi fluide, sans interruption de la lecture ni rechargement en raison de la recréation de l'activité.

AndroidManifest.xml

<activity
    android:name="com.example.android.cars.roadreels.MainActivity"
    ...
    android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout|density">
        ...
</activity>

Implémenter des commandes de lecture

Pour résoudre le problème empêchant de contrôler l'application lorsqu'elle se trouve sur l'écran distant, vous pouvez implémenter MediaSession. Les sessions multimédias offrent un moyen universel d'interagir avec un lecteur audio ou vidéo. Pour en savoir plus, consultez Contrôler et annoncer la lecture avec MediaSession.

  1. Ajouter une dépendance à l'artefact androidx.media3:media3-session.

libs.version.toml

androidx-media3-mediasession = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }

build.gradle.kts (Module :app)

implementation(libs.androidx.media3.mediasession)
  1. Créez une MediaSession à l'aide de son compilateur.

PlayerScreen.kt

import androidx.media3.session.MediaSession

@Composable
fun PlayerScreen(...) {
    ...
    val mediaSession = remember(context, player) {
        MediaSession.Builder(context, player).build()
    }
    ...
}
  1. Ajoutez ensuite une ligne dans le bloc onDispose de DisposableEffect dans le composable Player pour libérer MediaSession lorsque Player quitte l'arborescence de composition.

PlayerScreen.kt

DisposableEffect(Unit) {
    onDispose {
        mediaSession.release()
        player.release()
        ...
    }
}
  1. Enfin, sur l'écran du lecteur, vous pouvez tester les commandes multimédias à l'aide de la commande adb shell cmd media_session dispatch.
# To play content
adb shell cmd media_session dispatch play

# To pause content
adb shell cmd media_session dispatch pause

# To toggle the playing state
adb shell cmd media_session dispatch play-pause

L'application fonctionne ainsi beaucoup mieux dans les voitures disposant d'écrans distants. Mais ce n'est pas tout : elle fonctionne également mieux sur d'autres facteurs de forme. Sur les appareils qui peuvent faire pivoter leur écran ou qui permettent aux utilisateurs de redimensionner la fenêtre d'une application, l'application s'adapte désormais également à ces situations.

De plus, grâce à l'intégration de la session multimédia, la lecture de l'application peut être contrôlée non seulement par les commandes matérielles et logicielles dans les voitures, mais également par d'autres sources, par exemple une requête à l'Assistant Google ou un bouton de pause sur des écouteurs. Les utilisateurs disposent ainsi de plus d'options pour contrôler l'application sur différents facteurs de forme.

10. Tester l'application dans différentes configurations système

Maintenant que l'application fonctionne bien sur l'écran principal et l'écran distant, la dernière chose à vérifier est la façon dont l'application gère les différentes configurations de barre système, ainsi que les encoches. Comme décrit dans Utiliser des encarts de fenêtre et des encoches, il est possible que la configuration de certains appareils Android Automotive OS ne respecte pas les hypothèses généralement valables pour les facteurs de forme mobiles.

Dans cette section, vous allez télécharger un émulateur qui peut être configuré au moment de l'exécution. Vous allez ensuite configurer l'émulateur pour qu'il dispose d'une barre système à gauche, puis tester l'application dans cette configuration.

Installer l'image Android Automotive with Google APIs (Android Automotive avec Google APIs)

  1. Pour commencer, ouvrez SDK Manager dans Android Studio et sélectionnez l'onglet SDK Platforms (Plates-formes de SDK) si ce n'est pas déjà fait. En bas à droite de la fenêtre SDK Manager, assurez-vous que la case Show package details (Afficher les détails du package) est cochée.
  2. Installez l'image d'émulateur Android Automotive with Google APIs (Android Automotive avec Google APIs) de l'API 33 correspondant à l'architecture de votre ordinateur (x86/ARM).

Créer un appareil virtuel Android pour Android Automotive OS

  1. Après avoir ouvert le gestionnaire d'appareils, sélectionnezAutomotive (Automobile) dans la colonne Category (Catégorie) à gauche de la fenêtre. Sélectionnez ensuite le profil matériel groupé Automotive (1080p landscape) (Automobile (paysage 1080p)) dans la liste, puis cliquez sur Next (Suivant).
  2. Sur la page suivante, sélectionnez l'image système de l'étape précédente. Cliquez sur Next (Suivant), sélectionnez les options avancées de votre choix, puis créez l'AVD en cliquant sur Finish (Terminer).

Configurer une barre système latérale

Comme indiqué dans Effectuer des tests à l'aide de l'émulateur configurable, il existe différentes options pour émuler différentes configurations système présentes dans les voitures.

Pour les besoins de cet atelier de programmation, com.android.systemui.rro.left peut être utilisé pour tester une autre configuration de barre système. Pour l'activer, exécutez la commande suivante :

adb shell cmd overlay enable --user 0 com.android.systemui.rro.left

b642703a7278b219.png

Étant donné que l'application utilise le modificateur systemBars comme contentWindowInsets dans Scaffold, le contenu est déjà affiché dans une zone séparée des barres système. Pour voir ce qui se passerait si l'application supposait que les barres système n'apparaissaient qu'en haut et en bas de l'écran, remplacez ce paramètre par le suivant :

RoadReelsApp.kt

contentWindowInsets = if (route?.equals(Screen.Player.name) == true) WindowInsets(0.dp) else WindowInsets.systemBars.only(WindowInsetsSides.Vertical)

Petit problème… L'écran de liste et l'écran de détails s'affichent derrière la barre système. Grâce aux étapes que nous avons effectuées tout à l'heure, l'écran du lecteur devrait fonctionner correctement, même si les barres système n'étaient pas contrôlables depuis.

9898f7298a7dfb4.gif

Avant de passer à la section suivante, veillez à annuler la modification que vous venez d'apporter au paramètre windowContentPadding.

11. Utiliser des encoches

Enfin, certains écrans de voiture sont dotés d'encoches très différentes de celles des appareils mobiles. Au lieu d'encoches ou de découpes en trou d'épingle pour appareils photo, certains véhicules équipés d'Android Automotive OS disposent d'écrans incurvés qui ne sont pas rectangulaires.

Pour voir comment l'application se comporte lorsqu'une telle encoche est présente, commencez par activer l'encoche à l'aide de la commande suivante :

adb shell cmd overlay enable --user 0 com.android.internal.display.cutout.emulation.free_form

Pour tester réellement le comportement de l'application, activez également la barre système de gauche utilisée dans la dernière section, si ce n'est pas déjà fait :

adb shell cmd overlay enable --user 0 com.android.systemui.rro.left

En l'état, l'application ne s'affiche pas dans l'encoche (la forme exacte de l'encoche est difficile à déterminer pour le moment, mais cela sera plus clair à l'étape suivante). L'expérience est correcte et meilleure que si l'application s'affichait dans l'encoche et ne s'y adaptait pas soigneusement.

212628db84981025.gif

Afficher le contenu dans l'encoche

Pour offrir aux utilisateurs une expérience la plus immersive possible, vous pouvez utiliser une surface d'écran beaucoup plus importante en affichant du contenu dans l'encoche.

  1. Pour afficher du contenu dans l'encoche, créez un fichier integers.xml pour contenir le forçage propre aux voitures. Pour ce faire, utilisez le qualificatif UI mode (mode UI) avec la valeur Car Dock (Support voiture) (ce nom est un vestige de l'époque où Android Auto existait seul, mais il est également utilisé par Android Automotive OS). De plus, comme LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS a été introduit dans Android R, ajoutez également le qualificatif Version d'Android avec la valeur 30. Pour en savoir plus, consultez Utiliser d'autres ressources.

22b7f17657cac3fd.png

  1. Dans le fichier que vous venez de créer (res/values-car-v30/integers.xml), ajoutez ce qui suit :

integers.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <integer name="windowLayoutInDisplayCutoutMode">3</integer>
</resources>

La valeur entière 3 correspond à LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS et remplace la valeur par défaut de 0 présente dans res/values/integers.xml, qui correspond à LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT. Cette valeur entière est déjà référencée dans MainActivity.kt pour remplacer le mode défini par enableEdgeToEdge(). Pour en savoir plus sur cet attribut, consultez la documentation de référence.

À présent, lorsque vous exécutez l'application, vous remarquez que le contenu s'étend dans l'encoche et a l'air très immersif. Toutefois, la barre d'application supérieure et une partie du contenu sont partiellement masqués par l'encoche, ce qui entraîne un problème semblable à celui qui se produisait lorsque l'application supposait que les barres système n'apparaissent qu'en haut et en bas.

f0eefa42dee6f7c7.gif

Corriger les barres d'application supérieures

Pour corriger les barres d'application supérieures, vous pouvez ajouter le paramètre windowInsets suivant aux composables CenterAlignedTopAppBar :

RoadReelsApp.kt

import androidx.compose.foundation.layout.safeDrawing

...

windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)

Étant donné que safeDrawing se compose des encarts displayCutout et systemBars, cela améliore le paramètre windowInsets par défaut, qui n'utilise que systemBars lors du positionnement de la barre d'application supérieure.

De plus, comme la barre d'application supérieure est placée en haut de la fenêtre, vous ne devez pas inclure le composant inférieur des encarts safeDrawing. Cela pourrait en effet ajouter une marge intérieure inutile.

7d59ebb63ada5f71.gif

Corriger l'écran principal

Pour corriger le contenu sur l'écran principal et l'écran de détails, vous pouvez utiliser safeDrawing au lieu de systemBars pour l'élément contentWindowInsets de Scaffold. Toutefois, l'application semble nettement moins immersive avec cette option, car le contenu s'interrompt de façon abrupte là où commence l'encoche. Par rapport à tout à l'heure lorsque le contenu de l'application ne s'affichait pas du tout dans l'encoche, ce n'est pas beaucoup mieux.

6b3824ca3214cbfa.gif

Pour une interface utilisateur plus immersive, vous pouvez gérer les encarts de chaque composant à l'écran.

  1. Mettez à jour l'élément contentWindowInsets de Scaffold pour qu'il soit constamment de 0 dp (et pas uniquement pour PlayerScreen). Cela permet à chaque écran et/ou composant d'un écran de déterminer son comportement par rapport aux encarts.

RoadReelsApp.kt

Scaffold(
    ...,
    contentWindowInsets = WindowInsets(0.dp)
) { ... }
  1. Définissez la propriété windowInsetsPadding des composables Text de l'en-tête de ligne afin d'utiliser les composants horizontaux des encarts safeDrawing. Le composant supérieur de ces encarts est géré par la barre d'application supérieure. Le composant inférieur, quant à lui, sera géré ultérieurement.

MainScreen.kt

import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding

...

LazyColumn(
    contentPadding = PaddingValues(bottom = dimensionResource(R.dimen.screen_edge_padding))
) {
    items(NUM_ROWS) { rowIndex: Int ->
        Text(
            "Row $rowIndex",
            style = MaterialTheme.typography.headlineSmall,
            modifier = Modifier
                .padding(
                    horizontal = dimensionResource(R.dimen.screen_edge_padding),
                    vertical = dimensionResource(R.dimen.row_header_vertical_padding)
                )
                .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal))
        )
    ...
}
  1. Supprimez le paramètre contentPadding de LazyRow. Ensuite, au début et à la fin de chaque LazyRow, ajoutez un élément Spacer de la largeur du composant safeDrawing correspondant pour vous assurer que toutes les vignettes peuvent s'afficher entièrement. Utilisez le modificateur widthIn pour vous assurer que ces espaces vides sont au moins aussi larges que l'était la marge intérieure du contenu. Sans ces éléments, les éléments au début et à la fin de la ligne peuvent être masqués par les barres système et/ou l'encoche, même en balayant complètement jusqu'au début ou à la fin de la ligne.

MainScreen.kt

import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsEndWidth
import androidx.compose.foundation.layout.windowInsetsStartWidth

...

LazyRow(
    horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.list_item_spacing)),
) {
    item {
        Spacer(
            Modifier
                .windowInsetsStartWidth(WindowInsets.safeDrawing)
                .widthIn(min = dimensionResource(R.dimen.screen_edge_padding))
        )
    }
    items(NUM_ITEMS_PER_ROW) { ... }
    item {
        Spacer(
            Modifier
                .windowInsetsEndWidth(WindowInsets.safeDrawing)
                .widthIn(min = dimensionResource(R.dimen.screen_edge_padding))
        )
    }
}
  1. Enfin, ajoutez un Spacer à la fin de LazyColumn pour tenir compte des éventuelles barres système ou encoches en bas de l'écran. Il n'est pas nécessaire d'ajouter un espace vide équivalent en haut de LazyColumn, car la barre d'application supérieure s'en charge. Si l'application utilisait une barre d'application inférieure au lieu d'une barre d'application supérieure, vous ajouteriez un Spacer au début de la liste à l'aide du modificateur windowInsetsTopHeight. Si l'application utilisait à la fois une barre d'application supérieure et une barre d'application inférieure, aucun espace vide ne serait nécessaire.

MainScreen.kt

import androidx.compose.foundation.layout.windowInsetsBottomHeight

...

LazyColumn(...){
    items(NUM_ROWS) { ... }
    item {
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
    }
}

Parfait ! Les barres d'application supérieures sont entièrement visibles et toutes les vignettes s'affichent en entier lorsque vous faites défiler l'écran jusqu'à la fin d'une ligne.

543706473398114a.gif

Corriger l'écran de détails

f622958a8d0c16c8.png

Pour l'écran de détails, ce n'est pas aussi mauvais, mais le contenu est quand même tronqué.

Comme l'écran de détails ne contient aucun contenu à faire défiler, il vous suffit d'ajouter un modificateur windowInsetsPadding à l'élément Box de niveau supérieur pour résoudre le problème.

DetailScreen.kt

import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding

...

Box(
    modifier = modifier
        .padding(dimensionResource(R.dimen.screen_edge_padding))
        .windowInsetsPadding(WindowInsets.safeDrawing)
) { ... }

bdd6de6010fc139d.png

Corriger l'écran du lecteur

Bien que PlayerScreen applique déjà une marge intérieure pour tout ou partie des encarts de fenêtre de la barre système, comme nous l'avons vu dans la section Respecter les exigences de qualité d'Android Automotive OS : navigabilité, cela ne suffit pas à garantir que l'écran du lecteur ne sera pas masqué maintenant que l'application s'affiche dans les encoches. Sur les appareils mobiles, les encoches sont presque toujours entièrement contenues dans les barres système. Dans les voitures, cependant, les encoches peuvent s'étendre bien au-delà des barres système, ce qui contredit les hypothèses.

427227df5e44f554.png

Pour résoudre ce problème, remplacez simplement la valeur initiale de la variable windowInsetsForPadding (zéro) par displayCutout :

PlayerScreen.kt

import androidx.compose.foundation.layout.displayCutout

...

var windowInsetsForPadding = WindowInsets(WindowInsets.displayCutout)

b523d8c1e1423757.gif

Parfait ! L'application exploite au maximum l'écran tout en restant facilement utilisable.

Si vous exécutez l'application sur un appareil mobile, l'expérience est également plus immersive sur celui-ci. Les éléments de liste s'affichent jusqu'aux bords de l'écran, y compris derrière la barre de navigation.

dc7918499a33df31.png

12. Félicitations

Vous avez migré et optimisé votre première application à utiliser à l'arrêt. Il est maintenant temps d'appliquer ce que vous avez appris à votre propre application.

À essayer

Complément d'informations