Maintenant qu'on sait comment est construite une interface graphique, on va voir avec quoi il est possible de la peupler. Ce chapitre traitera uniquement des widgets, c'est-à-dire des vues qui fournissent un contenu et non qui le mettent en forme — ce sont les layouts qui s'occupent de ce genre de choses.
Fournir un contenu, c'est permettre à l'utilisateur d'interagir avec l'application, ou afficher une information qu'il est venu consulter.
Les widgets
Un widget est un élément de base qui permet d'afficher du contenu à l'utilisateur ou lui permet d'interagir avec l'application. Chaque widget possède un nombre important d'attributs XML et de méthodes Java, c'est pourquoi je ne les détaillerai pas, mais vous pourrez trouver toutes les informations dont vous avez besoin sur la documentation officielle d'Android (cela tombe bien, j'en parle à la fin du chapitre ).
TextView
Vous connaissez déjà cette vue, elle vous permet d'afficher une chaîne de caractères que l'utilisateur ne peut modifier. Vous verrez plus tard qu'on peut aussi y insérer des chaînes de caractères formatées, à l'aide de balises HTML, ce qui nous servira à souligner du texte ou à le mettre en gras par exemple.
Exemple en XML
1 2 3 4 5 6 | <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/textView" android:textSize="8sp" android:textColor="#112233" /> |
Vous n'avez pas encore vu comment faire, mais cette syntaxe @string/textView
signifie qu'on utilise une ressource de type string
. Il est aussi possible de passer directement une chaîne de caractères dans android:text
, mais ce n'est pas recommandé. On précise également la taille des caractères avec android:textSize
, puis on précise la couleur du texte avec android:textColor
. Cette notation avec un #
permet de décrire des couleurs à l'aide de nombres hexadécimaux.
Exemple en Java
1 2 3 4 | TextView textView = new TextView(this); textView.setText(R.string.textView); textView.setTextSize(8); textView.setTextColor(0x112233); |
Vous remarquerez que l'équivalent de #112233
est 0x112233
(il suffit de remplacer le #
par 0x
).
Rendu
Le rendu se trouve à la figure suivante.
EditText
Ce composant est utilisé pour permettre à l'utilisateur d'écrire des textes. Il s'agit en fait d'un TextView
éditable.
Il hérite de TextView
, ce qui signifie qu'il peut prendre les mêmes attributs que TextView
en XML et qu'on peut utiliser les mêmes méthodes Java.
Exemple en XML
1 2 3 4 5 6 | <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="@string/editText" android:inputType="textMultiLine" android:lines="5" /> |
- Au lieu d'utiliser
android:text
, on utiliseandroid:hint
. Le problème avecandroid:text
est qu'il remplit l'EditText
avec le texte demandé, alors qu'android:hint
affiche juste un texte d'indication, qui n'est pas pris en compte par l'EditText
en tant que valeur (si vous avez du mal à comprendre la différence, essayez les deux). - On précise quel type de texte contiendra notre
EditText
avecandroid:inputType
. Dans ce cas précis un texte sur plusieurs lignes. Cet attribut change la nature du clavier qui est proposé à l'utilisateur, par exemple si vous indiquez que l'EditText
servira à écrire une adresse e-mail, alors l'arobase sera proposé tout de suite à l'utilisateur sur le clavier. Vous trouverez une liste de tous lesinputTypes
possibles ici. - Enfin, on peut préciser la taille en lignes que doit occuper l'
EditText
avecandroid:lines
.
Exemple en Java
1 2 3 4 | EditText editText = new EditText(this); editText.setHint(R.string.editText); editText.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE); editText.setLines(5); |
Rendu
Le rendu se trouve à la figure suivante.
Button
Un simple bouton, même s'il s'agit en fait d'un TextView
cliquable.
Il hérite de TextView
, ce qui signifie qu'il peut prendre les mêmes attributs que TextView
en XML et qu'on peut utiliser les mêmes méthodes Java.
Exemple en XML
1 2 3 4 | <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/button" /> |
Exemple en Java
1 2 | Button button = new Button(this); editText.setText(R.string.button); |
Rendu
Le rendu se trouve à la figure suivante.
CheckBox
Une case qui peut être dans deux états : cochée ou pas.
Elle hérite de Button
, ce qui signifie qu'elle peut prendre les mêmes attributs que Button
en XML et qu'on peut utiliser les mêmes méthodes Java.
Exemple en XML
1 2 3 4 5 | <CheckBox android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/checkBox" android:checked="true" /> |
android:checked="true"
signifie que la case est cochée par défaut.
Exemple en Java
1 2 3 4 5 | CheckBox checkBox = new CheckBox(this); checkBox.setText(R.string.checkBox); checkBox.setChecked(true) if(checkBox.isChecked()) // Faire quelque chose si le bouton est coché |
Rendu
Le rendu se trouve à la figure suivante.
RadioButton et RadioGroup
Même principe que la CheckBox
, à la différence que l'utilisateur ne peut cocher qu'une seule case. Il est plutôt recommandé de les regrouper dans un RadioGroup
.
RadioButton
hérite de Button
, ce qui signifie qu'il peut prendre les mêmes attributs que Button
en XML et qu'on peut utiliser les mêmes méthodes Java.
Un RadioGroup
est en fait un layout, mais il n'est utilisé qu'avec des RadioButton
, c'est pourquoi on le voit maintenant. Son but est de faire en sorte qu'il puisse n'y avoir qu'un seul RadioButton
sélectionné dans tout le groupe.
Exemple en XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <RadioGroup android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" > <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" /> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" /> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RadioGroup> |
Exemple en Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | RadioGroup radioGroup = new RadioGroup(this); RadioButton radioButton1 = new RadioButton(this); RadioButton radioButton2 = new RadioButton(this); RadioButton radioButton3 = new RadioButton(this); // On ajoute les boutons au RadioGroup radioGroup.addView(radioButton1, 0); radioGroup.addView(radioButton2, 1); radioGroup.addView(radioButton3, 2); // On sélectionne le premier bouton radioGroup.check(0); // On récupère l'identifiant du bouton qui est coché int id = radioGroup.getCheckedRadioButtonId(); |
Rendu
Le rendu se trouve à la figure suivante.
Utiliser la documentation pour trouver une information
Je fais un petit aparté afin de vous montrer comment utiliser la documentation pour trouver les informations que vous recherchez, parce que tout le monde en a besoin. Que ce soit vous, moi, des développeurs Android professionnels ou n'importe qui chez Google, nous avons tous besoin de la documentation. Il n'est pas possible de tout savoir, et surtout, je ne peux pas tout vous dire ! La documentation est là pour ça, et vous ne pourrez pas devenir de bons développeurs Android — voire de bons développeurs tout court — si vous ne savez pas chercher des informations par vous-mêmes.
Je vais procéder à l'aide d'un exemple. Je me demande comment faire pour changer la couleur du texte de ma TextView
. Pour cela, je me dirige vers la documentation officielle : http://developer.android.com/.
Vous voyez un champ de recherche en haut à gauche. Je vais insérer le nom de la classe que je recherche : TextView
. Vous voyez une liste qui s'affiche et qui permet de sélectionner la classe qui pourrait éventuellement vous intéresser, comme à la figure suivante.
J'ai bien sûr cliqué sur Android.widget.TextView
puisque c'est celle qui m'intéresse. Nous arrivons alors sur une page qui vous décrit toutes les informations possibles et imaginables sur la classe TextView
(voir figure suivante).
On voit par exemple qu'il s'agit d'une classe, publique, qui dérive de View
et implémente une interface.
La partie suivante représente un arbre qui résume la hiérarchie de ses superclasses.
Ensuite, on peut voir les classes qui dérivent directement de cette classe (Known Direct Subclasses
) et les classes qui en dérivent indirectement, c'est-à-dire qu'un des ancêtres de ces classes dérive de View
(Known Indirect Subclasses
).
Enfin, on trouve en haut à droite un résumé des différentes sections qui se trouvent dans le document (je vais aussi parler de certaines sections qui ne se trouvent pas dans cette classe mais que vous pourrez rencontrer dans d'autres classes) :
Nested Classes
est la section qui regroupe toutes les classes internes. Vous pouvez cliquer sur une classe interne pour ouvrir une page similaire à celle de la classeView
.XML Attrs
est la section qui regroupe tous les attributs que peut prendre un objet de ce type en XML. Allez voir le tableau, vous verrez que pour chaque attribut XML on trouve associé un équivalent Java.Constants
est la section qui regroupe toutes les constantes dans cette classe.Fields
est la section qui regroupe toutes les structures de données constantes dans cette classe (listes et tableaux).Ctors
est la section qui regroupe tous les constructeurs de cette classe.Methods
est la section qui regroupe toutes les méthodes de cette classe.Protected Methods
est la section qui regroupe toutes les méthodes protégées (accessibles uniquement par cette classe ou les enfants de cette classe).
Vous rencontrerez plusieurs fois l'adjectif Inherited
, il signifie que cet attribut ou classe a hérité d'une de ses superclasses.
Ainsi, si je cherche un attribut XML, je peux cliquer sur XML Attrs
et parcourir la liste des attributs pour découvrir celui qui m'intéresse (voir figure suivante), ou alors je peux effectuer une recherche sur la page (le raccourci standard pour cela est Ctrl + F ).
J'ai trouvé ! Il s'agit de android:textColor
! Je peux ensuite cliquer dessus pour obtenir plus d'informations et ainsi l'utiliser correctement dans mon code.
Calcul de l'IMC - Partie 1
Énoncé
On va commencer un mini-TP (TP signifie « travaux pratiques » ; ce sont des exercices pour vous entraîner à programmer). Vous voyez ce qu'est l'IMC ? C'est un nombre qui se calcule à partir de la taille et de la masse corporelle d'un individu, afin qu'il puisse déterminer s'il est trop svelte ou trop corpulent.
Ayant travaillé dans le milieu médical, je peux vous affirmer qu'il ne faut pas faire trop confiance à ce nombre (c'est pourquoi je ne propose pas d'interprétation du résultat pour ce mini-TP). S'il vous indique que vous êtes en surpoids, ne complexez pas ! Sachez que tous les bodybuilders du monde se trouvent obèses d'après ce nombre.
Pour l'instant, on va se contenter de faire l'interface graphique. Elle ressemblera à la figure suivante.
Instructions
Avant de commencer, voici quelques instructions :
- On utilisera uniquement le XML.
- Pour mettre plusieurs composants dans un layout, on se contentera de mettre les composants entre les balises de ce layout.
- On n'utilisera qu'un seul layout.
- Les deux
EditText
permettront de n'insérer que des nombres. Pour cela, on utilise l'attributandroid:inputType
auquel on donne la valeurnumbers
. - Les
TextView
qui affichent « Poids : » et « Taille : » sont centrés, en rouge et en gras. - Pour mettre un
TextView
en gras on utilisera l'attributandroid:textStyle
en lui attribuant comme valeurbold
. - Pour mettre un
TextView
en rouge on utilisera l'attributandroid:textColor
en lui attribuant comme valeur#FF0000
. Vous pourrez trouver d'autres valeurs pour indiquer une couleur à cet endroit. - Afin de centrer du texte dans un
TextView
, on utilise l'attributandroid:gravity="center"
.
Voici le layout de base :
1 2 3 4 5 6 7 8 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <!-- mettre les composants ici --> </LinearLayout> |
Solution
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | <?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"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Poids : " android:textStyle="bold" android:textColor="#FF0000" android:gravity="center" /> <EditText android:id="@+id/poids" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Poids" android:inputType="numberDecimal" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Taille : " android:textStyle="bold" android:textColor="#FF0000" android:gravity="center" /> <EditText android:id="@+id/taille" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Taille" android:inputType="numberDecimal" /> <RadioGroup android:id="@+id/group" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checkedButton="@+id/radio2" android:orientation="horizontal" > <RadioButton android:id="@+id/radio1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Mètre" /> <RadioButton android:id="@+id/radio2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Centimètre" /> </RadioGroup> <CheckBox android:id="@+id/mega" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Mega fonction !" /> <Button android:id="@+id/calcul" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Calculer l'IMC" /> <Button android:id="@+id/raz" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="RAZ" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Résultat:" /> <TextView android:id="@+id/result" android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="Vous devez cliquer sur le bouton « Calculer l'IMC » pour obtenir un résultat." /> </LinearLayout> |
Et voilà, notre interface graphique est prête ! Bon pour le moment, elle ne fait rien : si vous appuyez sur les différents élements, rien ne se passe. Mais nous allons y remédier d'ici peu, ne vous inquiétez pas.
Gérer les évènements sur les widgets
On va voir ici comment gérer les interactions entre l'interface graphique et l'utilisateur.
Les listeners
Il existe plusieurs façons d'interagir avec une interface graphique. Par exemple cliquer sur un bouton, entrer un texte, sélectionner une portion de texte, etc. Ces interactions s'appellent des évènements. Pour pouvoir réagir à l'apparition d'un évènement, il faut utiliser un objet qui va détecter l'évènement et afin de vous permettre le traiter. Ce type d'objet s'appelle un listener. Un listener est une interface qui vous oblige à redéfinir des méthodes de callback
et chaque méthode sera appelée au moment où se produira l'évènement associé.
Par exemple, pour intercepter l'évènement clic
sur un Button
, on appliquera l'interface View.OnClickListener
sur ce bouton. Cette interface contient la méthode de callback
void onClick(View vue)
— le paramètre de type View
étant la vue sur laquelle le clic a été effectué, qui sera appelée à chaque clic et qu'il faudra implémenter pour déterminer que faire en cas de clic. Par exemple pour gérer d'autres évènements, on utilisera d'autres méthodes (liste non exhaustive) :
View.OnLongClickListener
pour les clics qui durent longtemps, avec la méthodeboolean onLongClick(View vue)
. Cette méthode doit retournertrue
une fois que l'action associée a été effectuée.View.OnKeyListener
pour gérer l'appui sur une touche. On y associe la méthodeboolean onKey(View vue, int code, KeyEvent event)
. Cette méthode doit retournertrue
une fois que l'action associée a été effectuée.
J'ai bien dit qu'il fallait utiliser View.OnClickListener
, de la classe View
! Il existe d'autres types de OnClickListener
et Eclipse pourrait bien vous proposer d'importer n'importe quel package qui n'a rien à voir, auquel cas votre application ne fonctionnerait pas. Le package à utiliser pour OnClickListener
est android.view.View.OnClickListener
.
Que veux-tu dire par « Cette méthode doit retourner true
une fois que l'action associée a été effectuée » ?
Petite subtilité pas forcément simple à comprendre. Il faut indiquer à Android quand vous souhaitez que l'évènement soit considéré comme traité, achevé. En effet, il est possible qu'un évènement continue à agir dans le temps. Un exemple simple est celui du toucher. Le toucher correspond au fait de toucher l'écran, pendant que vous touchez l'écran et avant même de lever le doigt pour le détacher de l'écran. Si vous levez ce doigt, le toucher s'arrête et un nouvel évènement est lancé : le clic, mais concentrons-nous sur le toucher. Quand vous touchez l'écran, un évènement de type onTouch
est déclenché. Si vous retournez true
au terme de cette méthode, ça veut dire que cet évènement toucher a été géré, et donc si l'utilisateur continue à bouger son doigt sur l'écran, Android considérera les mouvements sont de nouveaux évènements toucher et à nouveaux la méthode de callback onTouch
sera appelée pour chaque mouvement. En revanche, si vous retournez false
, l'évènement ne sera pas considéré comme terminé et si l'utilisateur continue à bouger son doigt sur l'écran, Android ne considérera pas que ce sont de nouveaux évènements et la méthode onTouch
ne sera plus appelée. Il faut donc réfléchir en fonction de la situation.
Enfin pour associer un listener à une vue, on utilisera une méthode du type setOn[Evenement]Listener(On[Evenenement]Listener listener)
avec Evenement
l'évènement concerné, par exemple pour détecter les clics sur un bouton on fera :
1 2 | Bouton b = new Button(getContext()); b.setOnClickListener(notre_listener); |
Par héritage
On va faire implémenter un listener à notre classe, ce qui veut dire que l'activité interceptera d'elle-même les évènements. N'oubliez pas que lorsqu'on implémente une interface, il faut nécessairement implémenter toutes les méthodes de cette interface. Enfin, il n'est bien entendu pas indispensable que vous gériez tous les évènements d'une interface, vous pouvez laisser une méthode vide si vous ne voulez pas vous préoccuper de ce style d'évènements.
Un exemple d'implémentation :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | import android.view.View.OnTouchListener; import android.view.View.OnClickListener; import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.widget.Button; // Notre activité détectera les touchers et les clics sur les vues qui se sont inscrites public class Main extends Activity implements View.OnTouchListener, View.OnClickListener { private Button b = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); b = (Button) findViewById(R.id.boutton); b.setOnTouchListener(this); b.setOnClickListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { /* Réagir au toucher */ return true; } @Override public void onClick(View v) { /* Réagir au clic */ } } |
Cependant, un problème se pose. À chaque fois qu'on appuiera sur un bouton, quel qu'il soit, on rentrera dans la même méthode, et on exécutera donc le même code… C'est pas très pratique, si nous avons un bouton pour rafraîchir un onglet dans une application de navigateur internet et un autre pour quitter un onglet, on aimerait bien que cliquer sur le bouton de rafraîchissement ne quitte pas l'onglet et vice-versa. Heureusement, la vue passée dans la méthode onClick(View)
permet de différencier les boutons. En effet, il est possible de récupérer l'identifiant de la vue (vous savez, l'identifiant défini en XML et qu'on retrouve dans le fichier R
!) sur laquelle le clic a été effectué. Ainsi, nous pouvons réagir différemment en fonction de cet identifiant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public void onClick(View v) { // On récupère l'identifiant de la vue, et en fonction de cet identifiant… switch(v.getId()) { // Si l'identifiant de la vue est celui du premier bouton case R.id.bouton1: /* Agir pour bouton 1 */ break; // Si l'identifiant de la vue est celui du deuxième bouton case R.id.bouton2: /* Agir pour bouton 2 */ break; /* etc. */ } } |
Par une classe anonyme
L'inconvénient principal de la technique précédente est qu'elle peut très vite allonger les méthodes des listeners, ce qui fait qu'on s'y perd un peu s'il y a beaucoup d'éléments à gérer. C'est pourquoi il est préférable de passer par une classe anonyme dès qu'on a un nombre élevé d'éléments qui réagissent au même évènement.
Pour rappel, une classe anonyme est une classe interne qui dérive d'une superclasse ou implémente une interface, et dont on ne précise pas le nom. Par exemple pour créer une classe anonyme qui implémente View.OnClickListener()
je peux faire :
1 2 3 4 5 6 7 | widget.setTouchListener(new View.OnTouchListener() { /** * Contenu de ma classe * Comme on implémente une interface, il y aura des méthodes à implémenter, dans ce cas-ci * « public boolean onTouch(View v, MotionEvent event) » */ }); // Et on n'oublie pas le point-virgule à la fin ! C'est une instruction comme les autres ! |
Voici un exemple de code :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class AnonymousExampleActivity extends Activity { // On cherchera à détecter les touchers et les clics sur ce bouton private Button touchAndClick = null; // On voudra détecter uniquement les clics sur ce bouton private Button clickOnly = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); touchAndClick = (Button)findViewById(R.id.touchAndClick); clickOnly = (Button)findViewById(R.id.clickOnly); touchAndClick.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { // Réagir à un long clic return false; } }); touchAndClick.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Réagir au clic } }); clickOnly.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Réagir au clic } }); } } |
Par un attribut
C'est un dérivé de la méthode précédente : en fait on implémente des classes anonymes dans des attributs de façon à pouvoir les utiliser dans plusieurs éléments graphiques différents qui auront la même réaction pour le même évènement. C'est la méthode que je privilégie dès que j'ai, par exemple, plusieurs boutons qui utilisent le même code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.widget.Button; public class Main extends Activity { private OnClickListener clickListenerBoutons = new View.OnClickListener() { @Override public void onClick(View v) { /* Réagir au clic pour les boutons 1 et 2*/ } }; private OnTouchListener touchListenerBouton1 = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { /* Réagir au toucher pour le bouton 1*/ return onTouch(v, event); } }; private OnTouchListener touchListenerBouton3 = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { /* Réagir au toucher pour le bouton 3*/ return super.onTouch(v, event); } }; Button b1 = null; Button b2 = null; Button b3 = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); b1 = (Button) findViewById(R.id.bouton1); b2 = (Button) findViewById(R.id.bouton2); b3 = (Button) findViewById(R.id.bouton3); b1.setOnTouchListener(touchListenerBouton1); b1.setOnClickListener(clickListenerBoutons); b2.setOnClickListener(clickListenerBoutons); b3.setOnTouchListener(touchListenerBouton3); } } |
Application
Énoncé
On va s'amuser un peu : nous allons créer un bouton qui prend tout l'écran et faire en sorte que le texte à l'intérieur du bouton grossisse quand on s'éloigne du centre du bouton, et rétrécisse quand on s'en rapproche.
Instructions
- On va se préoccuper non pas du clic mais du toucher, c'est-à-dire l'évènement qui débute dès qu'on touche le bouton jusqu'au moment où on le relâche (contrairement au clic qui ne se déclenche qu'au moment où on relâche la pression).
- La taille du
TextView
sera fixée avec la méthodesetTextSize(Math.abs(coordonnee_x - largeur_du_bouton / 2) + Math.abs(coordonnee_y - hauteur_du_bouton / 2))
. - Pour obtenir la coordonnée en abscisse (X) on utilise
float getX()
d'unMotionEvent
, et pour obtenir la coordonnée en ordonnée (Y) on utilisefloat getY()
.
Je vous donne le code pour faire en sorte d'avoir le bouton bien au milieu du layout :
1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:id="@+id/bouton" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_gravity="center" android:text="@string/hello" /> </LinearLayout> |
Maintenant, c'est à vous de jouer !
Solution
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // On fait implémenter OnTouchListener par notre activité public class Main extends Activity implements View.OnTouchListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // On récupère le bouton par son identifiant Button b = (Button) findViewById(R.id.bouton); // Puis on lui indique que cette classe sera son listener pour l'évènement Touch b.setOnTouchListener(this); } // Fonction qui sera lancée à chaque fois qu'un toucher est détecté sur le bouton rattaché @Override public boolean onTouch(View view, MotionEvent event) { // Comme l'évènement nous donne la vue concernée par le toucher, on le récupère et on le caste en Button Button bouton = (Button)view; // On récupère la largeur du bouton int largeur = bouton.getWidth(); // On récupère la hauteur du bouton int hauteur = bouton.getHeight(); // On récupère la coordonnée sur l'abscisse (X) de l'évènement float x = event.getX(); // On récupère la coordonnée sur l'ordonnée (Y) de l'évènement float y = event.getY(); // Puis on change la taille du texte selon la formule indiquée dans l'énoncé bouton.setTextSize(Math.abs(x - largeur / 2) + Math.abs(y - hauteur / 2)); // Le toucher est fini, on veut continuer à détecter les touchers d'après return true; } } |
On a procédé par héritage puisqu'on a qu'un seul bouton sur lequel agir.
Calcul de l'IMC - Partie 2
Énoncé
Il est temps maintenant de relier tous les boutons de notre application pour pouvoir effectuer tous les calculs, en respectant les quelques règles suivantes :
- La
CheckBox
de megafonction permet de changer le résultat du calcul en un message élogieux pour l'utilisateur. - La formule pour calculer l'IMC est $\frac{\mbox{poids (en kilogrammes)}}{{\mbox{taille (en metres)}^2} }$.
- Le bouton
RAZ
remet à zéro tous les champs (sans oublier le texte pour le résultat). - Les éléments dans le
RadioGroup
permettent à l'utilisateur de préciser en quelle unité il a indiqué sa taille. Pour obtenir la taille en mètres depuis la taille en centimètres il suffit de diviser par 100 : $\frac{171 \mbox{ centimetres}}{100} = 1.71 \mbox{ metres}$. - Dès qu'on change les valeurs dans les champs
Poids
etTaille
, on remet le texte du résultat par défaut puisque la valeur calculée n'est plus valable pour les nouvelles valeurs. - On enverra un message d'erreur si l'utilisateur essaie de faire le calcul avec une taille égale à zéro grâce à un
Toast
.
Un Toast
est un widget un peu particulier qui permet d'afficher un message à n'importe quel moment sans avoir à créer de vue. Il est destiné à informer l'utilisateur sans le déranger outre mesure ; ainsi l’utilisateur peut continuer à utiliser l'application comme si le Toast
n'était pas présent.
Consignes
- Voici la syntaxe pour construire un
Toast
:static Toast makeText(Context context, CharSequence texte, int duration)
. La durée peut être indiquée à l'aide de la constanteToast.LENGTH_SHORT
pour un message court etToast.LENGTH_LONG
pour un message qui durera plus longtemps. Enfin, il est possible d'afficher leToast
avec la méthodevoid show ()
. - Pour savoir si une
CheckBox
est sélectionnée, on utilisera la méthodeboolean isChecked()
qui renvoietrue
le cas échéant. - Pour récupérer l'identifiant du
RadioButton
qui est sélectionné dans unRadioGroup
il faut utiliser la méthodeint getCheckedRadioButtonId ()
. - On peut récupérer le texte d'un
EditText
à l'aide de la fonctionEditable getText ()
. On peut ensuite vider le contenu de cet objetEditable
à l'aide de la fonctionvoid clear()
. Plus d'informations sur Editable. - Parce que c'est déjà bien assez compliqué comme cela, on se simplifie la vie et on ne prend pas en compte les cas extrêmes (taille ou poids < 0 ou
null
par exemple). - Pour détecter le moment où l'utilisateur écrit dans un
EditText
, on peut utiliser l'évènementonKey
. Problème, cette technique ne fonctionne que sur les claviers virtuels, alors si l'utilisateur a un clavier physique, ce qu'il écrit n'enclenchera pas la méthode de callback… Je vais quand même vous présenter cette solution, mais pour faire ce genre de surveillance, on préférera utiliser un TextWatcher. C'est comme un listener, mais ça n'en porte pas le nom !
Ma solution
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | import android.app.Activity; import android.os.Bundle; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnKeyListener; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.RadioGroup; import android.widget.TextView; import android.widget.Toast; public class IMCActivity extends Activity { // La chaîne de caractères par défaut private final String defaut = "Vous devez cliquer sur le bouton « Calculer l'IMC » pour obtenir un résultat."; // La chaîne de caractères de la megafonction private final String megaString = "Vous faites un poids parfait ! Wahou ! Trop fort ! On dirait Brad Pitt (si vous êtes un homme)/Angelina Jolie (si vous êtes une femme)/Willy (si vous êtes un orque) !"; Button envoyer = null; Button raz = null; EditText poids = null; EditText taille = null; RadioGroup group = null; TextView result = null; CheckBox mega = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // On récupère toutes les vues dont on a besoin envoyer = (Button)findViewById(R.id.calcul); raz = (Button)findViewById(R.id.raz); taille = (EditText)findViewById(R.id.taille); poids = (EditText)findViewById(R.id.poids); mega = (CheckBox)findViewById(R.id.mega); group = (RadioGroup)findViewById(R.id.group); result = (TextView)findViewById(R.id.result); // On attribue un listener adapté aux vues qui en ont besoin envoyer.setOnClickListener(envoyerListener); raz.setOnClickListener(razListener); taille.addTextChangedListener(textWatcher); poids.addTextChangedListener(textWatcher); // Solution avec des onKey //taille.setOnKeyListener(modificationListener); //poids.setOnKeyListener(modificationListener); mega.setOnClickListener(checkedListener); } /* // Se lance à chaque fois qu'on appuie sur une touche en étant sur un EditText private OnKeyListener modificationListener = new OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { // On remet le texte à sa valeur par défaut pour ne pas avoir de résultat incohérent result.setText(defaut); return false; } };*/ private TextWatcher textWatcher = new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { result.setText(defaut); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { } }; // Uniquement pour le bouton "envoyer" private OnClickListener envoyerListener = new OnClickListener() { @Override public void onClick(View v) { if(!mega.isChecked()) { // Si la megafonction n'est pas activée // On récupère la taille String t = taille.getText().toString(); // On récupère le poids String p = poids.getText().toString(); float tValue = Float.valueOf(t); // Puis on vérifie que la taille est cohérente if(tValue == 0) Toast.makeText(IMCActivity.this, "Hého, tu es un Minipouce ou quoi ?", Toast.LENGTH_SHORT).show(); else { float pValue = Float.valueOf(p); // Si l'utilisateur a indiqué que la taille était en centimètres // On vérifie que la Checkbox sélectionnée est la deuxième à l'aide de son identifiant if(group.getCheckedRadioButtonId() == R.id.radio2) tValue = tValue / 100; tValue = (float)Math.pow(tValue, 2); float imc = pValue / tValue; result.setText("Votre IMC est " + String.valueOf(imc)); } } else result.setText(megaString); } }; // Listener du bouton de remise à zéro private OnClickListener razListener = new OnClickListener() { @Override public void onClick(View v) { poids.getText().clear(); taille.getText().clear(); result.setText(defaut); } }; // Listener du bouton de la megafonction. private OnClickListener checkedListener = new OnClickListener() { @Override public void onClick(View v) { // On remet le texte par défaut si c'était le texte de la megafonction qui était écrit if(!((CheckBox)v).isChecked() && result.getText().equals(megaString)) result.setText(defaut); } }; } |
Pourquoi on retourne false
dans le onKeyListener
? Il se serait passer quoi si j'avais retourné true
?
Curieux va ! En fait l'évènement onKey
sera lancé avant que l'écriture soit prise en compte par le système. Ainsi, si vous renvoyez true
, Android considérera que l'évènement a été géré, et que vous avez vous-même écrit la lettre qui a été pressée. Si vous renvoyez false
, alors le système comprendra que vous n'avez pas écrit la lettre et il le fera de lui-même. Alors vous auriez très bien pu renvoyer true
, mais il faudrait écrire nous-même la lettre et c'est du travail en plus pour rien !
Vous avez vu ce qu'on a fait ? Sans toucher à l'interface graphique, on a pu effectuer toutes les modifications nécessaires au bon fonctionnement de notre application. C'est l'intérêt de définir l'interface dans un fichier XML et le côté interactif en Java : vous pouvez modifier l'un sans toucher l'autre !
- Il existe un grand nombre de widgets différents. Parmi les plus utilisés, nous avons :
TextView
destiné à afficher du texte sur l'écran.EditText
qui hérite des propriétés deTextView
et qui permet à l'utilisateur d'écrire du texte.Button
qui hérite des propriétés deTextView
et qui permet à l'utilisateur de cliquer sur du texte.CheckBox
qui hérite des propriétés deButton
et qui permet à l'utilisateur de cocher une case.RadioButton
qui hérite des propriétés deButton
et qui permet à l'utilisateur de choisir parmi plusieurs choix. De plus,RadioGroup
est un layout spécifique auxRadioButton
.
- N'oubliez pas que la documentation est l'unique endroit où vous pourrez trouver toutes les possibilités offertes pour chacun des widgets disponibles.
- Pour écouter les différents évènements qui pourraient se produire sur vos vues, on utilise des listeners qui enclenchent des méthodes de callback que vous pouvez redéfinir pour gérer leur implémentation.
- Android permet de lier des listeners à des vues de trois manières différentes :
- Par héritage en implémentant l'interface au niveau de la classe, auquel cas il faudra réécrire les méthodes de callback directement dans votre classe.
- Par classe anonyme en donnant directement une implémentation unique à la vue.
- Par un attribut, si vous voulez réutiliser votre listener sur plusieurs vues.