Assurer la compatibilité avec différentes densités de pixels

Les appareils Android ont non seulement des tailles d'écran différentes (téléphones, tablettes, téléviseurs, etc.), mais également des écrans de différentes tailles en pixels. Un appareil peut avoir 160 pixels par pouce, tandis qu'un autre peut accueillir 480 pixels dans le même espace. Si vous ne prenez pas en compte ces variations de densité de pixels, le système peut mettre à l'échelle vos images, provoquant ainsi des images floues, ou leur taille risque d'être incorrecte.

Cette page vous explique comment concevoir votre application pour qu'elle accepte différentes densités de pixels en utilisant des unités de mesure indépendantes de la résolution et en fournissant d'autres ressources bitmap pour chaque densité de pixels.

Regardez la vidéo ci-dessous pour découvrir ces techniques.

Pour en savoir plus sur la conception d'éléments d'icône, consultez les consignes relatives aux icônes Material Design.

Utiliser des pixels indépendants de la densité

Évitez d'utiliser des pixels pour définir des distances ou des tailles. Définir des dimensions en pixels est problématique, car les différents écrans ont des densités de pixels différentes. Par conséquent, le même nombre de pixels correspond à différentes tailles physiques sur différents appareils.

Image montrant deux exemples d'écrans d'appareils avec des densités différentes
Figure 1: Deux écrans de même taille peuvent avoir un nombre de pixels différent.

Pour conserver la taille visible de votre UI sur des écrans de différentes densités, concevez-la en utilisant des pixels indépendants de la densité (dp) comme unité de mesure. Un dp correspond à une unité virtuelle de pixels d'environ 1 pixel sur un écran de densité moyenne (160 ppp, densité "de référence"). Android traduit cette valeur en nombre de pixels réels pour chaque densité.

Prenons l'exemple des deux appareils de la figure 1. Une vue de 100 pixels de large est beaucoup plus grande sur l'appareil de gauche. Une vue définie sur une largeur de 100 dp apparaît de la même taille sur les deux écrans.

Lorsque vous définissez des tailles de texte, vous pouvez utiliser des pixels évolutifs (sp) comme unités. Par défaut, l'unité sp est de la même taille que les dp, mais elle est redimensionnée en fonction de la taille de texte préférée de l'utilisateur. N'utilisez jamais sp pour les tailles de mise en page.

Par exemple, pour spécifier l'espacement entre deux vues, utilisez dp:

<Button android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/clickme"
    android:layout_marginTop="20dp" />

Lorsque vous spécifiez la taille du texte, utilisez sp:

<TextView android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="20sp" />

Convertir les unités dp en unités de pixels

Dans certains cas, vous devez exprimer les dimensions en dp, puis les convertir en pixels. La conversion des unités dp en pixels d'écran est la suivante:

px = dp * (dpi / 160)

Remarque:Ne codez jamais cette équation en dur pour calculer les pixels. Utilisez plutôt TypedValue.applyDimension(), qui convertit en pixels de nombreux types de dimensions (dp, sp, etc.).

Imaginez une application dans laquelle un geste de défilement ou de glissement d'un geste vif est reconnu après un déplacement d'au moins 16 pixels. Sur un écran de référence, le doigt de l'utilisateur doit déplacer la 16 pixels / 160 dpi, ce qui correspond à 2,5 mm, avant que le geste ne soit reconnu.

Sur un appareil doté d'un écran haute densité (240 ppp), le doigt de l'utilisateur doit déplacer 16 pixels / 240 dpi, ce qui équivaut à 1,7 mm. La distance est beaucoup plus courte et l'application semble donc plus sensible à l'utilisateur.

Pour résoudre ce problème, exprimez le seuil de geste dans le code en dp, puis convertissez-le en pixels réels. Par exemple :

Kotlin

// The gesture threshold expressed in dp
private const val GESTURE_THRESHOLD_DP = 16.0f

private var gestureThreshold: Int = 0

// Convert the dps to pixels, based on density scale
gestureThreshold = TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  resources.displayMetrics).toInt()

// Use gestureThreshold as a distance in pixels...

Java

// The gesture threshold expressed in dp
private final float GESTURE_THRESHOLD_DP = 16.0f;

// Convert the dps to pixels, based on density scale
int gestureThreshold = (int) TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  getResources().getDisplayMetrics());

// Use gestureThreshold as a distance in pixels...

Le champ DisplayMetrics.density spécifie le facteur d'échelle utilisé pour convertir les dp en pixels en fonction de la densité de pixels actuelle. Sur un écran de densité moyenne, DisplayMetrics.density correspond à 1.0 sur un écran haute densité, à 1,5. Sur un écran extra-haute densité, ce nombre est égal à 2.0.Sur un écran basse densité, il est égal à 0, 75. Ce chiffre est utilisé par TypedValue.applyDimension() pour obtenir le nombre réel de pixels pour l'écran actuel.

Utiliser des valeurs de configuration pré-mises à l'échelle

Vous pouvez utiliser la classe ViewConfiguration pour accéder aux distances, vitesses et heures courantes utilisées par le système Android. Par exemple, la distance en pixels utilisée par le framework en tant que seuil de défilement peut être obtenue avec getScaledTouchSlop():

Kotlin

private val GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).scaledTouchSlop

Java

private final int GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).getScaledTouchSlop();

Les méthodes de ViewConfiguration commençant par le préfixe getScaled renvoient une valeur en pixels qui s'affiche correctement, quelle que soit la densité de pixels actuelle.

Privilégier les graphiques vectoriels

Plutôt que de créer plusieurs versions d'une image spécifiques à la densité, vous pouvez créer un seul graphique vectoriel. Les graphiques vectoriels créent une image au format XML pour définir des chemins et des couleurs, au lieu de bitmaps de pixels. En tant que tels, les graphiques vectoriels peuvent être mis à l'échelle à n'importe quelle taille sans mettre à l'échelle les artefacts, bien qu'ils conviennent généralement aux illustrations telles que les icônes, et non aux photographies.

Les graphiques vectoriels sont souvent fournis sous forme de fichiers SVG (Scalable Vector Graphics), mais Android n'est pas compatible avec ce format. Vous devez donc convertir les fichiers SVG au format drawable vectoriel d'Android.

Vous pouvez convertir un SVG en drawable vectoriel à l'aide de Vector Asset Studio d'Android Studio comme suit:

  1. Dans la fenêtre Project (Projet), effectuez un clic droit sur le répertoire res, puis sélectionnez New > Vector Asset (Nouveau > Élément vectoriel).
  2. Sélectionnez Fichier local (SVG, PSD).
  3. Recherchez le fichier que vous souhaitez importer et effectuez les ajustements nécessaires.

    Image montrant comment importer des SVG dans Android Studio
    Figure 2: Importation d'un SVG avec Android Studio

    Vous remarquerez peut-être des erreurs dans la fenêtre Asset Studio indiquant que les drawables vectoriels ne sont pas compatibles avec certaines propriétés du fichier. Cela ne vous empêche pas d'importer le fichier ; les propriétés non compatibles sont ignorées.

  4. Cliquez sur Suivant.

  5. Sur l'écran suivant, confirmez l'ensemble de sources où vous souhaitez insérer le fichier dans votre projet, puis cliquez sur Terminer.

    Étant donné qu'un drawable vectoriel peut être utilisé pour toutes les densités de pixels, ce fichier se trouve dans votre répertoire de drawables par défaut, comme illustré dans la hiérarchie suivante. Vous n'avez pas besoin d'utiliser des répertoires spécifiques à la densité.

    res/
      drawable/
        ic_android_launcher.xml
    

Pour en savoir plus sur la création de graphiques vectoriels, consultez la documentation sur les drawables vectoriels.

Fournir d'autres bitmaps

Pour offrir une bonne qualité graphique sur des appareils avec différentes densités de pixels, fournissez plusieurs versions de chaque bitmap dans votre application (une pour chaque bucket de densité, avec une résolution correspondante). Sinon, Android doit mettre à l'échelle votre bitmap afin qu'il occupe le même espace visible sur chaque écran, ce qui entraîne des artefacts de scaling tels que le floutage.

Image montrant des tailles relatives pour des bitmaps avec différentes densités
Figure 3: Tailles relatives pour les bitmaps dans différents buckets de densité.

Vous pouvez utiliser plusieurs niveaux de densité dans vos applications. Le tableau 1 décrit les différents qualificatifs de configuration disponibles et les types d'écran auxquels ils s'appliquent.

Tableau 1. Qualificatifs de configuration pour différentes densités de pixels.

Qualificatif de densité Description
ldpi Ressources pour les écrans basse densité (ldpi) (~120 ppp).
mdpi Ressources pour les écrans de densité moyenne (mdpi) (~160 ppp). Il s'agit de la densité de référence.
hdpi Ressources pour les écrans haute densité (hdpi) (~240 ppp).
xhdpi Ressources pour les écrans extra-haute densité (xhdpi) (~320 ppp).
xxhdpi Ressources pour les écrans extra-extra-haute densité (xxhdpi) (environ 480 ppp).
xxxhdpi Ressources pour les utilisations extra-extra-extra-haute densité (xxxhdpi) (~640 ppp).
nodpi Ressources pour toutes les densités. Il s'agit de ressources indépendantes de la densité. Le système n'adapte pas les ressources taguées avec ce qualificatif, quelle que soit la densité de l'écran actuel.
tvdpi Ressources pour les écrans allant de mdpi à hdpi (environ 213 ppp). Il n'est pas considéré comme un groupe de densité "principal". Il est principalement destiné aux téléviseurs et la plupart des applications n'en ont pas besoin. Fournir des ressources mdpi et hdpi suffit pour la plupart des applications, et le système les met à l'échelle en conséquence. Si vous estimez nécessaire de fournir des ressources tvdpi, redimensionnez-les selon un facteur de 1,33 * mdpi. Par exemple, une image de 100 x 100 pixels pour les écrans mdpi mesure 133 x 133 pixels pour tvdpi.

Pour créer d'autres drawables bitmap pour différentes densités, suivez le ratio de mise à l'échelle 3:4:6:8:12:16 entre les six densités principales. Par exemple, si vous disposez d'un drawable bitmap de 48 x 48 pixels pour les écrans de densité moyenne, les tailles sont les suivantes:

  • 36 x 36 (0,75 fois) pour faible densité (ldpi)
  • 48 x 48 (version de référence 1,0) pour densité moyenne (mdpi)
  • 72 x 72 (1,5 fois) pour haute densité (hdpi)
  • 96 x 96 (2,0 x) pour extra-haute densité (xhdpi)
  • 144 x 144 (3 fois) pour extra-extra-haute densité (xxhdpi)
  • 192 x 192 (4 fois) pour extra-extra-extra-haute densité (xxxhdpi)

Placez les fichiers image générés dans le sous-répertoire approprié sous res/:

res/
  drawable-xxxhdpi/
    awesome_image.png
  drawable-xxhdpi/
    awesome_image.png
  drawable-xhdpi/
    awesome_image.png
  drawable-hdpi/
    awesome_image.png
  drawable-mdpi/
    awesome_image.png

Ensuite, chaque fois que vous référencez @drawable/awesomeimage, le système sélectionne le bitmap approprié en fonction de la résolution de l'écran. Si vous ne fournissez pas de ressource spécifique à la densité pour cette densité, le système localise la correspondance la plus proche et l'adapte à l'écran.

Conseil:Si vous disposez de ressources drawables que vous ne souhaitez pas que le système mette à l'échelle (par exemple, lorsque vous apportez vous-même des ajustements à l'image au moment de l'exécution), placez-les dans un répertoire avec le qualificatif de configuration nodpi. Les ressources associées à ce qualificatif sont considérées comme indépendantes de la densité, et le système ne les met pas à l'échelle.

Pour en savoir plus sur les autres qualificatifs de configuration et sur la manière dont Android sélectionne les ressources appropriées pour la configuration d'écran actuelle, consultez la présentation des ressources d'application.

Placer les icônes d'application dans les répertoires mipmap

Comme pour les autres éléments bitmap, vous devez fournir des versions de l'icône d'application spécifiques à la densité. Cependant, certains lanceurs d'applications affichent l'icône de votre application jusqu'à 25 % plus grande que celle prévue par le bucket de densité de l'appareil.

Par exemple, si le bucket de densité d'un appareil est xxhdpi et que la plus grande icône d'application que vous fournissez se trouve dans drawable-xxhdpi, le lanceur d'applications redimensionne cette icône pour la rendre moins nette.

Pour éviter cela, placez toutes les icônes d'application dans des répertoires mipmap au lieu de répertoires drawable. Contrairement aux répertoires drawable, tous les répertoires mipmap sont conservés dans l'APK, même si vous compilez des APK spécifiques à la densité. Cela permet aux applications de lanceur de choisir la meilleure icône de résolution à afficher sur l'écran d'accueil.

res/
  mipmap-xxxhdpi/
    launcher_icon.png
  mipmap-xxhdpi/
    launcher_icon.png
  mipmap-xhdpi/
    launcher_icon.png
  mipmap-hdpi/
    launcher_icon.png
  mipmap-mdpi/
    launcher_icon.png

Dans l'exemple précédent d'appareil xxhdpi, vous pouvez fournir une icône de lanceur de plus grande densité dans le répertoire mipmap-xxxhdpi.

Pour obtenir des consignes sur la conception des icônes, consultez l'article Icônes système.

Si vous avez besoin d'aide pour créer des icônes d'application, consultez Créer des icônes d'application avec Image Asset Studio.

Conseils pour les problèmes de densité peu courants

Cette section décrit comment Android effectue la mise à l'échelle des bitmaps sur différentes densités de pixels et comment vous pouvez contrôler davantage la façon dont les bitmaps sont dessinés en fonction de différentes densités. Vous pouvez ignorer cette section, sauf si votre application manipule les éléments graphiques ou si vous rencontrez des problèmes lors de son exécution avec différentes densités de pixels.

Pour mieux comprendre comment prendre en charge plusieurs densités lors de la manipulation des graphiques au moment de l'exécution, vous devez savoir comment le système s'assure de l'échelle appropriée des bitmaps. Pour ce faire, procédez comme suit:

  1. Pré-scaling des ressources, telles que des drawables bitmap

    En fonction de la densité de l'écran actuel, le système utilise les ressources spécifiques à la densité de votre application. Si les ressources ne sont pas disponibles avec la bonne densité, le système les charge et les adapte en fonction de la densité. Le système suppose que les ressources par défaut (celles d'un répertoire sans qualificatifs de configuration) sont conçues pour la densité de pixels de référence (mdpi) et redimensionne ces bitmaps à la taille appropriée pour la densité de pixels actuelle.

    Si vous demandez les dimensions d'une ressource pré-mise à l'échelle, le système renvoie des valeurs représentant les dimensions après le scaling. Par exemple, un bitmap conçu à 50 x 50 pixels pour un écran mdpi est réduit à 75 x 75 pixels sur un écran hdpi (s'il n'existe aucune autre ressource pour hdpi), et le système indique la taille en tant que telle.

    Dans certains cas, vous ne souhaiterez peut-être pas qu'Android effectue le pré-scaling d'une ressource. Le moyen le plus simple d'éviter le pré-scaling consiste à placer la ressource dans un répertoire de ressources à l'aide du qualificatif de configuration nodpi. Par exemple :

    res/drawable-nodpi/icon.png

    Lorsque le système utilise le bitmap icon.png de ce dossier, il ne le met pas à l'échelle en fonction de la densité actuelle de l'appareil.

  2. Autoscaling des dimensions et des coordonnées en pixels

    Vous pouvez désactiver les dimensions et les images de pré-mise à l'échelle en définissant android:anyDensity sur "false" dans le fichier manifeste, ou par programmation pour un Bitmap en définissant inScaled sur "false". Dans ce cas, le système effectue un autoscaling de toutes les coordonnées absolues en pixels et des valeurs de dimension en pixels au moment du dessin. Cela permet de garantir que les éléments d'écran définis par pixel s'affichent toujours à une taille physique quasiment identique à celle qu'ils peuvent afficher avec la densité de pixels de référence (mdpi). Le système gère cette mise à l'échelle de manière transparente pour l'application et lui signale les dimensions en pixels mises à l'échelle, plutôt que les dimensions physiques en pixels.

    Par exemple, supposons qu'un appareil dispose d'un écran haute densité WVGA (480 x 800) et à peu près de la même taille qu'un écran HVGA traditionnel, mais qu'il exécute une application qui a désactivé le pré-scaling. Dans ce cas, le système est "lié" à l'application lorsqu'il demande des dimensions d'écran et signale 320 x 533, soit la traduction mdpi approximative pour la densité en pixels.

    Ensuite, lorsque l'application effectue des opérations de dessin, telles que l'invalidation d'un rectangle de (10,10) à (100, 100), le système transforme les coordonnées en les mettant à l'échelle de la quantité appropriée, puis invalide la région (15,15) en (150, 150). Cet écart peut entraîner un comportement inattendu si votre application manipule directement le bitmap mis à l'échelle. Toutefois, cela est considéré comme un compromis raisonnable pour garantir les meilleures performances possible de l'application. Dans ce cas, consultez la section Convertir les unités dp en unités de pixels.

    En général, vous ne désactivez pas le pré-scaling. La meilleure façon de prendre en charge plusieurs écrans est de suivre les techniques de base décrites sur cette page.

Si votre application manipule des bitmaps ou interagit directement avec les pixels à l'écran d'une autre manière, vous devrez peut-être prendre des mesures supplémentaires pour prendre en charge différentes densités de pixels. Par exemple, si vous répondez à des gestes tactiles en comptant le nombre de pixels traversés par un doigt, vous devez utiliser les valeurs de pixels indépendantes de la densité appropriées au lieu des pixels réels, mais vous pouvez convertir les valeurs dp et px.

Tester toutes les densités de pixels

Testez votre application sur plusieurs appareils avec différentes densités de pixels afin de vous assurer que l'UI s'adapte correctement. Effectuez les tests sur un appareil physique si possible. Utilisez Android Emulator si vous n'avez pas accès à des appareils physiques pour toutes les densités de pixels.

Si vous souhaitez effectuer des tests sur des appareils physiques, mais que vous ne souhaitez pas acheter les appareils, vous pouvez utiliser Firebase Test Lab pour accéder aux appareils dans un centre de données Google.