Je ne vais pas refaire ici une présentation sur la gestion des clics sous Android car je pense que c’est tout simplement la base lorsqu’on développe ses premières interfaces graphiques. Quoi? Vous ne vous rappelez plus de cette fameuse “technique”? Un petit récapitulatif s’impose car la notion de “listener” est essentielle sous Android! Imaginons un bouton créé via XML sur lequel on souhaite être notifié des différents clics qu’il subit. La technique de récupération de l’évènement consiste tout d’abord à initialiser un bouton avec un identifiant (afin de pouvoir en récupérer une référence dans le code Java) :

<?xml version="1.0" encoding="utf-8"?>
<Button
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/bouton_vive_android"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="Vive Android!" />

On modifie ensuite le OnClickListener de ce bouton grâce à la méthode setOnClickListener de la classe View :

findViewById(R.id.bouton_vive_android).setOnClickListener(new View.OnClickListener() {
	public void onClick(View v) {
		handleClick(v);
	}
});
 
public void handleClick(View v) {
	// Gérer le clic
}

Le problème d’une telle technique est qu’il est assez rébarbatif pour le développeur de devoir répéter ce code à chaque vue sur laquelle on souhaite recevoir des notifications de clics. Heureusement Android 1.6 vient ajouter une fonctionnalité supplémentaire qui accélère le développement : l’attribut XML onClick. Notre exemple se simplifie maintenant en quelques lignes de code XML (une seule et unique ligne supplémentaire) :

<?xml version="1.0" encoding="utf-8"?>
<Button
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/bouton_vive_android"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:onClick="handleClick"
	android:text="Vive Android!" />

Grâce à ce code, Android va appeler la méthode handleClick lorsque un clic a eu lieu sur le bouton. Le code Java nécessite quant à lui aucune modification si vous avez utilisé le modèle présenté ci-dessus (vérifier simplement que la méthode handleClick est bien publique et qu’elle est définie dans l’Activity utilisant le bouton. Simple n’est ce pas? A vous de jouer!

J’entame grâce à cet article une nouvelle série de posts sur les astuces de développement Android. Pourquoi une telle série? La raison est simple : le développement sous la plateforme de Google comporte souvent des problèmes récurrents. Pour aider au mieux les débutants, il me semble primordial de leurs montrer les différentes techniques permettant d’optimiser leurs applications mais également de faciliter les futurs développements. Ne traitant que de méthodes générales, les articles de cette série seront donc normalement assez succincts et se baseront sur des questions/problèmes que j’ai pu remarquer dans différents forums.

Je pense essentiel de débuter cette série par un problème qui hante les développeurs débutant sur la plateforme Android : la minimisation des View. Lorsqu’on débute le développement avec le petit droid vert, on ne réfléchit pas trop à l’optimisation des ces applications. Malheureusement, force est de constater que le développement sur terminaux mobiles est régit par de fortes contraintes. La contrainte majeure réside dans le faible niveau de ressources disponibles (batterie, mémoire, processeur, etc.). Pour obtenir une application fluide, il est important de garder en tête de ne pas faire de choses inutiles (ça semble évident … et pourtant c’est exactement l’opposé de ce que je peux voir sur les forums de développement). L’astuce (c’est plus un conseil qu’une astuce) consiste à minimiser le nombre de vues utilisées dans votre interface graphique.

Prenons l’exemple suivant affichant simplement un texte au centre de l’écran (c’est une petite modification du layout disponible lors de la création d’un projet sous Eclipse) :

<?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:gravity="center"
		android:text="Minimisez l'utilisation des vues !" />
</LinearLayout>

HierarchyViewer permet de visualiser l’arbre des vues courant : Le cadre bleu indique la vue “racine” de notre programme. On s’aperçoit également qu’Android fournit, de base, un FrameLayout comme racine. Il est donc inutile d’utiliser un LinearLayout pour positionner notre TextView. Le code optimisé est le suivant :

<?xml version="1.0" encoding="utf-8"?>
<TextView
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:layout_height="wrap_parent"
	android:gravity="center"
	android:text="Minimisez l'utilisation des vues !" />

L’arbre résultant est minimal puisqu’on n’utilise aucune vue inutile :Dans notre course effrénée à l’optimisation, on pourrait même vouloir totalement supprimer le FrameLayout fournit par le système. Cette optimisation est malheureusement impossible puisqu’Android nécessite au minimum un layout (ViewGroup) comme racine pour englober les vues.

Je viens d’exposer un problème évident mais pourtant récurrent. Lorsque vous créez vos interfaces graphiques, réfléchissez bien à ne pas initialiser de vues/layouts inutiles. Préférez également les layouts comme FrameLayout dès que possible car ils sont plus rapides (il ne font qu’empiler les vues en haut à gauche) que LinearLayout ou RelativeLayout.

Note : malgré son indéniable application à Android, cet article traite d’un concept qui n’est pas propre à Android. En effet les images étirables sont par exemple disponibles dans des formes plus ou moins similaires sous le SDK de l’iPhone (de façon beaucoup moins avancée et intégrée - cf UIImage et son message - (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight) et dans le CSS3 par le biais de la propriété border-image (j’attends avec impatience la généralisation de cette propriété car elle signe la fin des blocs imbriqués les uns dans les autres - astuce nécessaire pour obtenir une “boite” à taille variable avec des bords arrondis en développement web).

Lors d’un précédent article, j’ai décrit un moyen de définir ses propres boutons. Cette technique reposait principalement sur l’utilisation de l’objet StateListDrawable qu’on retrouve dans le package android.graphics.drawable. En plus des différentes demandes, il me semble essentiel de bien faire connaître les différents Drawable utilisables sur Android. Il s’avère qu’Android fournit généralement l’ensemble des méthodes et objets pour faire de votre interface graphique la plus belle et la plus rapide au monde (j’exagère légèrement là :p) ! Il suffit simplement de savoir que ces objets “magiques” existent. Cet article s’attache à définir le concept de “9-patch” ou plus précisément de l’objet NinePatchDrawable

(Presque) fini la ligne de commande, l’informatique et plus particulièrement les interfaces utilisateurs d’aujourd’hui sont de plus en plus graphiques et animées. Comment concevoir, de nos jours, des interfaces du type des applications Windows 95 faites de segments et d’angles droits et où la transparence était encore une notion futuriste … c’est tout simplement impossible ! La tendance est plutôt à arrondir les angles, à jouer avec la transparence pour obtenir des effets sympas, etc. Le monde du mobile ne déroge pas à la règle puisque les utilisateurs de PC/Mac souhaitent retrouver l’ergonomie et la beauté de leurs OS “fixes” sur leur téléphone.

Les lecteurs de ce blog l’auront très probablement remarqué, je suis partisan du “apprendre par l’exemple”. Je vais donc utiliser un cas d’étude assez simple pour bien introduire le merveilleux concept d’images étirables. Pour faire en sorte que l’intégralité de l’UI de mon application s’accorde (charte graphique cohérente), j’aimerai définir une boite de dialogue uniformisée aux couleurs de l’interface. L’image ci dessous montre un des résultats que j’attends :

A première vue, ça semble très simple car il suffit simplement de mettre l’image du rectangle vert et blanc comme fond d’une simple TextView. Il ne faut néanmoins pas oublier que le contenu et la taille de la boite de dialogue peuvent changer. En utilisant cette méthode un peu rapide, on rencontre très vite un problème lors du passage en orientation paysage et/ou que le texte augmente : le rectangle est horriblement étiré et on retrouve des bords “salement” arrondis.

La seconde méthode, beaucoup plus barbare mais ayant de meilleurs résultats, consiste à séparer la boite de dialogue en différent parties (c’est exactement la même méthode utilisée en développement web lorsqu’on souhaite créer une “boite” à taille variable). Pour ce faire, il suffit de casser la boite de dialogue en composants élémentaires : les 4 coins, les 4 bords, l’image du droïd et une TextView (les plus doués remarqueront même que le modèle si dessous n’est pas adéquat car si on augmente la largeur de la boite de dialogue, le droïd est également étiré - mais l’objectif est ici de montrer que cette méthode est à proscrire). Avec beaucoup de courage et de patience, on réussit à obtenir un modèle de boite de dialogue extensible. Malheureusement cette méthode comporte de sérieux désavantages : il faut séparer le résultat en plusieurs vues (processus long et assez rébarbatif) pour aboutir à un empilement de vues assez indigeste (surtout consommateur de ressources).

La troisième méthode consiste à utiliser les … 9-patchs ! Pour résumer un 9-patch est une image sur laquelle on définit les zones qui sont extensibles. Ainsi le système, lors du dessin de l’image, peut, si nécessaire, étirer l’image à volonté en utilisant les zones précédemment citées. Dans le cas d’un rectangle simple à coins arrondis, il suffit d’informer le système que les zones extensibles se trouvent à l’intérieur du rectangle (coins arrondis exclus). Pour effectuer cette prouesse, reprenons notre exemple (qui est légèrement plus compliqué puisqu’il ne faut pas non plus étirer le droïd). On définit les zones étirables en déterminant les zones étirables sur X et sur Y (en vert). C’est le croisement de ces zones (en rose) qui indique au système la partie de l’image que sera étirée :

Android, dispose néanmoins d’avantages énormes sur ces concurrents : il est possible de définir des zones étirables multiples (comme dans l’exemple) ci dessus alors que sous l’iPhone par exemple il n’est possible de définir qu’une seule et unique zone étirable (qui doit de plus être obligatoirement centrée au milieu de l’image). Lorsque vous définissez plusieurs zones étirables, Android va étirer proportionnellement ces zones afin de conserver les proportions. Dans le cas de notre boite de dialogue, les zones extensibles font toutes les deux 4 pixels de large afin de laisser l’image du droïd au centre.

Le système de Google va encore plus loin en permettant de définir les zones dans lesquelles le contenu doit se positionner (c’est à dire les paddings/marges intérieures). La technique est similaire et consiste à choisir une zone de contenu dans le croisement de 2 zones respectivement sur X et Y. Cette option sur l’emplacement du contenu étant optionnelle, si vous ne définissez aucune zone, le système considèrera que la zone de contenu est l’intégralité de l’image (paddings nuls).

L’énorme avantage d’Android sur les autres systèmes est que l’information sur les zones étirables et la zone de contenu se trouve dans l’image elle même. Ainsi, il a une séparation parfaite de la partie métier et de l’interface graphique. On définit ces zones à l’aide d’un trait noir d’un pixel de large en haut et à gauche pour les zones extensibles et en bas et à droite pour la zone de contenu. Enfin, on ajoute l’extension .9.png pour informer le système que l’image est un 9-patch. Le résultat est donc le suivant :

J’ai développé un petit exemple (téléchargeable dans ce zip) afin de bien montrer la facilité d’utilisation des images extensibles sous Android :

package com.cyrilmottier.android.fancytoast;
 
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
 
public class LauncherActivity extends Activity {
 
	private Toast mFancyToast;
 
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
 
		View fancyToast = getLayoutInflater().inflate(R.layout.fancy_toast,
				null);
 
		mFancyToast = new Toast(this);
		mFancyToast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
		mFancyToast.setDuration(Toast.LENGTH_LONG);
		mFancyToast.setView(fancyToast);
 
		((TextView) mFancyToast.getView()).setText(LauncherActivity.this
				.getResources().getString(R.string.toast_text));
 
		setContentView(R.layout.main);
	}
 
	public void showToast(View view) {
		mFancyToast.show();
	}
}

R.layout.fancy_toast contient le code XML suivant. Remarquez l’utilisation du 9-patch fancy_toast_frame de façon totalement transparente (pas d’extension .9.png) :

<?xml version="1.0" encoding="utf-8"?>
<TextView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="@drawable/fancy_toast_frame"
  android:textColor="#000"
  android:singleLine="false"
  android:gravity="center" />

J’aimerai terminer sur un petit logiciel fournit avec le SDK Android. En effet, avec un Drawable aussi puissant, l’équipe Android se devait de fournir un logiciel permettant de créer de telles images. Ce logiciel c’est Draw9Patch. Je ne pense pas qu’il soit nécessaire d’expliquer son utilisation ici car c’est un outil très simple et ergonomique. Pour résumer il permet, à partir d’un png de définir les zones étirables et la zone de contenu et de voir, en temps réel, le résultat de l’étirement de l’image sur les deux axes.

Je tenais tout d’abord à m’excuser auprès des lecteurs de ce blog. En effet, voilà près de 3 mois que je n’ai rien posté concernant le développement sur Android. J’ai donc reçu quelques courriels me demandant la raison d’une telle “coupure” dans la rédaction de mes posts. La raison est assez simple : j’ai simplement été quelque peu débordé par les évenements durant ces 3 derniers mois : finalisation de mes études, recherche d’un emploi, … vacances d’un mois et demi au pays des cow-boys :p et j’en passe. Je remercie sincèrement les lecteurs qui m’ont encouragé car il m’ont montré que le travail que j’entreprends sur ce blog n’est pas inutile et sert même à beaucoup. Pour les intéréssés, j’ai trouvé un travail concordant avec mes préférences pour le monde “mobile” puisque je travaille sur les systèmes d’exploitation avancés tels qu’Android bien sûr mais aussi l’iPhone.

Pour ne pas brusquer mes petits neurones et les votres (mais également parce que je pense que parler de sujets extrêmement techniques sur ce blog implique que peu de lecteurs lisent et comprennent), je pense repartir sur des choses assez basiques. Ce post simple et facile d’accès traite de la création de boutons personnalisés.

Une interface graphique consiste à établir un lien entre le programme et l’utilisateur. La communication peut bien sûr s’effectuer à sens unique lorsque le programme affiche simplement des informations mais on se rend vite compte qu’une interface à “sens unique” n’a que peu d’intérêt. Imaginez notre bon vieux Android dépourvu de boutons … autant dire que l’utilisation devient rapidement déplorable et ennuyante :p. La plupart des interfaces graphiques contiennent donc des éléments de type “bouton”. Le look ‘n feel Android ne déroge pas à la régle et permet d’inclure des boutons dans les différents écrans de votre application.

Pour parfaire la “relation” entre l’interface et l’utilisateur, les boutons disposent en général de plusieurs états : normal, pressé, inactif, etc… L’utilisateur peut insi comprendre l’état d’un bouton suivant son image associée. Etudions, par exemple, le cas du bouton natif Android (dans sa version 1.6) : sa couleur change suivant son état courant :

  • Lorsqu’il est inactif (disabled), il est gris clair
  • Lorsqu’il est actif, il est gris légèrement foncé
  • Lorsqu’il a le focus, il est orange vif
  • Lorsqu’il est pressé, il est orange clair
  • etc.

Le développement d’un programme peut s’accompagner de la création d’un look ‘n feel propre à la marque/personne qui réalise l’application. L’objectif de cet article est donc d’étudier comment modifier l’apparence native des boutons Android et de mieux comprendre le principe de fonctionnement de ces derniers.

L’objet Button hérite de TextView héritant lui même de View. Il est donc possible d’accéder à l’attribut android:background de View et de le modifier afin de changer l’apparence native d’un bouton. Si vous effectuez cette opération (en modifiant simplement sa valeur à l’aide d’une image présente dans res/drawable, vous vous apercevrez que l’esthétique du bouton a bien changé mais ne se modifie pas en fonction des différents états de ce dernier. L’astuce réside en fait dans l’objet “foure-tout” (mais magnifiquement conçu) Drawable. Un Drawable est un objet pouvant “se dessiner”. Dans notre cas, il serait nécessaire d’avoir un Drawable qui change en fonction de l’état du bouton : un StateListDrawable!

StateListDrawable permet d’encapsuler plusieurs Drawables qui sont associés à un état (selected, focused, disabled, etc.). C’est grâce à ce type que nous allons pouvoir créer un bouton totalement personnalisé. Commençons tout d’abord par créer nos nouveaux boutons sous forme de 9-patchs (je ne pense pas avoir fait d’articles sur cette merveilleuse technique des 9-patchs - images extensibles - mais si certains lecteurs sont intéressés pour en savoir un peu plus, qu’ils n’hésitent pas à se manifester dans les commentaires) :

  

Il suffit maintenant de créer ce nouveau Drawable qui sera utilisé comme arrière plan de notre bouton. Comme d’accoutumé, deux possibilités s’offrent à nous : la méthode code, et la méthode XML. Assez partisan du “tout XML”, je vous conseille d’utiliser cette méthode car elle allège votre code et sépare bien la partie interface graphique de la partie métier. Le code d’exemple fourni plus bas montre une application utilisant les deux possibilités. EN XML, on utilise simplement la balise selector dans laquelle on ajoute tous nos Drawables sous la forme de balises item. Pour finir, on choisit les différentes valeurs des attributs android:state et android:drawable qui permettent tout simplement d’associer le Drawable à un état (fichier mybutton.xml mis dans res/drawable):

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" android:state_enabled="true" android:drawable="@drawable/button1" />
    <item android:state_pressed="true" android:drawable="@drawable/button2" />
    <item android:state_focused="true" android:drawable="@drawable/button3" />
    <item android:state_enabled="true" android:drawable="@drawable/button1" />
</selector>

Prenez garde à l’ordre des items dans la balise selector. En effet, lorsqu’on appui sur le bouton, le système lit les lignes dans l’ordre pour déterminer quel Drawable utiliser. Lorsqu’on appui sur un bouton, ce dernier est à la fois “pressed” et “enabled”. Si la ligne de l’état “enabled” avait été placée avant la ligne de l’état “pressed” dans l’exemple ci-dessus, le bouton n’aurait pas changé d’apparence car l’état “enabled” aurait été considéré comme viable par le système et donc utilisé en premier.

Cela peut sembler bizarre mais nous venons bien de créer un nouveau Drawable c’est à dire un objet “dessinable” à partir d’un fichier XML/texte. La dernière étape consiste donc à modifier la propriété android:background de notre Button pour que ce dernier change totalement d’apparence. Encore une fois, cette action s’effectue plus facilement via le XML :

<Button
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="New dummy content"
	android:background="@drawable/mybutton" />

Nous ne venons que d’éfleurer les possibilités données par le package android.graphics.drawable. En effet des objets comme LevelListDrawable, NinePatchDrawable (mon préféré :p) ou AnimationDrawable facilite grandement l’utilisation des images sous Android. Pour bien montrer à quoi ressemble les boutons créés, j’ai développé une petite application d’exemple disponible ici: