Les widgets les plus simples

Ce contenu est obsolète. Il peut contenir des informations intéressantes mais soyez prudent avec celles-ci.

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.

Rendu d'un TextView

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 utilise android:hint. Le problème avec android: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 avec android: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 les inputTypes possibles ici.
  • Enfin, on peut préciser la taille en lignes que doit occuper l'EditText avec android: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.

Rendu d'un EditText

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.

Rendu d'un Button

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.

Rendu d'une CheckBox : cochée à gauche, non cochée à droite

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.

Le bouton radio de droite est sélectionné

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.

Une liste s'affiche afin que vous sélectionniez ce qui vous intéresse

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).

Vous avez accès à beaucoup d'informations sur la classe

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 classe View.
  • 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 ).

Apprenez à utiliser les recherches

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.

Notre programme ressemblera à ça

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'attribut android:inputType auquel on donne la valeur numbers.
  • Les TextView qui affichent « Poids : » et « Taille : » sont centrés, en rouge et en gras.
  • Pour mettre un TextView en gras on utilisera l'attribut android:textStyle en lui attribuant comme valeur bold.
  • Pour mettre un TextView en rouge on utilisera l'attribut android: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'attribut android: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éthode boolean onLongClick(View vue). Cette méthode doit retourner true 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éthode boolean onKey(View vue, int code, KeyEvent event). Cette méthode doit retourner true 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éthode setTextSize(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'un MotionEvent, et pour obtenir la coordonnée en ordonnée (Y) on utilise float 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 et Taille, 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 constante Toast.LENGTH_SHORT pour un message court et Toast.LENGTH_LONG pour un message qui durera plus longtemps. Enfin, il est possible d'afficher le Toast avec la méthode void show ().
  • Pour savoir si une CheckBox est sélectionnée, on utilisera la méthode boolean isChecked() qui renvoie true le cas échéant.
  • Pour récupérer l'identifiant du RadioButton qui est sélectionné dans un RadioGroup il faut utiliser la méthode int getCheckedRadioButtonId ().
  • On peut récupérer le texte d'un EditText à l'aide de la fonction Editable getText (). On peut ensuite vider le contenu de cet objet Editable à l'aide de la fonction void 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ènement onKey. 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 ! :p 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 de TextView et qui permet à l'utilisateur d'écrire du texte.
    • Button qui hérite des propriétés de TextView et qui permet à l'utilisateur de cliquer sur du texte.
    • CheckBox qui hérite des propriétés de Button et qui permet à l'utilisateur de cocher une case.
    • RadioButton qui hérite des propriétés de Button et qui permet à l'utilisateur de choisir parmi plusieurs choix. De plus, RadioGroup est un layout spécifique aux RadioButton.
  • 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.