Assurer la compatibilité avec différentes tailles d'écran

La compatibilité avec différentes tailles d'écran permet d'accéder à votre application pour un très grand nombre d'appareils et pour un plus grand nombre d'utilisateurs.

Pour accepter un maximum de tailles d'écran, concevez vos mises en page d'application pour qu'elles soient responsives et adaptatives. Les mises en page responsives et adaptatives offrent une expérience utilisateur optimisée quelle que soit la taille de l'écran. Elles permettent à votre application de s'adapter aux téléphones, tablettes, appareils pliables, aux appareils ChromeOS, aux orientations portrait et paysage, et aux configurations redimensionnables comme le mode multifenêtre.

Les mises en page responsives et adaptatives changent en fonction de l'espace d'affichage disponible. Il peut s'agir de petits ajustements de mise en page qui remplissent l'espace (design responsif) ou d'un remplacement complet d'une mise en page par une autre pour que votre application s'adapte au mieux à différentes tailles d'écran (design adaptatif).

En tant que kit d'interface utilisateur déclaratif, Jetpack Compose est idéal pour concevoir et implémenter des mises en page qui changent de manière dynamique pour afficher le contenu différemment sur différentes tailles d'écran.

Rendre explicites les changements de mise en page importants pour les composables au niveau de l'écran

Lorsque vous utilisez Compose pour mettre en page une application entière, les composables au niveau de l'application et de l'écran occupent tout l'espace qui est alloué à cette application pour afficher l'interface. À ce niveau de la conception, il peut être judicieux de modifier la mise en page globale d'un écran afin d'exploiter les grands écrans.

Évitez d'utiliser des valeurs physiques et matérielles pour prendre des décisions concernant la mise en page. Il peut être tentant de prendre des décisions basées sur une valeur tangible fixe (l'appareil est-il une tablette ? L'écran physique a-t-il un certain format ?), mais les réponses à ces questions peuvent ne pas être utiles pour déterminer l'espace avec lequel votre interface utilisateur peut fonctionner.

Schéma illustrant différents facteurs de forme d'appareils, y compris téléphone, pliable, tablette et ordinateur portable.
Figure 1 : Facteurs de forme pour les téléphones, appareils pliables, tablettes et ordinateurs portables

Sur les tablettes, une application peut s'exécuter en mode multifenêtre, ce qui signifie qu'elle divise l'écran avec une autre application. Sur ChromeOS, une application peut se trouver dans une fenêtre redimensionnable. Il peut même y avoir plusieurs écrans physiques, par exemple avec les pliables. Dans tous ces cas, la taille de l'écran physique n'est pas importante pour décider comment afficher le contenu.

Vous devez plutôt prendre des décisions basées sur la partie de l'écran qui est réellement allouée à votre application, telles que les métriques de fenêtre actuelles fournies par la bibliothèque WindowManager de Jetpack. Pour découvrir comment utiliser WindowManager dans une application Compose, consultez l'exemple JetNews.

Cette approche permet à votre application d'être plus flexible, car son comportement sera approprié dans tous les scénarios ci-dessus. En adaptant vos mises en page à l'espace disponible à l'écran, vous réduisez également le nombre de tâches spéciales à prendre en charge pour les plates-formes comme ChromeOS, ainsi que les facteurs de forme tels que les tablettes et les pliables.

Une fois que vous avez évalué l'espace pertinent disponible pour votre application, il est utile de convertir la taille brute en une classe de taille significative, comme décrit dans la section Classes de taille de fenêtre. Les tailles sont ainsi regroupées dans des buckets de taille standard, qui sont des points d'arrêt conçus pour équilibrer la simplicité et la flexibilité nécessaire pour optimiser votre application dans la plupart des cas uniques. Ces classes de taille font référence à la fenêtre globale de votre application. Vous devez donc les utiliser pour les décisions qui affectent la mise en page globale de votre écran. Vous pouvez transmettre ces classes de taille en tant qu'état ou exécuter une logique supplémentaire pour créer un état dérivé à transmettre aux composables imbriqués.

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

Cette approche multicouche limite la logique de taille d'écran à un seul emplacement, au lieu de la disperser dans votre application à de nombreux endroits qui doivent être synchronisés. Cet emplacement unique produit un état, qui peut être explicitement transmis à d'autres composables comme vous le feriez pour tout autre état de l'application. La transmission explicite de l'état simplifie les composables individuels, car ils sont simplement traités comme des fonctions modulables standards qui utilisent la classe de taille ou la configuration spécifiée, ainsi que d'autres données.

Les composables imbriqués flexibles sont réutilisables

Les composables qui peuvent être placés à divers endroits sont plus facilement réutilisables. Si un composable suppose qu'il sera toujours placé à un certain emplacement avec une taille spécifique, il sera alors plus difficile de le réutiliser ailleurs dans un autre emplacement ou avec un autre espace disponible. Cela signifie également que les composables individuels réutilisables doivent éviter implicitement de dépendre d'informations de taille "globales".

Prenons l'exemple suivant: Imaginez un composable imbriqué qui implémente une mise en page liste/détails, qui peut afficher un ou deux volets côte à côte.

Capture d'écran d'une application affichant deux volets côte à côte.
Figure 2 : Capture d'écran d'une application présentant une mise en page de vue détaillée en liste : 1 correspond à la zone de liste et 2 à la zone de détails.

Cette décision doit faire partie de la mise en page globale de l'application. C'est pourquoi elle est transmise à partir d'un composable au niveau de l'écran, comme nous l'avons vu plus haut :

@Composable
fun AdaptivePane(
    showOnePane: Boolean,
    /* ... */
) {
    if (showOnePane) {
        OnePane(/* ... */)
    } else {
        TwoPane(/* ... */)
    }
}

Que se passe-t-il si nous voulons qu'un composable modifie indépendamment sa mise en page en fonction de l'espace disponible ? Par exemple, une fiche qui souhaite afficher des détails supplémentaires si l'espace le permet. Nous voulons exécuter une logique basée sur une taille disponible, mais quelle taille en particulier ?

Exemples de deux fiches différentes.
Figure 3 : Fiche étroite affichant uniquement une icône et un titre, et carte plus large affichant l'icône, le titre et une brève description.

Comme nous l'avons vu ci-dessus, nous devons éviter d'essayer d'utiliser la taille de l'écran réel de l'appareil. Ce ne sera pas précis pour plusieurs écrans, ni même si l'application n'est pas en plein écran.

Comme le composable n'est pas un composable au niveau de l'écran, les métriques de la fenêtre actuelles ne peuvent pas non plus être utilisées afin de maximiser la réutilisation. Si le composant est placé avec une marge intérieure (par exemple, pour des encarts) ou s'il existe des composants tels que des rails de navigation ou des barres d'application, l'espace disponible pour le composable peut différer considérablement de l'espace global disponible pour l'application.

Par conséquent, nous devons tenir compte de la largeur donnée au composable pour qu'il s'affiche. Deux options s'offrent à nous pour obtenir cette largeur :

Si vous souhaitez modifier l'emplacement ou le mode d'affichage du contenu, vous pouvez utiliser un ensemble de modificateurs ou une mise en page personnalisée pour la rendre responsive. Vous pouvez par exemple demander à un enfant de remplir tout l'espace disponible ou disposer plusieurs éléments enfants avec plusieurs colonnes s'il y a assez d'espace.

Si vous souhaitez modifier ce qui s'affiche, vous pouvez utiliser BoxWithConstraints comme alternative plus puissante. Ce composable fournit des contraintes de mesure que vous pouvez utiliser pour appeler différents composables en fonction de l'espace disponible. Toutefois, cela s'accompagne d'un certain nombre de frais, car BoxWithConstraints reporte la composition jusqu'à la phase de mise en page, lorsque ces contraintes sont connues, ce qui entraîne davantage de travail lors de la mise en page.

@Composable
fun Card(/* ... */) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }
}

S'assurer que toutes les données sont compatibles avec différentes tailles

Lorsque vous exploitez l'espace supplémentaire disponible à l'écran, vous pouvez avoir l'espace nécessaire pour présenter plus de contenu à l'utilisateur sur un grand écran que sur un petit écran. Lorsque vous implémentez un composable avec ce comportement, il peut être tentant d'en profiter pour charger les données en tant qu'effet secondaire de la taille actuelle.

Toutefois, cela va à l'encontre des principes du flux de données unidirectionnel, selon lequel les données peuvent être hissées et fournies aux composables pour s'afficher correctement. Vous devez fournir suffisamment de données au composable afin qu'il ait toujours ce qu'il a besoin pour s'afficher dans n'importe quelle taille, même si une partie des données peut ne pas toujours être utilisée.

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(description)
                }
                Image(imageUrl)
            }
        }
    }
}

En nous appuyant sur l'exemple de fiche (Card), notez que nous transmettons toujours la description à Card. Même si la description n'est utilisée que lorsque la largeur permet de l'afficher, Card l'exige toujours, quelle que soit la largeur disponible.

Le fait de toujours transmettre des données simplifie les mises en page adaptatives en les rendant moins avec état. Cela évite également de déclencher des effets secondaires lors du passage d'une taille à une autre (ce qui peut se produire en raison d'un redimensionnement de la fenêtre, d'un changement d'orientation ou du pliage et du dépliage d'un appareil).

Ce principe permet également de préserver l'état entre des modifications de mise en page. En hissant des informations qui peuvent ne pas être utilisées dans toutes les tailles, nous pouvons conserver l'état de l'utilisateur à mesure que la taille de la mise en page change. Par exemple, nous pouvons hisser un indicateur booléen showMore afin de préserver l'état de l'utilisateur lorsque les redimensionnements entraînent le masquage ou l'affichage de la description dans la mise en page:

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

En savoir plus

Pour en savoir plus sur les mises en page personnalisées dans Compose, consultez les ressources supplémentaires suivantes.

Exemples d'applications

  • Les mises en page standards sur grand écran sont un référentiel de modèles de conception éprouvés qui offrent une expérience utilisateur optimale sur les appareils à grand écran.
  • JetNews montre comment concevoir une application qui adapte son interface utilisateur afin d'utiliser l'espace disponible.
  • Répondre est un exemple adaptatif adapté aux mobiles, aux tablettes et aux appareils pliables.
  • Now in Android est une application qui utilise des mises en page adaptatives pour prendre en charge différentes tailles d'écran.

Vidéos