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.
Prérequis
- La dernière version d'Android Studio
- Expérience avec le langage Kotlin de base.
- Expérience avec la création d'appareils virtuels Android et leur exécution dans Android Emulator.
- Connaissances de base concernant Jetpack Compose.
- Connaissances concernant les effets secondaires.
- Connaissances de base concernant les encarts de fenêtre.
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. |
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
- Le code pour cet atelier de programmation se trouve dans le répertoire
build-a-parked-app
au sein du dépôt GitHubcar-codelabs
. Pour le cloner, exécutez la commande suivante :
git clone https://github.com/android/car-codelabs.git
- Vous pouvez aussi télécharger le dépôt sous la forme d'un 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épertoirebuild-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émarrage.
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à adaptatives 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
- 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.
- Installez l'image d'émulateur Android Automotive with Google APIs (Android Automotive avec Google APIs) de l'API 33 listée dans Add generic system images (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
- 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 (1408p landscape) (Automobile [paysage 1408p]) dans la liste, puis cliquez sur Next (Suivant).
- 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.
5. Mettre à jour le fichier manifeste pour déclarer la compatibilité avec 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. Ces modifications peuvent être apportées de sorte que le même APK ou app bundle soit compatible avec les appareils mobiles et Android Automotive OS. La première série de modifications consiste à mettre à jour le fichier AndroidManifest.xml
pour indiquer que l'application est compatible avec les appareils Android Automotive OS et qu'il s'agit d'une application vidéo.
Déclarer la fonctionnalité matérielle automobile
Pour indiquer que votre application est compatible avec les appareils Android Automotive OS, ajoutez l'élément <uses-feature>
suivant dans le fichier AndroidManifest.xml :
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<uses-feature
android:name="android.hardware.type.automotive"
android:required="false" />
...
</manifest>
En utilisant la valeur false
pour l'attribut android:required
, vous pouvez distribuer l'APK ou l'app bundle généré à la fois sur les appareils Android Automotive OS et sur les appareils mobiles. Pour en savoir plus, consultez Choisir un type de canal 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.
- 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) :
main
- Directory name (Nom du répertoire) :
xml
- 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>
- Dans l'élément
<application>
existant, ajoutez l'élément<meta-data>
suivant qui fait référence au fichierautomotive_app_desc.xml
que vous venez de créer.
AndroidManifest.xml
<application ...>
<meta-data
android:name="com.android.automotive"
android:resource="@xml/automotive_app_desc" />
</application>
En suivant ces étapes, vous avez apporté toutes les modifications nécessaires pour déclarer la compatibilité avec Android Automotive OS.
6. Respecter les exigences de qualité d'Android Automotive OS : navigabilité
Bien qu'il soit nécessaire de déclarer la compatibilité de votre application avec Android Automotive OS pour l'intégrer à des véhicules, vous devez également vous assurer que votre application est utilisable de façon sécurisée.
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
...
} else if (route?.startsWith(Screen.Detail.name) == true) {
CenterAlignedTopAppBar(
title = { Text(stringResource(R.string.bbb_title)) },
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 :
- Mettez à jour le composable
TopControls
pour qu'il accepte un paramètre de rappel appeléonClose
et ajoutez unIconButton
qui l'appelle lorsque l'utilisateur clique dessus.
PlayerControls.kt
import androidx.compose.material.icons.twotone.Close
...
@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) { ... }
}
}
- Mettez à jour le composable
PlayerControls
pour qu'il accepte également un paramètre de rappelonClose
et le transmette àTopControls
.
PlayerControls.kt
fun PlayerControls(
uiState: PlayerUiState,
onClose: () -> Unit,
onPlayPause: () -> Unit,
onSeek: (seekToMillis: Long) -> Unit,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = uiState.isShowingControls,
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 = uiState.mediaMetadata.title?.toString(),
onClose = onClose
)
...
}
}
}
- Mettez ensuite à jour le composable
PlayerScreen
pour qu'il accepte le même paramètre et le transmette à son élémentPlayerControls
.
PlayerScreen.kt
@Composable
fun PlayerScreen(
onClose: () -> Unit,
modifier: Modifier = Modifier,
viewModel: PlayerViewModel = viewModel()
) {
...
PlayerControls(
modifier = Modifier
.fillMaxSize(),
uiState = playerUiState,
onClose = onClose,
onPlayPause = { if (playerUiState.isPlaying) viewModel.pause() else viewModel.play() },
onSeek = viewModel::seekTo
)
}
- Enfin, dans
RoadReelsNavHost
, fournissez l'implémentation qui est transmise àPlayerScreen
:
RoadReelsNavHost.kt
composable(route = Screen.Player.name, ...) {
PlayerScreen(onClose = { navController.popBackStack() })
}
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.
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.
- 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)
}
}
- 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
...
DisposableEffect(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 (activity.supportedOrientations().contains(SupportedOrientation.Landscape)
&& !activity.isInMultiWindowMode
) {
activity.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' | 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' | 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 les variantes sensor
, reverse
et user
de chacune d'entre 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.
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.
- É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 fichierLocalControllableInsets.kt
dans le packagecom.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 }
- 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)
}
}
- 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))
}
}
- Dans le lecteur, lisez la valeur actuelle et utilisez-la pour déterminer les encarts à masquer et à 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.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.unit.dp
import com.example.android.cars.roadreels.LocalControllableInsets
...
val controllableInsetsTypeMask by rememberUpdatedState(LocalControllableInsets.current)
DisposableEffect(Unit) {
...
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars().and(controllableInsetsTypeMask))
...
}
...
// 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. |
Mieux encore : 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 les restrictions liées à l'expérience utilisateur deviennent actives et empêcher la reprise de la lecture tant que ces restrictions sont actives. Une superposition système s'affiche dès lors que les restrictions liées à l'expérience utilisateur deviennent actives. 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.
Mettre en pause la lecture lorsque la conduite démarre
- Ajoutez une dépendance à l'artefact
androidx.lifecycle:lifecycle-runtime-compose
, qui contient leLifecycleEventEffect
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)
- Après avoir synchronisé le projet pour télécharger la dépendance, ajoutez un
LifecycleEventEffect
qui s'exécute lors de l'événementON_PAUSE
pour mettre en pause la lecture (et éventuellement lors de l'événementON_RESUME
pour reprendre la lecture).
PlayerScreen.kt
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
...
@Composable
fun PlayerScreen(...) {
...
LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) {
viewModel.pause()
}
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
viewModel.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)
- Pour commencer, ouvrez SDK Manager dans l'aperçu d'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.
- Installez l'image d'émulateur Automotive Distant Display with Google Play (Écran distant automobile avec Google Play) de l'API 33 correspondant à l'architecture de votre ordinateur (x86/ARM).
Créer un appareil virtuel Android pour Android Automotive OS
- 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 with Google Play (Écran distant automobile avec Google Play) dans la liste, puis cliquez sur Next (Suivant).
- 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.
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 :
- La lecture est saccadée lorsque l'application est déplacée vers ou depuis l'écran distant.
- 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
Les saccades lors de la lecture sont dues 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.
- 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)
- Dans
PlayerViewModel
, ajoutez une variable pour contenir la session multimédia et créez uneMediaSession
à l'aide de son compilateur.
PlayerViewModel.kt
import androidx.media3.session.MediaSession
...
class PlayerViewModel(...) {
...
private var mediaSession: MediaSession? = null
init {
viewModelScope.launch {
_player.onEach { player ->
playerUiStateUpdateJob?.cancel()
mediaSession?.release()
if (player != null) {
initializePlayer(player)
mediaSession = MediaSession.Builder(application, player).build()
playerUiStateUpdateJob = viewModelScope.launch {... }
}
}.collect()
}
}
}
- Ajoutez ensuite une ligne dans la méthode
onCleared
pour libérerMediaSession
lorsquePlayerViewModel
n'est plus nécessaire.
PlayerViewModel.kt
override fun onCleared() {
super.onCleared()
mediaSession?.release()
_player.value?.release()
}
- Enfin, sur l'écran du lecteur (avec l'application sur l'écran principal ou distant), 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
Empêcher la reprise de la lecture
Bien que la prise en charge de MediaSession
permette de contrôler la lecture lorsque l'application est sur l'écran distant, elle introduit un nouveau problème. Plus précisément, cela permet de reprendre la lecture alors que des restrictions liées à l'expérience utilisateur sont en place, ce qui ne respecte pas les consignes DD-2 relatives à la qualité (encore une fois !). Pour tester cela par vous-même :
- Commencez la lecture.
- Simulez la conduite.
- Exécutez la commande
media_session dispatch
. Notez que la lecture reprend même si l'application est masquée.
Pour résoudre ce problème, vous pouvez écouter les restrictions de l'appareil liées à l'expérience utilisateur et n'autoriser la reprise de la lecture que lorsqu'elles sont actives. Vous pouvez même utiliser la même logique pour les appareils mobiles et Android Automotive OS.
- Dans le fichier
build.gradle
du moduleapp
, ajoutez l'élément suivant pour inclure la bibliothèque Android Automotive, puis effectuez une synchronisation Gradle :
build.gradle.kts
android {
...
useLibrary("android.car")
}
- Effectuez un clic droit sur le package
com.example.android.cars.roadreels
, puis sélectionnez New > Kotlin Class/File (Nouveau > Classe/Fichier Kotlin). SaisissezRoadReelsPlayer
comme nom, puis cliquez sur le type Class (Classe). - Dans le fichier que vous venez de créer, ajoutez l'implémentation de démarrage suivante de la classe. En étendant
ForwardingSimpleBasePlayer
, il est facile de modifier les commandes et les interactions compatibles avec un lecteur encapsulé en ignorant la méthodegetState()
.
RoadReelsPlayer.kt
import android.content.Context
import androidx.media3.common.ForwardingSimpleBasePlayer
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
@UnstableApi
class RoadReelsPlayer(context: Context) :
ForwardingSimpleBasePlayer(ExoPlayer.Builder(context).build()) {
private var shouldPreventPlay = false
override fun getState(): State {
val state = super.getState()
return state.buildUpon()
.setAvailableCommands(
state.availableCommands.buildUpon().removeIf(COMMAND_PLAY_PAUSE, shouldPreventPlay)
.build()
).build()
}
}
- Dans
PlayerViewModel.kt
, mettez à jour la déclaration de la variable player pour utiliser une instance deRoadReelsPlayer
au lieu deExoPlayer
. À ce stade, le comportement sera exactement le même qu'auparavant, carshouldPreventPlay
conserve toujours sa valeur par défautfalse
.
PlayerViewModel.kt
init {
...
_player.update { RoadReelsPlayer(application) }
}
- Pour commencer à suivre les restrictions liées à l'expérience utilisateur, ajoutez le bloc
init
et l'implémentationhandleRelease
suivants :
RoadReelsPlayer.kt
import android.car.Car
import android.car.drivingstate.CarUxRestrictions
import android.car.drivingstate.CarUxRestrictionsManager
import android.content.pm.PackageManager
import com.google.common.util.concurrent.ListenableFuture
...
@UnstableApi
class RoadReelsPlayer(context: Context) :
ForwardingSimpleBasePlayer(ExoPlayer.Builder(context).build()) {
...
private var pausedByUxRestrictions = false
private lateinit var carUxRestrictionsManager: CarUxRestrictionsManager
init {
with(context) {
// Only listen to UX restrictions if the device is running Android Automotive OS
if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
val car = Car.createCar(context)
carUxRestrictionsManager =
car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager
// Get the initial UX restrictions and update the player state
shouldPreventPlay =
carUxRestrictionsManager.currentCarUxRestrictions.isRequiresDistractionOptimization
invalidateState()
// Register a listener to update the player state as the UX restrictions change
carUxRestrictionsManager.registerListener { carUxRestrictions: CarUxRestrictions ->
shouldPreventPlay = carUxRestrictions.isRequiresDistractionOptimization
if (!shouldPreventPlay && pausedByUxRestrictions) {
handleSetPlayWhenReady(true)
invalidateState()
} else if (shouldPreventPlay && isPlaying) {
pausedByUxRestrictions = true
handleSetPlayWhenReady(false)
invalidateState()
}
}
}
addListener(object : Player.Listener {
override fun onEvents(player: Player, events: Player.Events) {
if (events.contains(EVENT_IS_PLAYING_CHANGED) && isPlaying) {
pausedByUxRestrictions = false
}
}
})
}
}
...
override fun handleRelease(): ListenableFuture<*> {
if (::carUxRestrictionsManager.isInitialized) {
carUxRestrictionsManager.unregisterListener()
}
return super.handleRelease()
}
}
Voici quelques points à noter :
CarUxRestrictionsManager
est stocké en tant que variablelateinit
, car il n'est pas instancié ni utilisé sur les appareils qui ne fonctionnent pas sous Android Automotive OS. Toutefois, l'espace doit être libéré sur son écouteur au moment de la publication du lecteur.- Seule la valeur
isRequiresDistractionOptimization
est référencée au moment de déterminer l'état de la restriction de l'expérience utilisateur. Bien que la classeCarUxRestrictions
contienne des informations supplémentaires sur les restrictions actives, il n'est pas nécessaire de les référencer, car elles ne sont destinées qu'aux applications optimisées contre la distraction (telles que les applications de navigation), qui restent visibles lorsque les restrictions sont actives. - Après toute modification de la variable
shouldPreventPlay
,invalidateState()
est appelé pour informer les consommateurs de l'état du lecteur. - Dans l'écouteur lui-même, la lecture est automatiquement suspendue ou reprise en appelant
handleSetPlayWhenReady
avec la valeur appropriée.
- Testez maintenant la reprise de la lecture en simulant la conduite, comme décrit au début de cette section. Vous remarquerez qu'elle ne reprend pas.
- Enfin, la mise en pause de la lecture au moment où les restrictions liées à l'expérience utilisateur deviennent actives étant gérée par
RoadReelsPlayer
, il n'est pas nécessaire queLifecycleEventEffect
mette en pause le lecteur pendantON_PAUSE
. Vous pouvez le remplacer parON_STOP
pour que la lecture s'arrête lorsque l'utilisateur quitte l'application pour accéder au lanceur d'applications ou ouvrir une autre application.
PlayerScreen.kt
LifecycleEventEffect(Lifecycle.Event.ON_START) {
viewModel.play()
}
LifecycleEventEffect(Lifecycle.Event.ON_STOP) {
viewModel.pause()
}
Résumé
L'application fonctionne ainsi beaucoup mieux dans les voitures, avec ou sans é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 apprendre à configurer l'émulateur pour obtenir une barre système à gauche, puis tester l'application dans cette configuration.
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
É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.
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.top_and_right
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.
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.
- 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 la valeur que vous utiliserez,LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
, a été introduite dans Android R, ajoutez également le qualificatif Version d'Android avec la valeur 30. Pour en savoir plus, consultez Utiliser d'autres ressources.
- 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.
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.WindowInsetsSides
import androidx.compose.foundation.layout.only
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.
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.
Pour une interface utilisateur plus immersive, vous pouvez gérer les encarts de chaque composant à l'écran.
- Mettez à jour l'élément
contentWindowInsets
deScaffold
pour qu'il soit constamment de 0 dp (et pas uniquement pourPlayerScreen
). 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)
) { ... }
- Définissez la propriété
windowInsetsPadding
des composablesText
de l'en-tête de ligne afin d'utiliser les composants horizontaux des encartssafeDrawing
. 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))
)
...
}
- Supprimez le paramètre
contentPadding
deLazyRow
. Ensuite, au début et à la fin de chaqueLazyRow
, ajoutez un élémentSpacer
de la largeur du composantsafeDrawing
correspondant pour vous assurer que toutes les vignettes peuvent s'afficher entièrement. Utilisez le modificateurwidthIn
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))
)
}
}
- Enfin, ajoutez un
Spacer
à la fin deLazyColumn
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 deLazyColumn
, 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 unSpacer
au début de la liste à l'aide du modificateurwindowInsetsTopHeight
. 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))
}
}
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.
Corriger l'écran de détails
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)
) { ... }
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.
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)
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.
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
- Remplacez certaines valeurs de ressources de dimension pour augmenter la taille des éléments lorsque vous exécutez l'application dans une voiture.
- Essayez encore plus de configurations de l'émulateur configurable.
- Testez l'application à l'aide de certaines des images d'émulateur OEM disponibles.
Complément d'informations
- Créer des applications à utiliser à l'arrêt pour Android Automotive OS
- Créer des applications vidéo pour Android Automotive OS
- Créer des jeux pour Android Automotive OS
- Créer des navigateurs pour Android Automotive OS
- La page Qualité des applis Android pour les voitures décrit les critères auxquels votre application doit répondre pour créer une expérience utilisateur optimale et réussir l'examen du Play Store. Veillez à filtrer les critères en fonction de votre catégorie d'application.