Developpez.com - Android
X

Choisissez d'abord la catégorieensuite la rubrique :


Les Unités de Mesure

Et les interfaces fluides

Par Nicolas RomantzoffSite personnel

 

Public visé : débutant

Lors de la réalisation d'une interface sous Android, on est amené à choisir parmi un certain nombre d'unités de mesure. La plus classique est 'dp', celle que l'on est le plus tenté d'utiliser est 'px'. Dans les deux cas, ce n'est pas obligatoirement un bon choix. Cet article essaye d'expliquer pourquoi, et de faire le tour des unités possibles.

       Version PDF   Version hors-ligne   Version eBooks
Viadeo Twitter Facebook Share on Google+        



I. Introduction
II. Les unités de mesure dans Android.
II-A. Unités Physiques
II-A-1. Millimetres, Pouces et Points.
II-A-2. Pixels.
II-B. Unités Virtuelles
II-B-1. Les "Density Independant Pixels"
II-B-2. Les "Scale Independant Pixels"
II-C. Exemples par l'image
II-C-1. Les résultats concrets.
II-C-2. Les DPI «Constructeur»
II-D. Quelle taille fait mon écran ?
III. Réalisation d'interfaces fluides
III-A. Sélections de Ressources
III-A-1. Sélection basée sur la densité.
III-A-2. Sélection basée sur la taille de l'écran (avant Honeycomb).
III-A-3. Sélection basée sur la taille de l'écran (depuis Honeycomb).
III-B. Implémentation de Layout
III-B-1. Les images fixes.
III-B-2. Les arrières-plans des Widgets
III-B-3. Les arrières-plans des Layout
III-C. Conclusion


I. Introduction

Un des aspect primordiaux d'Android est la quantité d'équipements différents. Ce choix du "hardware", si il est à l'avantage de l'utilisateur (gammes de prix, hardware évolutif,etc.) rend la programmation d'interfaces particulièrement difficile, et on se retrouve comme tout développeur HTML à hésiter entre une interface "fixe" (quitte à perdre de l'espace), ou une interface "fluide", ardue à maintenir et programmer.

Tout, en général, commence par une question: Quelle taille fait mon écran ?
Il est très facile de répondre à côté de la plaque... En effet la réponse dépend de l'unité de mesure dans laquelle on veut cette taille, et comme nous allons le voir, l'unité la plus adaptée n'est pas toujours celle que l'on croit.


II. Les unités de mesure dans Android.


II-A. Unités Physiques

Ces unités de mesure correspondent à une dimension concrète (mesurable physiquement):


II-A-1. Millimetres, Pouces et Points.

Les trois unités principales sont les millimètres (mm), les pouces (in) et les points (pt).

Ces unités sont en fait la même 'dimension', dans le sens ou on peut toujours passer de l'une à l'autre avec un simple calcul:

1in = 72pt (il y a 72 points dans un pouce)

1in = 25,4mm (un pouce fait 2,54 cm).

1mm = 2,835pt

Utiliser ces unités est une bonne manière de proposer une interface dont la taille visuelle est identique sur tous les équipements. Le seul handicap est que justement, cette taille fixe empêche l'utilisation d'outils d'accessibilité (interface grossie, taille de texte énorme,etc.), et surtout, il est quasiment impossible d'insérer des éléments graphiques en pixels (bitmaps par exemple) dans une interface en dimension physique sans d'odieux redimensionnements. L'utilisation de ces unités est donc fortement non recommandée.


II-A-2. Pixels.

Les pixels 'hardware' sont aussi une dimension physique... On peut à tout moment les compter. L'unité utilisée pour dimensioner en pixel est px.

Si cette unité semble toute adaptée pour une interface (d'ailleurs les positions/tailles des vues dans Android sont dans cette unité) et en particulier pour des bitmaps, elle rend l'implémentation d'interfaces «fluides» (voir ci-dessous) quasiment impossible.

D'autre part, les écrans ayant presque tous des densités de pixels différentes, une taille en pixel pourra être énorme sur un appareil, et tout petit sur un autre...

Même sur un même appareil, un fabriquant peut tout à fait décider de modifier la résolution de l'écran, et ainsi voir le même modèle (Galaxy Note 10" par exemple) avec deux résolutions (en pixels) différentes.

Les dimensions en pixels sont donc à proscrire le plus possible.


II-B. Unités Virtuelles

Ces unités ne sont pas mesurables directement.

En connaissant le hardware il est toujours possible de convertir d'une unité physique en une unité virtuelle (ou l'inverse), mais leur principal objectif est de justement s'affranchir du hardware, et de permettre ainsi aux développeurs de supporter le maximum d'équipements sans rien modifier à leur interface.

Toutes ces unités dépendent (plus ou moins directement) de la densité en pixels de l'écran (appelée 'dpi': dots per inch, ou pixels par pouces en français).


II-B-1. Les "Density Independant Pixels"

Aussi appelés dip ou dp, c'est sans doute l'unité la plus utilisée dans Android. Ils correspondent à «la taille en pixels qu'aurait l'interface sur un écran à 160dpi» (160 pixels par pouce).

px = dp * dpi / 160
ou
dp = px * 160 / dpi

Puisque sur un écran à 160dpi, il faut 160 pixels pour faire un pouce, on pourrait aussi dire que le nombre de 'dip' (ou 'dp') est la taille en 1/160ème de pouce. Hélas ce n'est pas vrai, sur les terminaux dont la densité en pixels n'est pas exactement l'une des quatre densités prédéfinies sur Android... nous verrons pourquoi plus loin.

L'énorme avantage de cette mesure, est de pouvoir calculer une taille 'visuelle' quasiment identique sur tous les terminaux, en s'affranchissant entièrement de leur résolution ou de leur taille physique. Ainsi tous les terminaux auront une largeur / hauteur d'écran exprimée en 'dp'.


II-B-2. Les "Scale Independant Pixels"

Il existe une autre unité "virtuelle", les 'sp' (Scale Independant Pixels). Cette taille est basée sur les 'dp', mais intègre le choix de l'utilisateur quant à la taille du texte. Cette unité est particulièrement importante, en particulier pour les utilisateurs nécessitant des aides d'accessibilité !

Par défaut, 1dp = 1sp, mais si l'utilisateur choisit une taille de texte différente, on aura:
1dp = f * 1sp (f étant le facteur d'agrandissement du texte).


II-C. Exemples par l'image


II-C-1. Les résultats concrets.

Une image valant mieux qu'un long discours, voici 4 interfaces identiques. La première sur un écran à 160dpi, la seconde sur un écran à 240dpi, la troisième sur un écran à 240dpi dont la taille de texte a été modifiée, et la dernière sur un Galaxy S2.



II-C-2. Les DPI «Constructeur»

Comme le montre les images, sur les densités 160dpi & 240dpi le résultat attendu est bien cohérent, 160dp = 1 pouce. Mais pourquoi donc sur le Galaxy S2 cela n'est-il pas le cas?
La réponse tient dans une autre question:

Les designers sont susceptibles, et n'aiment pas voir leur travail pourri par un redimensionnement (même très bien réalisé), et peuvent se mettre à faire grève pour un pixel de travers. Et cela se comprend ! Dans ces conditions, comment être sur qu'une image de 64x64 pixels par exemple, fasse bien 64x64 pixels à l'écran (quand elle devra être incluse dans une vue dimensionnée en 'dp') ?

La solution de Google est simple, le constructeur de l'appareil choisit l'une des 4 densités prédéfinies: 'ldpi' (120dpi), 'mdpi' (160dpi), 'hdpi' (240dpi), ou 'xdpi' (320dpi) pour l'écran (il existe une cinquième résolution: 'tvdpi', mais non documentée pour l'instant).

Une fois cette densité choisie, elle sera appliquée partout, quelle que soit la véritable densité du téléphone. Un designer pourra donc faire une image, et au pire, voire cette image avec des pixels doublés, ou divisés par deux. En aucun cas le système n'aura à effectuer un redimensionnement compliqué (et périlleux)...

De plus le designer peut fournir une version pour chacune des densités et ainsi être sur que un pixel d'une bitmap correspondra toujours à un pixel à l'écran, quel que soit le terminal.

On a donc au final deux densités: une densité hardware (hsd= Hardware Screen Density) qui est la densité réelle physique de l'écran, et une densité software (dpi) qui est la densité choisie par le constructeur.

Si on prend l'exemple du Galaxy S2, celui-ci a une densité hardware de 217dpi, et une densité software 'hdpi' (soit 240dpi).

Les fonctions de conversion pixels/dp restent inchangées... Et on a:

in = px / hsd = (dp * dpi) / (160 * hsd)

Sur le Galaxy S2, donc on voit bien que 1in = 217px = 144,6dp


II-D. Quelle taille fait mon écran ?

Comme on l'a vu, un écran possède au moins 3 tailles :

A ces trois tailles, Android va rajouter une quatrième, relative à la taille en «dp» de l'écran, afin de catégoriser le type d'écran :

Notre Galaxy S2 a donc une taille 'normal'... Il est bon de noter que si Samsung avait choisi une densité 'mdpi' pour le téléphone, il aurait alors eu une taille 'large'!


III. Réalisation d'interfaces fluides

Comment dans ces conditions réaliser une interface qui s'adapte automatiquement à l'écran ?

Tout d'abord, en choisissant les bonnes ressources...


III-A. Sélections de Ressources


III-A-1. Sélection basée sur la densité.

Nous avons vu que l'écran se voit attribué une densité (software) qui peut être 'ldpi', 'mdpi', 'hdpi' ou 'xhdpi'.

Quand une ressource est chargée, il est possible de spécifier pour quelle densité software cette ressource s'applique, et ainsi fournir une version de la ressource pour chaque densité.

Si ceci n'a aucun intérêt pour la plupart des ressources, les ressources de types «drawable» basées sur des bitmaps bénéficient directement de cette sélection, avec la possibilité pour le designer de peaufiner les redimensionnements avec tous les filtres qu'il veut.

A noter que si la ressource n'existe pas dans la densité voulue, Android va utiliser une autre densité (quitte à choisir la densité par défaut) et redimensionner la ressource automatiquement. Cette solution marche assez bien pour les images simples (produits, contacts, photos) mais assez mal pour les éléments d'interface tels que les boutons, check-mark qui nécessitent un traitement particulier des bordures / effets d'ombre...


III-A-2. Sélection basée sur la taille de l'écran (avant Honeycomb).

Comme on l'a vu, Android a aussi assigné une «taille» à l'écran: 'small','normal','large' ou 'xlarge'... Cette taille peut aussi servir de qualificatif aux ressources.

Par exemple, si on a deux modes d'interface: un mode 'vertical' et un mode 'horizontal' qui nécessite au moins 470dp en largeur...

On va avoir deux «layouts». Le layout «vertical» sera placé dans les ressources 'small' et 'normal-portrait'. Le layout «horizontal» sera placé dans les ressources par défaut.

Et voilà, le système va sélectionner le bon layout, et utiliser le mode «horizontal» que dans le cas ou l'on a 470dp de large!


III-A-3. Sélection basée sur la taille de l'écran (depuis Honeycomb).

Depuis Android 3.0, il est possible de spécifier une ressource directement en fonction de la largeur de l'écran en dp... Le choix peut inclure l'orientation ou non...

Ainsi, disons qu'à partir de 480dp de large, notre interface va passer sur un mode 'horizontal' plutôt que vertical: on va alors spécifier une ressource avec le qualificatif "w480dp"...
Dès que la partie horizontale de l'écran dépasse 480dp, cette ressource sera choisie. Par exemple, le Galaxy S2 en mode 'landscape'.

Il est possible de faire de même pour la hauteur: "h480dp" choisira la ressource dès que la hauteur dépasse 480dp.... Ce qui inclus le Galaxy S2 en mode portrait.

Il est aussi possible de faire le test sur la largeur minimale de l'écran (la plus petite des dimensions): "sw480dp" par exemple ne passera pas sur un GalaxyS2 (mais "sw320dp" oui).

«sw320dp» est donc quasiment identique au qualificatif «normal», «sw480dp» pour «large» et «sw720dp» pour «xlarge».


III-B. Implémentation de Layout

La plupart du temps, l'implémentation va se baser sur un layout principal qui occupe tout l'espace ("fill_parent" ou "match_parent" selon la version).

L'ensemble des TextView / EditText ne devraient pas poser de problème... La ou l'on va commencer à se tirer les cheveux c'est quand on voudra mélanger des bitmaps et du texte.


III-B-1. Les images fixes.

J'appelle ici 'image' tout représentation d'une bitmap de dimension fixe, que ce soit par une ImageView ou tout autre moyen. Elle est fixe dans le sens ou sa taille (en pixel) est prédéfinie. C'est le cas, par exemple, pour l'image d'un contact, ou d'un produit. Il n'est généralement pas possible d'avoir plusieurs résolutions de l'image, et, en général, l'image est toujours de la même taille. Il y a alors deux possibilités:

La première consiste à laisser Androïd redimensionner l'image. On fixe alors la taille de l'ImageView à un certaine hauteur/largeur (en fonction de l'environnement autour de l'image, par exemple du texte). L'unité à utiliser est donc le 'sp' pour être relatif au texte, ou 'dp' au pire (très rare).

La seconde est de fixer la taille de l'image en pixels, et de fournir les règles aux objets environnants pour s'adapter à l'image-view (centrage, etc...). Comme nous allons le voir cette solution, si elle conserve toute la qualité de l'image originale, est particulièrement inadaptée.

Imaginons que vous ayez une image de 64x64 pixels dans une ImageView à gauche de quatre lignes de texte (16sp)... La taille totale du texte est de 64sp.

Si l'ImageView est définie en 'sp' (64sp par exemple), la taille de l'ImageView sera toujours identique à celle du texte, quel que soit l'écran... Et c'est à l'ImageView que vous direz comment afficher une image de 64 pixels, dans un région qui fera 48, 64, 96 ou 128 pixels de large. Centrée, redimensionnée, font partie des choix possibles... Mais elle fera toujours la même hauteur que les 4 lignes de texte.

Si l'ImageView est définie en pixels (64px), sur un écran mdpi, on aura donc 64 pixels de texte et 64 pixels d'image, sur un écran ldpi le texte fera 48 pixels (plus petit que l'image), sur un écran hdpi, il fera 96 pixels (plus grand que l'image). Il faudra donc une règle pour retailler le texte par rapport à une ImageView qui fera toujours 64 pixels et qui affichera toujours l'image entièrement (mais des fois l'image prendra toute la taille de l'écran, des fois juste un tout petit coin). Autant dire qu'il va devenir compliquer de gérer proprement l'interface autour de l'image.


III-B-2. Les arrières-plans des Widgets

Un bouton, est simplement un dessin de fond qui varie en fonction de l'état (focus, appuyé, selectionné, relaché, etc.). L'avant-plan peut être tout et n'importe quoi: du texte (classe 'Button' de base), une image ('ImageButton') ou tout un layout (aucune classe spécifique, mais n'importe quel layout est capable de réagir au 'press'/'unpress' et donc servir de bouton pour peu que l'arrière-plan le laisse penser à l'utilisateur).

Le problème est donc de fournir un arrière-plan cohérent qui s'adapte à la taille désirée du contenu du bouton. Et comme on l'a déjà fait remarquer, retailler une image donne souvent de pas très bons résultats. Ceci est d'autant plus vrai quand l'image contient des hautes fréquences (comme une bordure) qui ont tendance à baver à l'agrandissement, ou à disparaître lors de la réduction de taille.

Nous allons voir les solutions possibles avec un bouton contenants du texte de 16sp de haut.

Première solution: utiliser une image fixe... Cette solution est vraiment à exclure sauf dans de très très rare cas où le bouton contient une image fixe. En effet, il y a fort à parier que votre application n'utilisera pas un seul bouton, et il est plus que souhaitable que tous les boutons d'une application partagent la même imagerie (unité d'interface). La solution de l'imagerie fixe vous forcerait à proposer 4 images (selon les 4 résolutions possibles) pour chaque taille de bouton utilisée! Ce n'est ni pratique, ni économe en taille de ressources.
Pire encore, si l'utilisateur agrandi (ou rétrécit) le texte, le bouton ne sera plus adapté!

Deuxième solution: utiliser les «drawables vectoriels»... Ils sont particulièrement puissants sous Android (bords arrondis, dégradés, etc.). Mais malheureusement, on ne peut pas toujours tout faire avec eux. Par contre, cette solution a l'avantage ne devoir fournir qu'une définition vectorielle applicable à tous les boutons.

Dernière solution: Les 9-patch. Un 9-patch est une image dans laquelle on va spécifier 9 régions rectangulaires. Les régions dans les angles ne seront jamais redimensionnées. Dans ces régions, un pixel sera toujours un pixel. Les régions des bords seront redimensionnées que dans une seule direction. La partie centrale se verra redimensionnée entièrement. Au designer de ne pas remplir cette partie avec des hautes fréquences. En général d'ailleurs la région centrale ne fait que 1 pixel (qui sera donc répété au besoin). Le 9-patch donne aussi des informations de «padding» permettant d'indiquer à quel endroit le contenu (texte?) peut se situer.

Le 9-patch ne résout pas tout, et il n'empêche pas de fournir une version par densité... En particulier vis à vis du padding... Prenons un exemple simple: un 9-patch de 25x25 pixels, avec des régions définies comme 12-1-12 sur les deux axes, et un padding de 10 pixels sur tous les cotés...
Si nous prenons le bouton avec un texte de 16sp, en 'mdpi', le bouton final aura une hauteur de 10px (padding) + 16px (texte) + 10px (padding) = 36px de haut. Le texte occupe 44% du bouton.
Le même bouton en 'xhdpi' aura une hauteur de 10px (padding) + 32px (texte) + 10px (padding) = 52px de haut (67% de texte).
En ldpi, on aura 10px (padding) + 12px (texte) + 10px (padding) = 32px de haut (37% de texte).
Entre 63% de vide ('ldpi') et 33% de vide ('xhdpi'), il y a une grosse différence visuelle, d'où la nécessité de fournir des versions différentes pour chaque résolution, avec des padding redimensionnés comme il se doit...


III-B-3. Les arrières-plans des Layout

Dans le cas d'un arrière-plan général, le problème est tout autre... L'arrière-plan ne doit pas contenir de haute fréquence (afin de ne pas 'divertir' l'utilisateur) et ainsi peut être re-dimensionnable sans véritable problème (même si il est toujours possible de fournir plusieurs versions).


III-C. Conclusion

Comme nous l'avons vu, l'utilisation de "px" est une mauvaise idée dans 99% des cas. En général, la représentation visuelle d'une image se fait relativement aux éléments qui l'entourent (et a donc une taille en 'sp'). Seules les marges et les paddings auront éventuellement une taille en 'dp'.



               Version PDF   Version hors-ligne   Version eBooks

Valid XHTML 1.0 TransitionalValid CSS!

Copyright © 2012 Nicolas Romantzoff. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique Android : Feanorin -