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

La compatibilité avec différentes tailles d'écran permet d'accéder à votre application sur une grande variété 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 de sorte 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, aux tablettes, aux pliables, aux appareils ChromeOS, aux orientations portrait et paysage, ainsi qu'aux configurations redimensionnables telles que le mode multifenêtre.

Les mises en page responsives/adaptatives changent en fonction de l'espace d'affichage disponible. Les modifications vont de petits ajustements de mise en page qui remplissent l'espace (responsive design) au remplacement complet d'une mise en page par une autre afin que votre application puisse s'adapter au mieux aux différentes tailles d'affichage (conception adaptative).

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 en fonction d'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 plusieurs facteurs de forme d'appareils, dont un téléphone, un appareil pliable, une tablette et un ordinateur portable.
Figure 1 : Facteurs de forme pour téléphone, appareil pliable, tablette et ordinateur portable

Sur les tablettes, une application peut s'exécuter en mode multifenêtre, ce qui signifie qu'elle partage l'écran avec une autre application. Sous 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 physique de l'écran n'est pas pertinente 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. Le fait de rendre vos mises en page adaptables à l'espace d'écran disponible réduit également le nombre de manipulations spéciales nécessaires pour prendre en charge des plates-formes telles que ChromeOS, ainsi que les facteurs de forme tels que les tablettes et les pliables.

Une fois que vous avez observé 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é permettant d'optimiser votre application pour 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 appliquer 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 génère un état, qui peut être explicitement transmis à d'autres composables, comme vous le feriez pour tout autre état d'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 emplacement précis et d'une taille spécifique, il sera plus difficile de le réutiliser ailleurs ou avec une autre quantité d'espace disponible. Cela signifie également que les composables individuels et 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, pouvant 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 montrant une mise en page classique de type "Liste et vue détaillée" : 1 correspond à la zone de liste et 2 à la zone de détail.

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 plutôt qu'un composable modifie sa mise en page indépendamment en fonction de l'espace disponible ? (par exemple, une carte souhaitant afficher des détails supplémentaires si l'espace le permet). Nous voulons exécuter une logique en fonction d'une taille disponible, mais quelle taille en particulier ?

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

Comme nous l'avons vu ci-dessus, il ne faut pas essayer d'utiliser la taille réelle de l'écran de l'appareil. Cette information ne sera pas précise pour plusieurs écrans, ni ne le sera pas non plus 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 de dépenses, car BoxWithConstraints diffère la composition jusqu'à la phase de mise en page, lorsque ces contraintes sont connues, ce qui entraîne davantage de travail à effectuer 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.

Cependant, 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 un affichage approprié. Vous devez fournir suffisamment de données au composable pour qu'il ait toujours ce dont il a besoin pour s'afficher dans n'importe quelle taille, même si une partie des données n'est pas toujours 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.

La transmission continue des données simplifie les mises en page adaptatives en les rendant moins avec état et évite de déclencher des effets secondaires lors du changement de taille (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 par 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 constituent 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 UI pour utiliser l'espace disponible.
  • Répondre est un exemple adaptatif conçu pour les mobiles, les tablettes et les pliables.
  • Now in Android est une application qui utilise les mises en page adaptatives pour prendre en charge différentes tailles d'écran.

Vidéos