Tailles, marges et visibilité des vues sur Android
Si vous êtes un élève assidu, vous connaissez maintenant les premières arcanes de l’interface d’Android, de quoi commencer le développement de votre propre application. Or, on reconnaît la qualité d’un programme à sa finition, notamment graphique. Il ne s’agit pas seulement de positionner les éléments, encore faut ils qu’ils soient proportionnés intelligemment et disponibles quand on en à besoin, et uniquement à ce moment.
Le sujet de ce tutoriel Android sera donc :
- Connaître les éléments que l’on peut redimensionner et comment.
- Jouer avec les polices de caractère.
- Aérer sa mise en page en utilisant des marges.
- Afficher ou masquer une vue à la volée.
Pré-requis :
- Avoir évidement fait le tuto précédent.
- Avoir manipulé un toolkit graphique tel que SWIG ou AWT vous donnera des ailes.
- Une connaissance du HTML et du CSS, notamment du modèle de boîte, est un vrai plus.
Temps requis : 1 heure, si vous aimez lire les détails.
Toute vue a une taille
Les vues partagent un très grand nombre d’attributs, dont la gestion de la taille. Ça paraît évident ainsi énoncé, mais il est important de se le rappeler car cela à des conséquences dans toute la programmation. Par exemple, on peut considérer que les vues « EditText » sont l’équivalent des traditionnels champs « input ». Mais dans ce cas, quel l’équivalent d’une large zone de saisie, comme le « TextArea » ? Réponse : il n’y en a pas, on utilise aussi « EditText ».
En modifiant la taille d’une vue, on modifie aussi la manière dont l’utilisateur la perçoit. Voici deux « EditText » :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" /> <EditText android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
L’une est limitée à la taille du texte, l’autre s’étend complètement. Voici le résultat à l’écran :
« EditText » contient d’ailleurs tout ce qu’il faut pour répondre aux deux fonctions. Ainsi, la taille du premier champ s’adapte automatiquement à ce qu’il contient. A l’inverse, la deuxième reste fixe, et on peut spécifier l’apparition d’un ascenseur pour gérer le cas où le texte déborde :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" /> <!-- Une taille fixe, une scrollbar, et la hauteur ne bougera plus --> <EditText android:layout_width="fill_parent" android:layout_height="200px" android:scrollbars="vertical" /> </LinearLayout>
Du coup :
Nous ne faisons qu’effleurer ici les raffinements que permet Android. Il est impossible de tout lister ici, à moins de vouloir réécrire entièrement la documentation que 30 ingénieurs Google ont mis un an à pondre. C’est pourquoi, si vous avez un besoin particulier, je vous invite à farfouiller dans la doc. L’anglais est vraiment votre ami, pour le coup. On trouve des petites perles, comme la validation automatique des champs, ou le formatage. Pour vous donner envie de vous y plonger, voici une option d’aide à la saisie activable sur les « EditText » :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <!-- La première lettre de chaque mot est mise en majuscule automatiquement. Pratique pour faciliter la saisie des noms propres, une opération fastidieuse sur un petit clavier, et courante sur un téléphone. --> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:capitalize="words" /> <!-- Si le texte attendu doit être long, pourquoi ne pas activer les majuscules automatiques en début de phrase ? --> <EditText android:layout_width="fill_parent" android:layout_height="fill_parent" android:capitalize="sentences" /> </LinearLayout>
Très pratique :
Pour le texte, c’est pas la taille qui compte…
… c’est la façon de s’en servir. On peut spécifier la taille du texte en plusieurs unités sur Android, soit de manière absolue (pt, mm, px, etc), soit de manière relative (dip, sp…). Comme d’habitude, il est recommandé d’utiliser la dernière solution. Donner une taille fixe, c’est s’exposer à des aléas si la taille ou l’orientation de l’écran change. Une option à ne garder sous le coude que pour des design particuliers, par exemple pour les jeux. On préférera plutôt choisir d’afficher le texte proportionnellement à la résolution d’affichage. Voyez plutôt :
Côté XML, on a pas grand chose à changer :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <!-- Donné en pixels, la taille du texte ne change pas --> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="12px" /> <!-- Donné en DIP (« Device Independant Pixel », soit « Pixel Indépendant du Terminal »), le texte s'adapte à la taille de l'écran --> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="12dip" /> </LinearLayout>
Et côté écran, vous ne verrez pas de différence sur l’émulateur :
Mais dans le cas 2, votre texte s’adaptera si on passe sur un tout petit téléphone, ou un énorme. Vu le travail supplémentaire requis, ça vaut le coup.
« android:textSize » est valable pour toutes les vues qui incluent du texte.
Puisque l’on est dans la manipulation de texte, sachez que l’on peut mettre un peu de gaieté dans le format d’affichage :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <!-- On peut choisir de rendre du texte brute, Serif ou avec une police à chasse fixe avec "android:typeface" Ok, j'utilise "android:text" sans faire référence à string.xml. C'est mal. Ne le faites jamais. Je le fais parceque je des supers pouvoirs de formateur Android. --> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Texte par défaut 'Plain'" /> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:typeface="serif" android:text="Texte Serif" /> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:typeface="monospace" android:text="Texte avec police à chasse fixe" /> <!-- Si c'est important, "android:textStyle" permet de mettre le tout en gras ou en italique --> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="Du gras partout !" /> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="italic" android:text="De l'italique partout !" /> <!-- Bien sûr, cela marche aussi avec des TextView. Toute vue avec du texte, je vous dis ! --> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold|italic" android:text="Italique et gras, soyons fou." /> </LinearLayout>
Hop :
Marges internes et externes
Si les mots « padding » et « margin » en CSS sont synonymes de nuit blanche de debuggage pour faire plaisir à Firefox et IE, le problème est beaucoup plus simple avec Android : une plateforme, un comportement.
Voici un XML ventru qui va nous servir de base de travail :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:gravity="center" > <!-- On aligne 3 LinearLayout horizontaux dans un LinearLayout vertical pour donner l'effet d'une grille. Normalement pour cela on utilise TableLayout, mais pour voir les marges LinearLayout, sera plus pratique. --> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <!-- La TextView se compose ainsi : - Une largeur nulle et un layout_weight de 1, une astuce que l'on connait maintenant bien. - Une auteur de 24 px. - Un peu de texte dans "android:text". Ne faites jamais ça ! C'est pour l'exemple, nom de dieu ! - android:gravity="center" pour mettre le texte bien au milieu. - Une nouveauté android:background, qui permet de changer la couleur derrière le texte. On peut mettre des noms, des déclarations hexadécimales ou une image. Ici on alterne les couleurs flashy rouges et vertes en hexa pour bien voir les boîtes. - android:textColor, autre nouveauté qui détermine la couleur du texte. On le met en noir, pour bien le voir. --> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:text="Case 1" android:background="#FF0000" android:textColor="#000000" android:gravity="center" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#01FF00" android:text="Case 2" android:textColor="#000000" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#FF0000" android:text="Case 3" android:textColor="#000000" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:text="Case 4" android:background="#01FF00" android:textColor="#000000" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#FF0000" android:text="Case 5" android:textColor="#000000" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#01FF00" android:text="Case 6" android:textColor="#000000" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:text="Case 7" android:background="#FF0000" android:textColor="#000000" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#01FF00" android:text="Case 8" android:textColor="#000000" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#FF0000" android:text="Case 9" android:textColor="#000000" /> </LinearLayout> </LinearLayout>
Le résultat est très pratique, car il met bien en valeur le côté « boîte » :
Si on applique un margin à la case 5 :
<TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#FF0000" android:text="Case 5" android:textColor="#000000" android:gravity="center" android:layout_margin="20px" />
Alors on décale d’autant de pixel tout ce qui y a autour. Le margin agit comme une barrière de protection, une marge qui entoure la vue et ne peut être empiétée par les autres vues. Android fera ce qu’il pourra pour respecter la marge, y compris déplacer les vues. Comme vous pouvez le voir, la marge n’est pas incluse dans la taille de la vue, elle est ajoutée en plus :
Comme en CSS, on peut spécifier une marge sur un côté seulement. Par exemple, retirez la modification de la case 5, puis appliquez une marge seulement au bas du premier LinearLayout horizontal :
<LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="20px" >
Ce qui donne :
On peut appliquer le même principe à la marge intérieure, le « padding ». Revenez au XML initial, puis modifiez la première case pour lui donner une marge intérieure à gauche :
<TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:text="Case 1" android:background="#FF0000" android:textColor="#000000" android:gravity="center" android:paddingLeft="40px" />
Le texte se décale vers la droite, poussée par une marge à gauche :
Vous notez que en revanche, la taille de la marge intérieure n’est pas additionnée à la taille. Si vous en abusez (essayez android:paddingLeft=”80px”), vous dépasserez la taille de la boîte, et le texte sera tronqué :
Ceci s’explique ainsi :
- Quand vous utilisez le « margin », vous utilisez « Layout_margin ». Toutes les propriétés qui commencent par « layout » agissent sur un conteneur invisible qui englobe la vue, comme si vous aviez mis une vue Layout autour et que vous agissiez dessus.
- Quand vous utilisez le « padding », vous agissez directement sur la vue et déplacez son contenu. Les marges intérieures n’affectent que le contenu, c’est pour cela qu’on joue généralement avec le « padding » et la taille en même temps.
Question de visibilité
En plus de gérer les marges, vous pouvez choisir de faire apparaître une vue ou non en définissant son attribut « visibility ». Elle peut prendre 3 valeurs :
- visible : par défaut, on affiche la vue.
- invisible : la vue est là et prend toute la place qu’elle est supposée prendre, mais elle est parfaitement invisible.
- gone : la vue n’est pas visuellement là, elle ne prend pas de place et est invisible. L’objet est lui bien présent et vous pouvez le manipuler à votre guise. Il prend toujours autant de place en mémoire.
Reprenons notre XML, et voyons la différence :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:gravity="center" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:text="Case 1" android:background="#FF0000" android:textColor="#000000" android:gravity="center" android:visibility="visible" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#01FF00" android:text="Case 2" android:textColor="#000000" android:gravity="center" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#FF0000" android:text="Case 3" android:textColor="#000000" android:gravity="center" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:text="Case 4" android:background="#01FF00" android:textColor="#000000" android:gravity="center" android:visibility="invisible" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#FF0000" android:text="Case 5" android:textColor="#000000" android:gravity="center" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#01FF00" android:text="Case 6" android:textColor="#000000" android:gravity="center" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:text="Case 7" android:background="#FF0000" android:textColor="#000000" android:gravity="center" android:visibility="gone" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#01FF00" android:text="Case 8" android:textColor="#000000" android:gravity="center" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#FF0000" android:text="Case 9" android:textColor="#000000" android:gravity="center" /> </LinearLayout> </LinearLayout>
Première ligne, la case 1 est visible. Aucun changmement, puisque c’est le comportement par defaut. Deuxième ligne, la case 4 est invisible, la ligne ne bouge pas pas, mais on ne voit plus la case. Troisième ligne, la case 7 est « gone » (parti, en anglais) : on ne la voit pas et elle ne prend pas de place donc toute la ligne s’adapte à l’espace :
Et côté Java ?
Bien entendu toutes les propriétés que nous venons de voir sont accessibles depuis Java, via des « getters » et des « setters » qui portent généralement le même nom. Donnons des id à nos cases :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:gravity="center" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:text="Case 1" android:background="#FF0000" android:textColor="#000000" android:gravity="center" android:id="@+id/case1" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#01FF00" android:text="Case 2" android:textColor="#000000" android:gravity="center" android:id="@+id/case2" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#FF0000" android:text="Case 3" android:textColor="#000000" android:gravity="center" android:id="@+id/case3" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:text="Case 4" android:background="#01FF00" android:textColor="#000000" android:gravity="center" android:id="@+id/case4" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#FF0000" android:text="Case 5" android:textColor="#000000" android:gravity="center" android:id="@+id/case5" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#01FF00" android:text="Case 6" android:textColor="#000000" android:gravity="center" android:id="@+id/case6" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:text="Case 7" android:background="#FF0000" android:textColor="#000000" android:gravity="center" android:id="@+id/case7" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#01FF00" android:text="Case 8" android:textColor="#000000" android:gravity="center" android:id="@+id/case8" /> <TextView android:layout_width="0px" android:layout_height="24px" android:layout_weight="1" android:background="#FF0000" android:text="Case 9" android:textColor="#000000" android:gravity="center" android:id="@+id/case9" /> </LinearLayout> </LinearLayout>
Et voici un florilège des équivalents Java des attributs XML vus précédement :
package net.evidence.TutoDetailGui; import android.app.Activity; import android.graphics.Color; import android.graphics.Typeface; import android.os.Bundle; import android.util.TypedValue; import android.widget.LinearLayout; import android.widget.TextView; public class TutoDetailGui extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView case1 = (TextView) this.findViewById(R.id.case1); TextView case2 = (TextView) this.findViewById(R.id.case2); TextView case3 = (TextView) this.findViewById(R.id.case3); // On peut utiliser la classe Color pour celà // elle définit des constantes de couleur... case1.setTextColor(Color.GREEN); // On peut passer des codes RGB case2.setTextColor(Color.rgb(41, 45, 20)); // pour le background, idem, ici on parse du code hexa case3.setBackgroundColor(Color.parseColor("#ffffff")); // on peut aussi passer une image, mais c'est un usage avancé // que nous verrons une autre fois // On peut également manipuler la police de caractère : // la taille case1.setTextSize(TypedValue.COMPLEX_UNIT_DIP, // les unités sont des constantes de TypesValue 22f); // le style case3.setTypeface(Typeface.SERIF, Typeface.BOLD_ITALIC); // les marges ne sont pas en reste : TextView case4 = (TextView) this.findViewById(R.id.case4); TextView case5 = (TextView) this.findViewById(R.id.case5); // un padding a gauche case4.setPadding(10, 0, 0, 0); /* Pour le margin, c'est un peu plus compliqué. Je vous avais dis que l'on mettait en place des marges, comme sur un conteneur invisible. La vérité est que chaque vue a ses paramètres de disposition externe (donc le "margin") délégués à la vue parente. C'est cette vue parente qui décide ensuite quoi faire de ces paramètres (par exemple déplacer les vues voisines pour tout faire tenir). On doit donc créer des paramètres qui seront utilisables par le parent */ // On récupère les paramètres de layout de la vue et // on le cast dans le type de paramètre spécialement concu pour la vue parent // C'est très important. Ici la vue parente est un LinearLayout, donc // on cast en LinearLayout.LayoutParams LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) case5.getLayoutParams(); // On change les marges dans les params, // ici 20 pixels en bas params.bottomMargin = 20; // Puis on remet les params en place case5.setLayoutParams(params); // Vous comprendrez qu'il est très commode d'utiliser le XML puisque // tout cela est pris en charge automatiquement // et pour finir, la visiblité TextView case8 = (TextView) this.findViewById(R.id.case8); case8.setVisibility(TextView.GONE); // les mots clés sont des constantes de TextView } }
Une œuvre d’art :
C’est fini
Nous avons vu beaucoup plus de pratique que de théorie aujourd’hui, et en fin de compte beaucoup de détails d’implémentation de concepts que vous connaissiez surement déjà. Vous reconnaitrez tout de même que c’est un passage nécessaire, Android est cohérent, mais il faut bien se mettre en tête ses petites habitudes.
Dans le prochain tuto nous allons rentrer dans quelque chose de beaucoup plus spécifique à la programmation embarquée, la téléphonie en général et Android en particulier puisque nous apprendrons à gérer le cycle de vie d’une application Android. Si vous vous êtes toujours demandé ce qui se passait quand votre utilisateur recevait un appel en plein fonctionnement de votre programme, c’est un rendez-vous à ne pas manquer.












23/09/2009 à 16:01
[...] avez essayer d’écrire un application un peu poussée avec ce que vous avez apris lors du tutoriel précédent, vous avez du vous heurter à une méchante erreur lorsque l’application passait en arrière [...]
29/09/2009 à 14:45
Bonjour Kevin,
Un grand merci pour les tutos Android que tu nous fais partager !
Juste une petite coquille (enfin je crois) sur le commentaire de code suivant :
” - Une largeur nulle et un layout_height de 1, une astuce que l’on
connait maintenant bien.”
Je pense qu’il s’agit de layout_weight de 1
Au plaisir de te lire d’autres tutos…
Olivier
29/09/2009 à 16:41
Hello Olivier, encore merci de permettre de corriger ces erreurs.
29/01/2010 à 17:11
Salut Kevin,
Merci pour ces tutos qui permettent de commencer la programmation sous androïd sans se sentir perdu. Ils sont vraiment bien détaillés, et cohérents dans leur structure : continue comme ça
J’attend avec impatience la suite (s’il y en a une de prévue).
Vincent
26/04/2010 à 23:10
Bonjour,
déjà bravo, excellent tutoriel !
vous dites “On trouve des petites perles, comme la validation automatique des champs, ou le formatage. Pour vous donner envie de vous y plonger….” ou est ce ???
merci
29/04/2010 à 8:21
Par exemple http://developer.android.com/reference/android/widget/TextView.html ou on peut avoir “android:capitalize” qui met les lettres en majuscule ou encore “android:digits” qui n’accepte que les entrées numériques.
Android est plein de bonnes choses