Gestion des menus de l’application

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

À une époque pas si lointaine, tous les terminaux sous Android possédaient un bouton physique pour afficher le menu. Cependant, cette pratique est devenue un peu plus rare depuis que les constructeurs essaient au maximum de dématérialiser les boutons. Mais depuis Android 2.3, il existe un bouton directement dans l'interface du système d'exploitation, qui permet d'ouvrir un menu. En sorte, on peut dire que tous les utilisateurs sont touchés par la présence d'un menu.

En tout cas, un menu est un endroit privilégié pour placer certaines fonctions tout en économisant notre précieux espace dans la fenêtre. Vous pouvez par exemple faire en sorte que ce menu ouvre la page des options, ou au contraire vous ramène à la page d'accueil.

Il existe deux sortes de menu dans Android :

  • Le menu d'options, celui qu'on peut ouvrir avec le bouton Menu sur le téléphone. Si le téléphone est dépourvu de cette touche, Android fournit un bouton dans son interface graphique pour y accéder.
  • Les menus contextuels, vous savez, ces menus qui s'ouvrent à l'aide d'un clic droit sous Windows et Linux ? Eh bien, dans Android ils se déroulent dès lors qu'on effectue un long clic sur un élément de l'interface graphique.

Et ces deux menus peuvent bien entendu contenir des sous-menus, qui peuvent contenir des sous-menus, etc. Encore une fois, on va devoir manipuler des fichiers XML mais, franchement, vous êtes devenus des experts maintenant, non ? :p

Menu d'options

Créer un menu

Chaque activité est capable d'avoir son menu propre. On peut définir un menu de manière programmatique en Java, mais la meilleure façon de procéder est en XML. Tout d'abord, la racine de ce menu est de type <menu> (vous arriverez à retenir ? :euh: ), et on ne peut pas vraiment le personnaliser avec des attributs, ce qui donne la majorité du temps :

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <!-- Code -->
</menu>

Ce menu doit être peuplé avec des éléments, et c'est sur ces éléments que cliquera l'utilisateur pour activer ou désactiver une fonctionnalité. Ces éléments s'appellent en XML des <item> et peuvent être personnalisés à l'aide de plusieurs attributs :

  • android:id, que vous connaissez déjà. Il permet d'identifier de manière unique un <item>. Autant d'habitude cet attribut est facultatif, autant cette fois il est vraiment indispensable, vous verrez pourquoi dans cinq minutes.
  • android:icon, pour agrémenter votre <item> d'une icône.
  • android:title, qui sera son texte dans le menu.
  • Enfin, on peut désactiver par défaut un <item> avec l'attribut android:enabled="false".

Vous pouvez récupérer gratuitement et légalement les icônes fournies avec Android. Par rapport à l'endroit où se situe le SDK, vous les trouverez dans .\platforms\android-x\data\res\ avec x le niveau de l'API que vous utilisez. Il est plutôt recommandé d'importer ces images en tant que drawables dans notre application, plutôt que de faire référence à l'icône utilisée actuellement par Android, car elle pourrait ne pas exister ou être différente en fonction de la version d'Android qu'exploite l'utilisateur. Si vous souhaitez faire vos propres icônes, sachez que la taille maximale recommandée est de 72 pixels pour les hautes résolutions, 48 pixels pour les moyennes résolutions et 38 pixels pour les basses résolutions.

Le problème est que l'espace consacré à un menu est assez réduit, comme toujours sur un périphérique portable, remarquez. Afin de gagner un peu de place, il est possible d'avoir un <item> qui ouvre un sous-menu, et ce sous-menu sera à traiter comme tout autre menu. On lui mettra donc des items aussi. En d'autres termes, la syntaxe est celle-ci :

1
2
3
4
5
6
7
<item>
  <menu>
    <item />
    <!-- d'autres items-->
    <item />
  </menu>
</item>

Le sous-menu s'ouvrira dans une nouvelle fenêtre, et le titre de cette fenêtre se trouve dans l'attribut android:title. Si vous souhaitez mettre un titre plutôt long dans cette fenêtre et conserver un nom court dans le menu, utilisez l'attribut android:titleCondensed, qui permet d'indiquer un titre à utiliser si le titre dans android:title est trop long. Ces <item> qui se trouvent dans un sous-menu peuvent être modulés avec d'autres attributs, comme android:checkable auquel vous pouvez mettre true si vous souhaitez que l'élément puisse être coché, comme une CheckBox. De plus, si vous souhaitez qu'il soit coché par défaut, vous pouvez mettre android:checked à true. Je réalise que ce n'est pas très clair, aussi vous proposé-je de regarder les deux figures suivantes : la première utilise android:titleCondensed="Item 1", la deuxième android:title="Item 1 mais avec un titre plus long quand même".

Le titre est condensé Le titre est plus long

Enfin, il peut arriver que plusieurs éléments se ressemblent beaucoup ou fonctionnent ensemble, c'est pourquoi il est possible de les regrouper avec <group>. Si on veut que tous les éléments du groupe soient des CheckBox, on peut mettre au groupe l'attribut android:checkableBehavior="all", ou, si on veut qu'ils soient tous des RadioButton, on peut mettre l'attribut android:checkableBehavior="single".

Voici un exemple de menu qu'il vous est possible de créer avec cette méthode :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
  <item android:id="@+id/item1" android:title="Item 1"></item>
  <item android:id="@+id/item2" android:titleCondensed="Item 2" android:title="Item 2 mais avec un nom assez long quand même">
    <menu>
      <item android:id="@+id/item3" android:title="Item 2.1" android:checkable="true"/>
      <item android:id="@+id/item4" android:title="Item 2.2"/>
    </menu>
  </item>
  <item android:id="@+id/item5" android:title="Item 3" android:checkable="true"/>
  <item android:id="@+id/item6" android:title="Item 4">
    <group android:id="@+id/group1" android:checkableBehavior="all">
      <item android:id="@+id/item7" android:title="Item 4.1"></item>
      <item android:id="@+id/item8" android:title="Item 4.2"></item>
    </group>
  </item>
  <group android:id="@+id/group2" android:enabled="false">
    <item android:id="@+id/item9" android:title="Item 5.1"></item>
    <item android:id="@+id/item10" android:title="Item 5.2"></item>
  </group>
</menu>

Comme pour un layout, il va falloir dire à Android qu'il doit parcourir le fichier XML pour construire le menu. Pour cela, c'est très simple, on va surcharger la méthode boolean onCreateOptionsMenu (Menu menu) d'une activité. Cette méthode est lancée au moment de la première pression du bouton qui fait émerger le menu. Cependant, comme avec les boîtes de dialogue, si vous souhaitez que le menu évolue à chaque pression du bouton, alors il vous faudra surcharger la méthode boolean onPrepareOptionsMenu (Menu menu).

Pour parcourir le XML, on va l'inflater, eh oui ! encore une fois ! Encore un petit rappel de ce qu'est inflater ? To inflate, c'est désérialiser en français, et dans notre cas c'est transformer un objet qui n'est décrit qu'en XML en véritable objet qu'on peut manipuler. Voici le code type dès qu'on a constitué un menu en XML :

1
2
3
4
5
6
7
8
@Override
public boolean onCreateOptionsMenu(Menu menu) {
  super.onCreateOptionsMenu(menu);
  MenuInflater inflater = getMenuInflater();
  //R.menu.menu est l'id de notre menu
  inflater.inflate(R.menu.menu, menu);
  return true;
}

Si vous testez ce code, vous remarquerez tout d'abord que, contrairement au premier exemple, il n'y a pas assez de place pour contenir tous les items, c'est pourquoi le 6e item se transforme en un bouton pour afficher les éléments cachés, comme à la figure suivante.

Un bouton permet d'accéder aux autres items

Ensuite vous remarquerez que les items 4.1 et 4.2 sont décrits comme Checkable, mais ne possèdent pas de case à cocher. C'est parce que les seuls <item> que l'on puisse cocher sont ceux qui se trouvent dans un sous-menu.

Les <item> 5.1 et 5.2 sont désactivés par défaut, mais vous pouvez les réactiver de manière programmatique à l'aide de la fonction MenuItem setEnabled (boolean activer) (le MenuItem retourné est celui sur lequel l'opération a été effectuée, de façon à pouvoir cumuler les setters).

Un setter est une méthode qui permet de modifier un des attributs d'un objet. Un getter est une méthode qui permet, elle, de récupérer un attribut d'un objet.

Vous pouvez aussi si vous le désirez construire un menu de manière programmatique avec la méthode suivante qui s'utilise sur un Menu :

1
MenuItem add (int groupId, int objectId, int ordre, CharSequence titre)

Où :

  • groupId permet d'indiquer si l'objet appartient à un groupe. Si ce n'est pas le cas, vous pouvez mettre Menu.NONE.
  • objectId est l'identifiant unique de l'objet, vous pouvez aussi mettre Menu.NONE, mais je ne le recommande pas.
  • ordre permet de définir l'ordre dans lequel vous souhaitez le voir apparaître dans le menu. Par défaut, l'ordre respecté est celui du fichier XML ou de l'ajout avec la méthode add, mais avec cette méthode vous pouvez bousculer l'ordre établi pour indiquer celui que vous préférez. Encore une fois, vous pouvez mettre Menu.NONE.
  • titre est le titre de l'item.

De manière identique et avec les mêmes paramètres, vous pouvez construire un sous-menu avec la méthode suivante :

1
MenuItem addSubMenu (int groupId, int objectId, int ordre, CharSequence titre)

Et c'est indispensable de passer le menu à la superclasse comme on le fait ?

La réponse courte est non, la réponse longue est non, mais faites-le quand même. En passant le menu à l'implémentation par défaut, Android va peupler le menu avec des items systèmes standards. Alors, en tant que débutants, vous ne verrez pas la différence, mais si vous devenez des utilisateurs avancés, un oubli pourrait bien vous encombrer.

Réagir aux clics

Vous vous rappelez quand je vous avais dit qu'il était inconcevable d'avoir un <item> sans identifiant ? C'était parce que l'identifiant d'un <item> permet de déterminer comment il réagit aux clics au sein de la méthode boolean onOptionsItemSelected (MenuItem item).

Dans l'exemple précédent, si on veut que cliquer sur le premier item active les deux items inactifs, on pourrait utiliser le code suivant dans notre activité :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private Menu m = null;

@Override
public boolean onCreateOptionsMenu(Menu menu) 
{
  MenuInflater inflater = getMenuInflater();
  inflater.inflate(R.menu.menu, menu);
  m = menu;
  return true;
}

@Override
public boolean onOptionsItemSelected (MenuItem item)
{
  switch(item.getItemId())
  {
    case R.id.item1:
      //Dans le Menu "m", on active tous les items dans le groupe d'identifiant "R.id.group2"
      m.setGroupEnabled(R.id.group2, true);
      return true;
  }
  return super.onOptionsItemSelected(item);
}

Et ils veulent dire quoi les true et false en retour ?

On retourne true si on a bien géré l'item, false si on a rien géré. D'ailleurs, si on passe l'item à super.onOptionsItemSelected(item), alors la méthode retournera false puisqu'elle ne sait pas gérer l'item. En revanche, je vous conseille de toujours retourner super.onOptionsItemSelected(item) quand vous êtes dans une classe qui ne dérive pas directement de Activity, puisqu'il se peut que vous gériez l'item dans une superclasse de votre classe actuelle.

Dans boolean onCreateOptionsMenu(menu), on retourne toujours true puisqu'on gère dans tous les cas la création du menu.

Google nous fournit une astuce de qualité sur son site : souvent, une application partage plus ou moins le(s) même(s) menu(s) entre tous ses écrans (et donc toutes ses activités), c'est pourquoi il est conseillé d'avoir une activité de base qui ne gère que les évènements liés au(x) menu(s) (création dans onCreateOptionsMenu, mise à jour dans onPrepareOptionsMenu et gestion des évènements dans onOptionsItemSelected), puis d'en faire dériver toutes les activités de notre application qui utilisent les mêmes menus.

Menu contextuel

Le menu contextuel est très différent du menu d'options, puisqu'il n’apparaît pas quand on appuie sur le bouton d'options, mais plutôt quand on clique sur n'importe quel élément ! Sur Windows, c'est le menu qui apparaît quand vous faites un clic droit.

Alors, on ne veut peut-être pas que tous les objets aient un menu contextuel, c'est pourquoi il faut déclarer quels widgets en possèdent, et cela se fait dans la méthode de la classe Activity void registerForContextMenu (View vue). Désormais, dès que l'utilisateur fera un clic long sur cette vue, un menu contextuel s'ouvrira… enfin, si vous le définissez !

Ce menu se définit dans la méthode suivante :

1
void onCreateContextMenu (ContextMenu menu, View vue, ContextMenu.ContextMenuInfo menuInfo)

menu est le menu à construire, vue la vue sur laquelle le menu a été appelé et menuInfo indique sur quel élément d'un adaptateur a été appelé le menu, si on se trouve dans une liste par exemple. Cependant, il n'existe pas de méthode du type OnPrepare cette fois-ci, par conséquent le menu est détruit puis reconstruit à chaque appel. C'est pourquoi il n'est pas conseillé de conserver le menu dans un paramètre comme nous l'avions fait pour le menu d'options. Voici un exemple de construction de menu contextuel de manière programmatique :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//Notez qu'on utilise Menu.FIRST pour indiquer le premier élément d'un menu
private int final static MENU_DESACTIVER = Menu.FIRST;
private int final static MENU_RETOUR = Menu.FIRST + 1;

@Override
public void onCreateContextMenu(ContextMenu menu, View vue, ContextMenuInfo menuInfo) {
  super.onCreateContextMenu(menu, vue, menuInfo);
  menu.add(Menu.NONE, MENU_DESACTIVER, Menu.NONE, "Supprimer cet élément");
  menu.add(Menu.NONE, MENU_RETOUR, Menu.NONE, "Retour");
}

On remarque deux choses. Tout d'abord pour écrire des identifiants facilement, la classe Menu possède une constante Menu.FIRST qui permet de désigner le premier élément, puis le deuxième en incrémentant, etc. Ensuite, on passe les paramètres à la superclasse. En fait, cette manœuvre a pour but bien précis de permettre de récupérer le ContextMenuInfo dans la méthode qui gère l'évènementiel des menus contextuels, la méthode boolean onContextItemSelected (MenuItem item). Ce faisant, vous pourrez récupérer des informations sur la vue qui a appelé le menu avec la méthode ContextMenu.ContextMenuInfo getMenuInfo () de la classe MenuItem. Un exemple d'implémentation pour notre exemple pourrait être :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Override
public boolean onContextItemSelected(MenuItem item) {
  switch (item.getItemId()) {
    case MENU_DESACTIVER:
      item.getMenuInfo().targetView.setEnabled(false);

    case MENU_RETOUR:
      return true;
  }
  return super.onContextItemSelected(item);
}

Voilà ! Le ContextMenuInfo a permis de récupérer la vue grâce à son attribut targetView. Il possède aussi un attribut id pour récupérer l'identifiant de l'item (dans l'adaptateur) concerné ainsi qu'un attribut position pour récupérer sa position au sein de la liste.

Maintenant que vous maîtrisez les menus, oubliez tout

Titre racoleur, j'en conviens, mais qui révèle une vérité qu'il vous faut considérer : le bouton Menu est amené à disparaître. :o De manière générale, les utilisateurs n'utilisent pas ce bouton, il n'est pas assez visuel pour eux, ce qui fait qu'ils n'y pensent pas ou ignorent son existence. C'est assez grave, oui. Je vous apprends à l'utiliser parce que c'est quand même sacrément pratique et puissant, mais c'est à vous de faire la démarche d'apprendre à l'utilisateur comment utiliser correctement ce bouton, avec un Toast par exemple.

Il existe des solutions qui permettent de se passer de ce menu. Android a introduit dans son API 11 (Android 3.0) l'ActionBar, qui est une barre de titre étendue sur laquelle il est possible d'ajouter des widgets de façon à disposer d'options constamment visibles. Cette initiative a été efficace puisque le taux d'utilisation de l'ActionBar est bien supérieur à celui du bouton Menu.

Cependant, pour notre cours, cette ActionBar n'est pas disponible puisque nous utilisons l'API 7, et qu'il n'est pas question d'utiliser l'API 11 rien que pour ça — vous ne toucheriez plus que 5 % des utilisateurs de l'Android Market, au lieu des 98 % actuels… Il existe des solutions alternatives, comme celle-ci qui est officielle ou celle-là qui est puissante. Je vous invite à les découvrir par vous-mêmes. ;)

Histoire de retourner le couteau dans la plaie, sachez que les menus contextuels sont rarement utilisés, puisqu'en général l'utilisateur ignore leur présence ou ne sait pas comment les utiliser (faire un appui long, c'est compliqué pour l'utilisateur, vraiment >_< ). Encore une fois, vous pouvez enseigner à vos utilisateurs comment les utiliser, ou bien ajouter une alternative plus visuelle pour ouvrir un menu sur un objet. Ça tombe super bien, c'est le sujet du prochain chapitre.


  • La création d'un menu se fait en XML pour tout ce qui est statique et en Java pour tout ce qui est dynamique.
  • La déclaration d'un menu se fait obligatoirement avec un élément menu à la racine du fichier et contiendra des éléments item.
  • Un menu d'options s'affiche lorsque l'utilisateur clique sur le bouton de menu de son appareil. Il ne sera affiché que si le fichier XML représentant votre menu est désérialisé dans la méthode boolean onCreateOptionsMenu(Menu menu) et que vous avez donné des actions à chaque item dans la méthode boolean onOptionsItemSelected(MenuItem item).
  • Un menu contextuel s'affiche lorsque vous appuyez longtemps sur un élément de votre interface. Pour ce faire, vous devez construire votre menu à partir de la méthode void onCreateContextMenu(ContextMenu menu, View vue, ContextMenu.ContextMenuInfo menuInfo) et récupérer la vue qui a fait appel à votre menu contextuel à partir de la méthode boolean onContextItemSelected(MenuItem item).
  • Sachez tout de même que le bouton menu physique tend à disparaitre de plus en plus pour un menu tactile.