Version en ligne

Tutoriel : Créez des applications pour Android

Table des matières

Créez des applications pour Android
L'univers Android
La création d'Android
La philosophie et les avantages d'Android
Les difficultés du développement pour des systèmes embarqués
Le langage Java
Installation et configuration des outils
Conditions initiales
Le Java Development Kit
Le SDK d'Android
L'IDE Eclipse
L'émulateur de téléphone : Android Virtual Device
Test et configuration
Configuration du vrai terminal
Votre première application
Activité et vue
Création d'un projet
Un non-Hello world!
Lancement de l'application
Les ressources
Le format XML
Les différents types de ressources
L'organisation
Ajouter un fichier avec Eclipse
Récupérer une ressource
Constitution des interfaces graphiques
L'interface d'Eclipse
Règles générales sur les vues
Identifier et récupérer des vues
Les widgets les plus simples
Les widgets
Gérer les évènements sur les widgets
Organiser son interface avec des layouts
LinearLayout : placer les éléments sur une ligne
RelativeLayout : placer les éléments les uns en fonction des autres
TableLayout : placer les éléments comme dans un tableau
FrameLayout : un layout un peu spécial
ScrollView : faire défiler le contenu d'une vue
Les autres ressources
Aspect général des fichiers de ressources
Les chaînes de caractères
Les drawables
Les styles
Les animations
TP : un bloc-notes
Objectif
Spécifications techniques
Déboguer des applications Android
Ma solution
Objectifs secondaires
Des widgets plus avancés et des boîtes de dialogue
Les listes et les adaptateurs
Plus complexe : les adaptateurs personnalisés
Les boîtes de dialogue
Les autres widgets
Gestion des menus de l’application
Menu d'options
Menu contextuel
Maintenant que vous maîtrisez les menus, oubliez tout
Création de vues personnalisées
Règles avancées concernant les vues
Méthode 1 : à partir d'une vue préexistante
Méthode 2 : une vue composite
Méthode 3 : créer une vue en partant de zéro
Préambule : quelques concepts avancés
Généralités sur le nœud <manifest>
Le nœud <application>
Les permissions
Gérer correctement le cycle des activités
Gérer le changement de configuration
La communication entre composants
Aspect technique
Les intents explicites
Les intents implicites
La résolution des intents
Pour aller plus loin : navigation entre des activités
Pour aller plus loin : diffuser des intents
Le stockage de données
Préférences partagées
Manipulation des fichiers
TP : un explorateur de fichiers
Objectifs
Spécifications techniques
Ma solution
Améliorations envisageables
Les bases de données
Généralités
Création et mise à jour
Opérations usuelles
Les curseurs
Le travail en arrière-plan
La gestion du multitâche par Android
Gérer correctement les threads simples
AsyncTask
Les services
Qu'est-ce qu'un service ?
Gérer le cycle de vie d'un service
Créer un service
Les notifications et services de premier plan
Pour aller plus loin : les alarmes
Le partage de contenus entre applications
Côté client : accéder à des fournisseurs
Créer un fournisseur
Créer un AppWidget
L'interface graphique
Définir les propriétés
Le code
Déclarer l'AppWidget dans le Manifest
Application : un AppWidget pour accéder aux tutoriels du Site du Zéro
La connectivité réseau
Surveiller le réseau
Afficher des pages Web
Effectuer des requêtes HTTP
Apprenez à dessiner
La toile
Afficher notre toile
La localisation et les cartes
La localisation
Afficher des cartes
La téléphonie
Téléphoner
Envoyer et recevoir des SMS et MMS
Le multimédia
Le lecteur multimédia
Enregistrement
Les capteurs
Les différents capteurs
Opérations génériques
Les capteurs de mouvements
Les capteurs de position
Les capteurs environnementaux
TP : un labyrinthe
Objectifs
Spécifications techniques
Ma solution
Améliorations envisageables
Publier et rentabiliser une application
Préparez votre application à une distribution
Les moyens de distribution
Rentabilisez votre application
L'architecture d'Android
Le noyau Linux
Le moteur d'exécution d'Android

Créez des applications pour Android

Bonjour à tous et bienvenue dans le monde merveilleux du développement d'applications Android !

Bugdroid, la mascotte d'Android
Bugdroid, la mascotte d'Android

Avec l'explosion des ventes de smartphones ces dernières années, Android a pris une place importante dans la vie quotidienne. Ce système d'exploitation permet d'installer des applications de toutes sortes : jeux, bureautique, multimédia, etc. Que diriez-vous de développer vos propres applications pour Android, en les proposant au monde entier via le Play Store, le marché d'applications de Google ? Eh bien figurez-vous que c'est justement le but de ce cours : vous apprendre à créer des applications pour Android !

Cependant, pour suivre ce cours, il vous faudra quelques connaissances :

Rien de bien méchant, comme vous pouvez le voir. Mais le développement pour Android est déjà assez complet comme cela, ce serait bien trop long de revenir sur ces bases-là. Ce cours débutera cependant en douceur et vous présentera d'abord les bases essentielles pour le développement Android afin que vous puissiez effectuer des applications simples et compatibles avec la majorité des terminaux. Puis nous verrons tout ce que vous avez besoin de savoir afin de créer de belles interfaces graphiques ; et enfin on abordera des notions plus avancées afin d'exploiter les multiples facettes que présente Android, dont les différentes bibliothèques de fonctions permettant de mettre à profit les capacités matérielles des appareils.

À la fin de ce cours, vous serez capables de réaliser des jeux, des applications de géolocalisation, un navigateur Web, des applications sociales, et j'en passe. En fait, le seul frein sera votre imagination !

Open Handset Alliance
Installation des versions d'Android
Le bloc notes que nous allons développer dans un TP

L'univers Android

La création d'Android

Dans ce tout premier chapitre, je vais vous présenter ce que j'appelle l'« univers Android » ! Le système, dans sa genèse, part d'une idée de base simple, et très vite son succès fut tel qu'il a su devenir indispensable pour certains constructeurs et utilisateurs, en particulier dans la sphère de la téléphonie mobile.
Nous allons rapidement revenir sur cette aventure et sur la philosophie d'Android, puis je rappellerai les bases de la programmation en Java, pour ceux qui auraient besoin d'une petite piqûre de rappel... ;)

La création d'Android

L'univers Android La philosophie et les avantages d'Android

La création d'Android

Quand on pense à Android, on pense immédiatement à Google, et pourtant il faut savoir que cette multinationale n'est pas à l'initiative du projet. D'ailleurs, elle n'est même pas la seule à contribuer à plein temps à son évolution. À l'origine, « Android » était le nom d'une PME américaine, créée en 2003 puis rachetée par Google en 2005, qui avait la ferme intention de s'introduire sur le marché des produits mobiles. La gageure, derrière Android, était de développer un système d'exploitation mobile plus intelligent, qui ne se contenterait pas uniquement de permettre d’envoyer des SMS et transmettre des appels, mais qui devait permettre à l'utilisateur d'interagir avec son environnement (notamment avec son emplacement géographique). C'est pourquoi, contrairement à une croyance populaire, il n'est pas possible de dire qu'Android est une réponse de Google à l'iPhone d'Apple, puisque l'existence de ce dernier n'a été révélée que deux années plus tard.

C'est en 2007 que la situation prit une autre tournure. À cette époque, chaque constructeur équipait son téléphone d'un système d'exploitation propriétaire. Chaque téléphone avait ainsi un système plus ou moins différent. Ce système entravait la possibilité de développer facilement des applications qui s'adapteraient à tous les téléphones, puisque la base était complètement différente. Un développeur était plutôt spécialisé dans un système particulier et il devait se contenter de langages de bas niveaux comme le C ou le C++. De plus, les constructeurs faisaient en sorte de livrer des bibliothèques de développement très réduites de manière à dissimuler leurs secrets de fabrication. En janvier 2007, Apple dévoilait l'iPhone, un téléphone tout simplement révolutionnaire pour l'époque. L'annonce est un désastre pour les autres constructeurs, qui doivent s'aligner sur cette nouvelle concurrence. Le problème étant que pour atteindre le niveau d'iOS (iPhone OS), il aurait fallu des années de recherche et développement à chaque constructeur...

C'est pourquoi est créée en novembre de l'année 2007 l'Open Handset Alliance (que j'appellerai désormais par son sigle OHA), et qui comptait à sa création 35 entreprises évoluant dans l'univers du mobile, dont Google. Cette alliance a pour but de développer un système open source (c'est-à-dire dont les sources sont disponibles librement sur internet) pour l'exploitation sur mobile et ainsi concurrencer les systèmes propriétaires, par exemple Windows Mobile et iOS. Cette alliance a pour logiciel vedette Android, mais il ne s'agit pas de sa seule activité.

L'OHA compte à l'heure actuelle 80 membres.

Le logo de l'OHA, une organisation qui cherche à développer des standards open source pour les appareils mobiles
Le logo de l'OHA, une organisation qui cherche à développer des standards open source pour les appareils mobiles

Android est à l'heure actuelle le système d'exploitation pour smartphones et tablettes le plus utilisé.

Les prévisions en ce qui concerne la distribution d'Android sur le marché sont très bonnes avec de plus en plus de machines qui s'équipent de ce système. Bientôt, il se trouvera dans certains téléviseurs (vous avez entendu parler de Google TV, peut-être ?) et les voitures. Android sera partout. Ce serait dommage de ne pas faire partie de ça, n'est-ce pas ? ;)

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

L'univers Android La philosophie et les avantages d'Android

La philosophie et les avantages d'Android

La création d'Android Les difficultés du développement pour des systèmes embarqués

La philosophie et les avantages d'Android

Open source

Le contrat de licence pour Android respecte les principes de l'open source, c'est-à-dire que vous pouvez à tout moment télécharger les sources et les modifier selon vos goûts ! Bon, je ne vous le recommande vraiment pas, à moins que vous sachiez ce que vous faites... Notez au passage qu'Android utilise des bibliothèques open source puissantes, comme par exemple SQLite pour les bases de données et OpenGL pour la gestion d'images 2D et 3D.

Gratuit (ou presque)

Android est gratuit, autant pour vous que pour les constructeurs. S'il vous prenait l'envie de produire votre propre téléphone sous Android, alors vous n'auriez même pas à ouvrir votre porte-monnaie (mais bon courage pour tout le travail à fournir !). En revanche, pour poster vos applications sur le Play Store, il vous en coûtera la modique somme de 25$. Ces 25$ permettent de publier autant d'applications que vous le souhaitez, à vie ! :D

Facile à développer

Toutes les API mises à disposition facilitent et accélèrent grandement le travail. Ces APIs sont très complètes et très faciles d'accès. De manière un peu caricaturale, on peut dire que vous pouvez envoyer un SMS en seulement deux lignes de code (concrètement, il y a un peu d'enrobage autour de ce code, mais pas tellement).

Facile à vendre

Le Play Store (anciennement Android Market) est une plateforme immense et très visitée ; c'est donc une mine d'opportunités pour quiconque possède une idée originale ou utile.

Flexible

Le système est extrêmement portable, il s'adapte à beaucoup de structures différentes. Les smartphones, les tablettes, la présence ou l'absence de clavier ou de trackball, différents processeurs... On trouve même des fours à micro-ondes qui fonctionnent à l'aide d'Android ! ^^
Non seulement c'est une immense chance d'avoir autant d'opportunités, mais en plus Android est construit de manière à faciliter le développement et la distribution en fonction des composants en présence dans le terminal (si votre application nécessite d'utiliser le Bluetooth, seuls les terminaux équipés de Bluetooth pourront la voir sur le Play Store).

Ingénieux

L'architecture d'Android est inspirée par les applications composites, et encourage par ailleurs leur développement. Ces applications se trouvent essentiellement sur internet et leur principe est que vous pouvez combiner plusieurs composants totalement différents pour obtenir un résultat surpuissant. Par exemple, si on combine l'appareil photo avec le GPS, on peut poster les coordonnées GPS des photos prises.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

La création d'Android Les difficultés du développement pour des systèmes embarqués

Les difficultés du développement pour des systèmes embarqués

La philosophie et les avantages d'Android Le langage Java

Les difficultés du développement pour des systèmes embarqués

Il existe certaines contraintes pour le développement Android, qui ne s'appliquent pas au développement habituel !

Prenons un cas concret : la mémoire RAM est un composant matériel indispensable. Quand vous lancez un logiciel, votre système d'exploitation lui réserve de la mémoire pour qu'il puisse créer des variables, telles que des tableaux, des listes, etc. Ainsi, sur mon ordinateur, j'ai 4 Go de RAM, alors que je n'ai que 512 Mo sur mon téléphone, ce qui signifie que j'en ai huit fois moins. Je peux donc lancer moins de logiciels à la fois et ces logiciels doivent faire en sorte de réserver moins de mémoire. C'est pourquoi votre téléphone est dit limité, il doit supporter des contraintes qui font doucement sourire votre ordinateur.

Voici les principales contraintes à prendre en compte quand on développe pour un environnement mobile :

Les conséquences de telles négligences peuvent être terribles pour l'utilisateur. Saturez le processeur et il ne pourra plus rien faire excepté redémarrer ! Faire crasher une application ne fera en général pas complètement crasher le système, cependant il pourrait bien s'interrompre quelques temps et irriter profondément l'utilisateur.

Il faut bien comprendre que dans le paradigme de la programmation classique vous êtes dans votre propre monde et vous n'avez vraiment pas grand-chose à faire du reste de l'univers dans lequel vous évoluez, alors que là vous faites partie d'un système fragile qui évolue sans anicroche tant que vous n'intervenez pas. Votre but est de fournir des fonctionnalités de plus à ce système et faire en sorte de ne pas le perturber.

Bon, cela paraît très alarmiste dit comme ça, Android a déjà anticipé la plupart des âneries que vous commettrez et a pris des dispositions pour éviter des catastrophes qui conduiront au blocage total du téléphone. ;) Si vous êtes un tantinet curieux, je vous invite à lire l'annexe sur l'architecture d'Android pour comprendre un peu pourquoi il faut être un barbare pour vraiment réussir à saturer le système.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

La philosophie et les avantages d'Android Le langage Java

Le langage Java

Les difficultés du développement pour des systèmes embarqués Installation et configuration des outils

Le langage Java

Cette petite section permettra à ceux fâchés avec le Java de se remettre un peu dans le bain et surtout de réviser le vocabulaire de base. Notez qu'il ne s'agit que d'un rappel, il est conseillé de connaître la programmation en Java auparavant ; je ne fais ici que rappeler quelques notions de base pour vous rafraîchir la mémoire ! Il ne s'agit absolument pas d'une introduction à la programmation.

Les variables

La seule chose qu'un programme sait faire, c'est des calculs. Il arrive qu'on puisse lui faire afficher des formes et des couleurs, mais pas toujours. Pour faire des calculs, on a besoin de variables. Ces variables permettent de conserver des informations avec lesquelles on va pouvoir faire des opérations. Ainsi, on peut avoir une variable radis qui vaudra 4 pour indiquer qu'on a quatre radis. Si on a une variable carotte qui vaut 2, on peut faire le calcul radis + carotte de manière à pouvoir déduire qu'on a six légumes.

Les primitives

En Java, il existe deux types de variable. Le premier type s'appelle les primitives. Ces primitives permettent de retenir des informations simples telles que des nombres sans virgule (auquel cas la variable est un entier, int), des chiffres à virgule (des réels, float) ou des booléens (variable qui ne peut valoir que vrai (true) ou faux (false), avec les boolean).

Les objets

Le second type, ce sont les objets. En effet, à l'opposé des primitives (variables simples), les objets sont des variables compliquées.
En fait, une primitive ne peut contenir qu'une information, par exemple la valeur d'un nombre ; tandis qu'un objet est constitué d'une ou plusieurs autres variables, et par conséquent d'une ou plusieurs valeurs. Ainsi, un objet peut lui-même contenir un objet ! Un objet peut représenter absolument ce qu'on veut : une chaise, une voiture, un concept philosophique, une formule mathématique, etc. Par exemple, pour représenter une voiture, je créerai un objet qui contient une variable roue qui vaudra 4, une variable vitesse qui variera en fonction de la vitesse et une variable carrosserie pour la couleur de la carrosserie et qui pourra valoir « rouge », « bleu », que sais-je ! D'ailleurs, une variable qui représente une couleur ? Ça ne peut pas être une primitive, ce n'est pas une variable facile ça, une couleur ! Donc cette variable sera aussi un objet, ce qui signifie qu'un objet peut contenir des primitives ou d'autres objets.

Mais dans le code, comment représenter un objet ? Pour cela, il va falloir déclarer ce qu'on appelle une classe. Cette classe aura un nom, pour notre voiture on peut simplement l'appeler Voiture, comme ceci :

// On déclare une classe Voiture avec cette syntaxe
class Voiture {
  // Et dedans on ajoute les attributs qu'on utilisera, par exemple le nombre de roues
  int roue = 4;
  // On ne connaît pas la vitesse, alors on ne la déclare pas
  float vitesse;
  // Et enfin la couleur, qui est représentée par une classe de nom Couleur
  Couleur carrosserie;
}

Les variables ainsi insérées au sein d'une classe sont appelées des attributs.

Il est possible de donner des instructions à cette voiture, comme d'accélérer ou de s'arrêter. Ces instructions s'appellent des méthodes, par exemple pour freiner :

//Je déclare une méthode qui s'appelle "arreter"
void arreter() {
  //Pour s'arrêter, je passe la vitesse à 0 
  vitesse = 0;
}

En revanche, pour changer de vitesse, il faut que je dise si j'accélère ou décélère et de combien la vitesse change. Ces deux valeurs données avant l'exécution de la méthode s'appellent des paramètres. De plus, je veux que la méthode rende à la fin de son exécution la nouvelle vitesse. Cette valeur rendue à la fin de l'exécution d'une méthode s'appelle une valeur de retour. Par exemple :

// On dit ici que la méthode renvoie un float et qu'elle a besoin d'un float et d'un boolean pour s'exécuter
float changer_vitesse(float facteur_de_vitesse, boolean acceleration)
  // S'il s'agit d'une accelération
  if(acceleration == true) {
    // On augmente la vitesse
    vitesse = vitesse + facteur_de_vitesse;
  }else {
    // On diminue la vitesse
    vitesse = vitesse - facteur_de_vitesse;
  }
  // La valeur de retour est la nouvelle vitesse
  return vitesse;
}

Parmi les différents types de méthode, il existe un type particulier qu'on appelle les constructeurs. Ces constructeurs sont des méthodes qui construisent l'objet désigné par la classe. Par exemple, le constructeur de la classe Voiture renvoie un objet de type Voiture :

// Ce constructeur prend en paramètre la couleur de la carrosserie
Voiture(Couleur carros) {
  // Quand on construit une voiture, elle a une vitesse nulle
  vitesse = 0;
  carrosserie = carros;
}

On peut ensuite construire une voiture avec cette syntaxe :

Voiture v = new Voiture(rouge);

Construire un objet s'appelle l'instanciation.

L'héritage

Il existe certains objets dont l'instanciation n'aurait aucun sens. Par exemple, un objet de type Véhicule n'existe pas vraiment dans un jeu de course. En revanche il est possible d'avoir des véhicules de certains types, par exemple des voitures ou des motos. Si je veux une moto, il faut qu'elle ait deux roues et, si j'instancie une voiture, elle doit avoir 4 roues, mais dans les deux cas elles ont des roues. Dans les cas de ce genre, c'est-à-dire quand plusieurs classes ont des attributs en commun, on fait appel à l'héritage. Quand une classe A hérite d'une classe B, on dit que la classe A est la fille de la classe B et que la classe B est le parent (ou la superclasse) de la classe A.

// Dans un premier fichier
// Classe qui ne peut être instanciée
abstract class Vehicule {
  int nombre_de_roues;
  float vitesse;
}

// Dans un autre fichier
// Une Voiture est un Vehicule
class Voiture extends Vehicule {

}

// Dans un autre fichier
// Une Moto est aussi un Vehicule
class Moto extends Vehicule {

}

// Dans un autre fichier
// Un Cabriolet est une Voiture (et par conséquent un Véhicule)
class Cabriolet extends Voiture {

}

Le mot-clé abstract signifie qu'une classe ne peut être instanciée.

Pour contrôler les capacités des classes à utiliser les attributs et méthodes les unes des autres, on a accès à trois niveaux d'accessibilité :

On trouve par exemple :

// Cette classe est accessible à tout le monde
public abstract class Vehicule {
  // Cet attribut est accessible à toutes les filles de la classe Vehicule
  protected roue;

  // Personne n'a accès à cette méthode.
  abstract private void decelerer();
}

Enfin, il existe un type de classe mère particulier : les interfaces. Une interface est impossible à instancier et toutes les classes filles de cette interface devront instancier les méthodes de cette interface — elles sont toutes forcément abstract.

//Interface des objets qui peuvent voler
interface PeutVoler {
  void décoller();
}

class Avion extends Vehicule implements PeutVoler {
  //Implémenter toutes les méthodes de PeutVoler et les méthodes abstraites de Vehicule
}
La compilation et l'exécution

Votre programme est terminé et vous souhaitez le voir fonctionner, c'est tout à fait normal. Cependant, votre programme ne sera pas immédiatement compréhensible par l'ordinateur. En effet, pour qu'un programme fonctionne, il doit d'abord passer par une étape de compilation, qui consiste à traduire votre code Java en bytecode. Dans le cas d'Android, ce bytecode sera ensuite lu par un logiciel qui s'appelle la machine virtuelle Dalvik. Cette machine virtuelle interprète les instructions bytecode et va les traduire en un autre langage que le processeur pourra comprendre, afin de pouvoir exécuter votre programme.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les difficultés du développement pour des systèmes embarqués Installation et configuration des outils

Installation et configuration des outils

Le langage Java Conditions initiales

Avant de pouvoir entrer dans le vif du sujet, nous allons vérifier que votre ordinateur est capable de supporter la charge du développement pour Android, puis, le cas échéant, on installera tous les programmes et composants nécessaires. Vous aurez besoin de plus de 800 Mo pour tout installer. Et si vous possédez un appareil sous Android, je vous montrerai comment le configurer de façon à pouvoir travailler directement avec.

Encore un peu de patience, les choses sérieuses démarreront dès le prochain chapitre.

Conditions initiales

Installation et configuration des outils Le Java Development Kit

Conditions initiales

De manière générale, n'importe quel matériel permet de développer sur Android du moment que vous utilisez Windows, Mac OS X ou une distribution Linux. Il y a bien sûr certaines limites à ne pas franchir.

Voyons si votre système d'exploitation est suffisant pour vous mettre au travail.
Pour un environnement Windows, sont tolérés XP (en version 32 bits), Vista (en version 32 et 64 bits) et 7 (aussi en 32 et 64 bits). Officieusement (en effet, Google n'a rien communiqué à ce sujet), Windows 8 est aussi supporté en 32 et 64 bits.

Et comment savoir quelle version de Windows j'utilise ?

C'est simple, si vous utilisez Windows 7 ou Windows Vista, appuyez en même temps sur la touche Windows et sur la touche R. Si vous êtes sous Windows XP, il va falloir cliquer sur Démarrer puis sur Exécuter. Dans la nouvelle fenêtre qui s'ouvre, tapez winver. Si la fenêtre qui s'ouvre indique Windows 7 ou Windows Vista, c'est bon, mais s'il est écrit Windows XP, alors vous devez vérifier qu'il n'est écrit à aucun moment 64 bits. Si c'est le cas, alors vous ne pourrez pas développer pour Android.

Sous Mac, il vous faudra Mac OS 10.5.8 ou plus récent et un processeur x86.

Sous GNU/Linux, Google conseille d'utiliser une distribution Ubuntu plus récente que la 10.04. Enfin de manière générale, n'importe quelle distribution convient à partir du moment où votre bibliothèque GNU C (glibc) est au moins à la version 2.7. Si vous avez une distribution 64 bits, elle devra être capable de lancer des applications 32 bits.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Installation et configuration des outils Le Java Development Kit

Le Java Development Kit

Conditions initiales Le SDK d'Android

Le Java Development Kit

En tant que développeur Java vous avez certainement déjà installé le JDK (pour « Java Development Kit »), cependant on ne sait jamais ! Je vais tout de même vous rappeler comment l'installer. En revanche, si vous l'avez bien installé et que vous êtes à la dernière version, ne perdez pas votre temps et filez directement à la prochaine section !

Un petit rappel technique ne fait de mal à personne. Il existe deux plateformes en Java :

Rendez-vous ici et cliquez sur Download à côté de Java SE 6 Update xx (on va ignorer Java SE 7 pour le moment) dans la colonne JDK, comme à la figure suivante.

On télécharge Java SE 6 et non Java SE 7
On télécharge Java SE 6 et non Java SE 7

On vous demande ensuite d'accepter (Accept License Agreement) ou de décliner (Decline License Agreement) un contrat de licence, vous devez accepter ce contrat avant de continuer.

Choisissez ensuite la version adaptée à votre configuration. Une fois le téléchargement terminé, vous pouvez installer le tout là où vous le désirez. Vous aurez besoin de 200 Mo de libre sur le disque ciblé.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Conditions initiales Le SDK d'Android

Le SDK d'Android

Le Java Development Kit L'IDE Eclipse

Le SDK d'Android

C'est quoi un SDK?

Un SDK, c'est-à-dire un kit de développement dans notre langue, est un ensemble d'outils que met à disposition un éditeur afin de vous permettre de développer des applications pour un environnement précis. Le SDK Android permet donc de développer des applications pour Android et uniquement pour Android.

Pour se le procurer, rendez-vous ici et cliquez sur USE AN EXISTING IDE puis sur Download the SDK Tools. Au premier lancement du SDK, un écran semblable à la figure suivante s'affichera.

L'Android SDK Manager vous permet de choisir les paquets à télécharger
L'Android SDK Manager vous permet de choisir les paquets à télécharger

Les trois paquets que je vous demanderai de sélectionner sont Tools, Android 2.1 (API 7) et Extras, mais vous pouvez voir que j'en ai aussi sélectionné d'autres.

À quoi servent les autres paquets ?

Regardez bien le nom des paquets, vous remarquerez qu'ils suivent tous un même motif. Il est écrit à chaque fois Android [un nombre] (API [un autre nombre]). La présence de ces nombres s'explique par le fait qu'il existe plusieurs versions de la plateforme Android qui ont été développées depuis ses débuts et qu'il existe donc plusieurs versions différentes en circulation.
Le premier nombre correspond à la version d'Android et le second à la version de l'API Android associée. Quand on développe une application, il faut prendre en compte ces numéros, puisqu'une application développée pour une version précise d'Android ne fonctionnera pas pour les versions précédentes.
J'ai choisi de délaisser les versions précédant la version 2.1 (l'API 7), de façon à ce que l'application puisse fonctionner pour 2.1, 2.2, 3.1… mais pas forcément pour 1.6 ou 1.5 !

Vous penserez peut-être qu'il est injuste de laisser de côté les personnes qui sont contraintes d'utiliser encore ces anciennes versions, mais sachez qu'ils ne représentent que 0,5 % du parc mondial des utilisateurs d'Android. De plus, les changements entre la version 1.6 et la version 2.1 sont trop importants pour être ignorés. Ainsi, toutes les applications que nous développerons fonctionneront sous Android 2.1 minimum. On trouve aussi pour chaque SDK des échantillons de code, samples, qui vous seront très utiles pour approfondir ou avoir un second regard à propos de certains aspects, ainsi qu'une API Google associée. Dans un premier temps, vous pouvez ignorer ces API, mais sachez qu'on les utilisera par la suite.

Une fois votre choix effectué, un écran vous demandera de confirmer que vous souhaitez bien télécharger ces éléments-là. Cliquez sur Accept All puis sur Install pour continuer, comme à la figure suivante.

Cliquez sur « Accept All » pour accepter toutes les licences d'un coup
Cliquez sur « Accept All » pour accepter toutes les licences d'un coup

Si vous installez tous ces paquets, vous aurez besoin de 1,8 Go sur le disque de destination. Eh oui, le téléchargement prendra un peu de temps.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Le Java Development Kit L'IDE Eclipse

L'IDE Eclipse

Le SDK d'Android L'émulateur de téléphone : Android Virtual Device

L'IDE Eclipse

Un IDE est un logiciel dont l'objectif est de faciliter le développement, généralement pour un ensemble restreint de langages. Il contient un certain nombre d'outils, dont au moins un éditeur de texte - souvent étendu pour avoir des fonctionnalités avancées telles que l'auto-complétion ou la génération automatique de code - des outils de compilation et un débogueur. Dans le cas du développement Android, un IDE est très pratique pour ceux qui souhaitent ne pas avoir à utiliser les lignes de commande.

J'ai choisi pour ce tutoriel de me baser sur Eclipse : tout simplement parce qu'il est gratuit, puissant et recommandé par Google dans la documentation officielle d'Android. Vous pouvez aussi opter pour d'autres IDE compétents tels que IntelliJ IDEA, NetBeans avec une extension ou encore MoSync.

Le tutoriel reste en majorité valide quel que soit l'IDE que vous sélectionnez, mais vous aurez à explorer vous-mêmes les outils proposés, puisque je ne présenterai ici que ceux d'Eclipse.

Cliquez ici pour choisir une version d'Eclipse à télécharger. J'ai personnellement opté pour Eclipse IDE for Java Developers qui est le meilleur compromis entre contenu suffisant et taille du fichier à télécharger. Les autres versions utilisables sont Eclipse IDE for Java EE Developers (je ne vous le recommande pas pour notre cours, il pèse plus lourd et on n'utilisera absolument aucune fonctionnalité de Java EE) et Eclipse Classic (qui lui aussi intègre des modules que nous n'utiliserons pas).

Il vous faudra 110 Mo sur le disque pour installer la version d'Eclipse que j'ai choisie.

Maintenant qu'Eclipse est installé, lancez-le. Au premier démarrage, il vous demandera de définir un Workspace, un espace de travail, c'est-à-dire l'endroit où il créera les fichiers indispensables contenant les informations sur les projets. Sélectionnez l'emplacement que vous souhaitez.

Vous avez maintenant un Eclipse prêt à fonctionner… mais pas pour le développement pour Android ! Pour cela, on va télécharger le plug-in (l'extension) Android Development Tools (que j'appellerai désormais ADT). Il vous aidera à créer des projets pour Android avec les fichiers de base, mais aussi à tester, à déboguer et à exporter votre projet au format APK (pour pouvoir publier vos applications).

Allez dans Help puis dans Install New Softwares… (installer de nouveaux programmes). Au premier encart intitulé Work with:, cliquez sur le bouton Add… qui se situe juste à côté. On va définir où télécharger ce nouveau programme. Dans l'encart Name écrivez par exemple ADT et, dans location, mettez cette adresse https://dl-ssl.google.com/android/eclipse/, comme à la figure suivante. Avec cette adresse, on indique à Eclipse qu'on désire télécharger de nouveaux logiciels qui se trouvent à cet emplacement, afin qu'Eclipse nous propose de les télécharger. Cliquez ensuite sur OK.

On ajoute un répertoire distant d'où seront téléchargés les sources de l'ADT
On ajoute un répertoire distant d'où seront téléchargés les sources de l'ADT

Si cette manipulation ne fonctionne pas, essayez avec cette adresse suivante : http://dl-ssl.google.com/android/eclipse/ (même chose mais sans le « s » à « http »).

Si vous rencontrez toujours une erreur, alors il va falloir télécharger l'ADT manuellement. Rendez-vous sur la documentation officielle, puis cliquez sur le lien qui se trouve dans la colonne Package du tableau afin de télécharger une archive qui contient l'ADT, comme à la figure suivante.

Téléchargez l'archive
Téléchargez l'archive

Si le nom n'est pas exactement le même, ce n'est pas grave, il s'agit du même programme mais à une version différente. Ne désarchivez pas le fichier, cela ne vous mènerait à rien.

Une fois le téléchargement terminé, retournez dans la fenêtre que je vous avais demandé d'ouvrir dans Eclipse, puis cliquez sur Archives. Sélectionnez le fichier que vous venez de télécharger, entrez un nom dans le champ Name: et là seulement cliquez sur OK. Le reste est identique à la procédure normale.

Vous devrez patienter tant que sera écrit Pending…, puisque c'est ainsi qu'Eclipse indique qu'il cherche les fichiers disponibles à l'emplacement que vous avez précisé. Dès que Developer Tools apparaît à la place de Pending…, développez le menu en cliquant sur le triangle à gauche du carré de sélection et analysons les éléments proposés, comme sur la figure suivante.

Il nous faut télécharger au moins ces modules
Il nous faut télécharger au moins ces modules

Il existe d'autres modules que nous n'utiliserons pas pendant ce cours :

Sélectionnez tout et cliquez sur Next, à nouveau sur Next à l'écran suivant puis finalement sur « I accept the terms of the license agreements » après avoir lu les différents contrats. Cliquez enfin sur Finish.

L'ordinateur téléchargera puis installera les composants. Une fenêtre s'affichera pour vous dire qu'il n'arrive pas à savoir d'où viennent les programmes téléchargés et par conséquent qu'il n'est pas sûr qu'ils soient fonctionnels et qu'ils ne soient pas dangereux. Cependant, nous savons qu'ils sont sûrs et fonctionnels, alors cliquez sur OK. ;)

Une fois l'installation et le téléchargement terminés, il vous proposera de redémarrer l'application. C'est presque fini, mais il nous reste quand même une dernière étape à accomplir.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Le SDK d'Android L'émulateur de téléphone : Android Virtual Device

L'émulateur de téléphone : Android Virtual Device

L'IDE Eclipse Test et configuration

L'émulateur de téléphone : Android Virtual Device

L'Android Virtual Device, aussi appelé AVD, est un émulateur de terminal sous Android, c'est-à-dire que c'est un logiciel qui fait croire à votre ordinateur qu'il est un appareil sous Android. C'est la raison pour laquelle vous n'avez pas besoin d'un périphérique sous Android pour développer et tester la plupart de vos applications ! En effet, une application qui affiche un calendrier par exemple peut très bien se tester dans un émulateur, mais une application qui exploite le GPS doit être éprouvée sur le terrain pour que l'on soit certain de son comportement.

Lancez à nouveau Eclipse si vous l'avez fermé. Au cas où vous auriez encore l'écran d'accueil, cliquez sur la croix en haut à gauche pour le fermer. Repérez tout d'abord où se trouve la barre d'outils, visible à la figure suivante.

La barre d'outils d'Eclipse
La barre d'outils d'Eclipse

Vous voyez le couple d'icônes représenté à la figure suivante ? Celle de gauche permet d'ouvrir les outils du SDK et celle de droite permet d'ouvrir l'interface de gestion d'AVD. Cliquez dessus puis sur New… pour ajouter un nouvel AVD.

Les deux icônes réservées au SDK et à l'AVD
Les deux icônes réservées au SDK et à l'AVD

Une fenêtre s'ouvre (voir figure suivante), vous proposant de créer votre propre émulateur ! Bien que ce soit facultatif, je vous conseille d'indiquer un nom dans Name, histoire de pouvoir différencier vos AVD. Pour ma part, j'ai choisi « Site_Du_Zero_2_1 ». Notez que certains caractères comme les caractères accentués et les espaces ne sont pas autorisés. Dans Target, choisissez Android 2.1 - API Level 7, puisque j'ai décidé que nous ferons nos applications avec la version 7 de l'API et sans le Google API. Laissez les autres options à leur valeur par défaut, nous y reviendrons plus tard quand nous confectionnerons d'autres AVD. Cliquez enfin sur Create AVD et vous aurez une machine prête à l'emploi !

Créez votre propre émulateur
Créez votre propre émulateur

Si vous utilisez Windows et que votre nom de session contient un caractère spécial, par exemple un accent, alors Eclipse vous enverra paître en déclarant qu'il ne trouve pas le fichier de configuration de l'AVD. Par exemple, un de nos lecteur avait une session qui s'appelait « Jérémie » et avait ce problème. Heureusement, il existe une solution à ce problème. Si vous utilisez Windows 7 ou Windows Vista, appuyez en même temps sur la touche Windows et sur la touche R. Si vous êtes sous Windows XP, il va falloir cliquer sur Démarrer puis sur Exécuter.

Dans la nouvelle fenêtre qui s'ouvre, tapez « cmd » puis appuyez sur la touche Entrée de votre clavier. Une nouvelle fenêtre va s'ouvrir, elle permet de manipuler Windows en ligne de commande. Tapez cd .. puis Entrée. Maintenant, tapez dir /x. Cette commande permet de lister tous les répertoires et fichiers présents dans le répertoire actuel et aussi d'afficher le nom abrégé de chaque fichier ou répertoire. Par exemple, pour la session Administrator on obtient le nom abrégé ADMINI~1, comme le montre la figure suivante.

La valeur à gauche est le nom réduit, alors que celle de droite est le nom entier
La valeur à gauche est le nom réduit, alors que celle de droite est le nom entier

Maintenant, repérez le nom réduit qui correspond à votre propre session, puis dirigez-vous vers le fichier X:\Utilisateurs\<Votre session>\.android\avd\<nom_de_votre_avd>.ini et ouvrez ce fichier. Il devrait ressembler au code suivant :

target=android-7
path=X:\Users\<Votre session>\.android\avd\SDZ_2.1.avd

S'il n'y a pas de retour à la ligne entre target=android-7 et path=X:\Users\<Votre session>\.android\avd\SDZ_2.1.avd, c'est que vous n'utilisez pas un bon éditeur de texte. Utilisez le lien que j'ai donné ci-dessus.
Enfin, il vous suffit de remplacer <Votre session> par le nom abrégé de la session que nous avions trouvé précédemment. Par exemple pour le cas de la session Administrator, je change :

target=android-7
path=C:\Users\Administrator\.android\avd\SDZ_2.1.avd

en

target=android-7
path=C:\Users\ADMINI~1\.android\avd\SDZ_2.1.avd
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

L'IDE Eclipse Test et configuration

Test et configuration

L'émulateur de téléphone : Android Virtual Device Configuration du vrai terminal

Test et configuration

Bien, maintenant que vous avez créé un AVD, on va pouvoir vérifier qu'il fonctionne bien.

Si vous êtes sortis du gestionnaire Android, retournez-y en cliquant sur l'icône Bugdroid, comme nous l'avons fait auparavant. Vous aurez quelque chose de plus ou moins similaire à la figure suivante.

La liste des émulateurs que connaît votre AVD Manager
La liste des émulateurs que connaît votre AVD Manager

Vous y voyez l'AVD que nous venons tout juste de créer. Cliquez dessus pour déverrouiller le menu de droite. Comme je n'ai pas l'intention de vraiment détailler ces options moi-même, je vais rapidement vous expliquer à quoi elles correspondent pour que vous sachiez les utiliser en cas de besoin. Les options du menu de droite sont les suivantes :

Cliquons donc sur le bouton Start… et une nouvelle fenêtre se lance, qui devrait ressembler peu ou prou à la figure suivante.

Les différentes options pour l'exécution de cet AVD
Les différentes options pour l'exécution de cet AVD

Laissez les options vierges pour l'instant, on n'a absolument pas besoin de ce genre de détails ! Cliquez juste sur Launch. En théorie, une nouvelle fenêtre se lancera et passera par deux écrans de chargement successifs. Enfin, votre terminal se lancera. Voici la liste des boutons qui se trouvent dans le menu à droite et à quoi ils servent :

Mais ! L'émulateur n'est pas à l'heure ! En plus c'est de l'anglais !

La maîtrise de l'anglais devient vite indispensable dans le monde de l'informatique… ! Ensuite, les machines que vous achetez dans le commerce sont déjà configurées pour le pays dans lequel vous les avez acquises, et, comme ce n'est pas une machine réelle ici, Android a juste choisi les options par défaut. Nous allons devoir configurer la machine pour qu'elle réponde à nos exigences. Vous pouvez manipuler la partie de gauche avec votre souris, ce qui simulera le tactile. Faites glisser le verrou sur la gauche pour déverrouiller la machine. Vous vous retrouverez sur l'accueil. Cliquez sur le bouton MENU à droite pour ouvrir un petit menu en bas de l'écran de l'émulateur, comme à la figure suivante.

Le menu est ouvert
Le menu est ouvert

Cliquez sur l'option Settings pour ouvrir le menu de configuration d'Android. Vous pouvez y naviguer soit en faisant glisser avec la souris (un clic, puis en laissant appuyé on dirige le curseur vers le haut ou vers le bas), soit avec la molette de votre souris. Si par mégarde vous entrez dans un menu non désiré, appuyez sur le bouton Retour présenté précédemment (une flèche qui effectue un demi-tour).

Cliquez sur l'option Language & keyboard (voir figure suivante) ; c'est le menu qui vous permet de choisir dans quelle langue utiliser le terminal et quel type de clavier utiliser (par exemple, vous avez certainement un clavier dont les premières lettres forment le mot AZERTY, c'est ce qu'on s'appelle un clavier AZERTY. Oui, oui, les informaticiens ont beaucoup d'imagination ;) ).

On va sélectionner « Language & keyboard »
On va sélectionner « Language & keyboard »

Puis, vous allez cliquer sur Select locale. Dans le prochain menu, il vous suffit de sélectionner la langue dans laquelle vous préférez utiliser Android. J'ai personnellement choisi Français (France). Voilà, un problème de réglé ! Maintenant j'utiliserai les noms français des menus pour vous orienter. Pour revenir en arrière, il faut appuyer sur le bouton Retour du menu de droite.

Votre prochaine mission, si vous l'acceptez, sera de changer l'heure pour qu'elle s'adapte à la zone dans laquelle vous vous trouvez, et ce, par vous-mêmes. En France, nous vivons dans la zone GMT + 1. À l'heure où j'écris ces lignes, nous sommes en heure d'été, il y a donc une heure encore à rajouter. Ainsi, si vous êtes en France, en Belgique ou au Luxembourg et en heure d'été, vous devez sélectionner une zone à GMT + 2. Sinon GMT + 1 pour l'heure d'hiver. Cliquez d'abord sur Date & heure, désélectionnez Automatique, puis cliquez sur Définir fuseau horaire et sélectionnez le fuseau qui vous concerne.

Très bien, votre terminal est presque complètement configuré, nous allons juste activer les options pour le rendre apte à la programmation. Toujours dans le menu de configuration, allez chercher Applications et cliquez dessus. Cliquez ensuite sur Développement et vérifiez que tout est bien activé comme à la figure suivante.

Ce menu vous permet de développer pour Android
Ce menu vous permet de développer pour Android

Si vous comptez faire immédiatement le prochain chapitre qui vous permettra de commencer — enfin — le développement, ne quittez pas la machine. Dans le cas contraire, il vous suffit de rester appuyé sur le bouton pour arrêter l'émulateur puis de vous laisser guider.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

L'émulateur de téléphone : Android Virtual Device Configuration du vrai terminal

Configuration du vrai terminal

Test et configuration Votre première application

Configuration du vrai terminal

Maintenant on va s'occuper de notre vrai outil, si vous en avez un !

Configuration du terminal

Tout naturellement, vous devez configurer votre téléphone comme on a configuré l'émulateur. En plus, vous devez indiquer que vous acceptez les applications qui ne proviennent pas du Market dans Configuration > Application > Source inconnue.

Pour les utilisateurs de Windows

Tout d'abord, vous devez télécharger les drivers adaptés à votre terminal. Je peux vous donner la marche à suivre pour certains terminaux, mais pas pour tous… En effet, chaque appareil a besoin de drivers adaptés, et ce sera donc à vous de les télécharger, souvent sur le site du constructeur. Cependant, il existe des pilotes génériques qui peuvent fonctionner sur certains appareils. En suivant ma démarche, ils sont déjà téléchargés, mais rien n'assure qu'ils fonctionnent pour votre appareil. En partant du répertoire où vous avez installé le SDK, on peut les trouver à cet emplacement : \android-sdk\extras\google\usb_driver. Vous trouverez l'emplacement des pilotes à télécharger pour toutes les marques dans le tableau qui se trouve sur cette page.

Pour les utilisateurs de Mac

À la bonne heure, vous n'avez absolument rien à faire de spécial pour que tout fonctionne !

Pour les utilisateurs de Linux

La gestion des drivers USB de Linux étant beaucoup moins chaotique que celle de Windows, vous n'avez pas à télécharger de drivers. Il y a cependant une petite démarche à accomplir. On va en effet devoir ajouter au gestionnaire de périphériques une règle spécifique pour chaque appareil qu'on voudra relier. Je vais vous décrire cette démarche pour les utilisateurs d'Ubuntu :

  1. On va d'abord créer le fichier qui contiendra ces règles à l'aide de la commande sudo touch /etc/udev/rules.d/51-android.rules. touch est la commande qui permet de créer un fichier, et udev est l'emplacement des fichiers du gestionnaire de périphériques. udev conserve ses règles dans le répertoire ./rules.d.

  2. Le système vous demandera de vous identifier en tant qu'utilisateur root.

  3. Puis on va modifier les autorisations sur le fichier afin d'autoriser la lecture et l'écriture à tous les utilisateurs chmod a+rw /etc/udev/rules.d/51-android.rules.

  4. Enfin, il faut rajouter les règles dans notre fichier nouvellement créé. Pour cela, on va ajouter une instruction qui ressemblera à : SUBSYSTEM=="usb", ATTR{idVendor}=="XXXX", MODE="0666", GROUP="plugdev". Attention, on n'écrira pas exactement cette phrase.

Est-il possible d'avoir une explication ?

SUBSYSTEM est le mode de connexion entre le périphérique et votre ordinateur, dans notre cas on utilisera une interface USB. MODE détermine qui peut faire quoi sur votre périphérique, et la valeur « 0666 » indique que tous les utilisateurs pourront lire des informations mais aussi en écrire. GROUP décrit tout simplement quel groupe UNIX possède le périphérique. Enfin, ATTR{idVendor est la ligne qu'il vous faudra modifier en fonction du constructeur de votre périphérique. On peut trouver quelle valeur indiquer sur la documentation. Par exemple pour mon HTC Desire, j'indique la ligne suivante :

SUBSYSTEM=="usb", ATTR{idVendor}=="0bb4", MODE="0666", GROUP="plugdev"

… ce qui entraîne que je tape dans la console :

echo "SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0bb4\", MODE=\"0666\", GROUP=\"plugdev\"" >> /etc/udev/rules.d/51-android.rules

Si cette configuration ne vous correspond pas, je vous invite à lire la documentation de udev afin de créer votre propre règle.

Et après ?

Ben rien ! :p La magie de l'informatique opère, reliez votre terminal à l'ordinateur et tout devrait se faire de manière automatique (tout du moins sous Windows 7, désolé pour les autres !).

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Test et configuration Votre première application

Votre première application

Configuration du vrai terminal Activité et vue

Ce chapitre est très important. Il vous permettra d'enfin mettre la main à la pâte, mais surtout on abordera la notion de cycle d'une activité, qui est la base d'un programme pour Android. Si pour vous un programme en Java débute forcément par un main, vous risquez d'être surpris. :-°

On va tout d'abord voir ce qu'on appelle des activités et comment les manipuler. Sachant que la majorité de vos applications (si ce n'est toutes) contiendront plusieurs activités, il est indispensable que vous maîtrisiez ce concept ! Nous verrons aussi ce que sont les vues et nous créerons enfin notre premier projet — le premier d'une grande série — qui n'est pas, de manière assez surprenante, un « Hello World! ». Enfin presque ! ;)

Activité et vue

Votre première application Création d'un projet

Activité et vue

Qu'est-ce qu'une activité ?

Si vous observez un peu l'architecture de la majorité des applications Android, vous remarquerez une construction toujours à peu près similaire. Prenons par exemple l'application du Play Store. Vous avez plusieurs fenêtres à l'intérieur même de cette application : si vous effectuez une recherche, une liste de résultats s'affichera dans une première fenêtre et si vous cliquez sur un résultat, une nouvelle fenêtre s'ouvre pour vous afficher la page de présentation de l'application sélectionnée. Au final, on remarque qu'une application est un assemblage de fenêtres entre lesquelles il est possible de naviguer.

Ces différentes fenêtres sont appelées des activités. Un moyen efficace de différencier des activités est de comparer leur interface graphique : si elles sont radicalement différentes, c'est qu'il s'agit d'activités différentes. De plus, comme une activité remplit tout l'écran, votre application ne peut en afficher qu'une à la fois. La figure suivante illustre ce concept.

Cliquer sur un élément de la liste dans la première activité permet d'ouvrir les détails dans une seconde activité
Cliquer sur un élément de la liste dans la première activité permet d'ouvrir les détails dans une seconde activité

Je me permets de faire un petit aparté pour vous rappeler ce qu'est une interface graphique : il s'agit d'un ensemble d’éléments visuels avec lesquels peuvent interagir les utilisateurs, ou qui leur fournissent des informations. Tout ça pour vous dire qu'une activité est un support sur lequel nous allons greffer une interface graphique. Cependant, ce n'est pas le rôle de l'activité que de créer et de disposer les éléments graphiques, elle n'est que l’échafaudage sur lequel vont s'insérer les objets graphiques.

De plus, une activité contient des informations sur l'état actuel de l'application : ces informations s'appellent le context. Ce context constitue un lien avec le système Android ainsi que les autres activités de l'application, comme le montre la figure suivante.

Une activité est constituée du contexte de l'application et d'une seule et unique interface graphique
Une activité est constituée du contexte de l'application et d'une seule et unique interface graphique

Comme il est plus aisé de comprendre à l'aide d'exemples, imaginez que vous naviguiez sur le Site du Zéro avec votre téléphone, le tout en écoutant de la musique sur ce même téléphone. Il se passe deux choses dans votre système :

On a ainsi au moins deux applications lancées en même temps ; cependant, le navigateur affiche une activité alors que le lecteur audio n'en affiche pas.

États d'une activité

Si un utilisateur reçoit un appel, il devient plus important qu'il puisse y répondre que d'émettre la chanson que votre application diffuse. Pour pouvoir toujours répondre à ce besoin, les développeurs d'Android ont eu recours à un système particulier :

Pour être plus précis, quand une application se lance, elle se met tout en haut de ce qu'on appelle la pile d'activités.

Fonctionnement de la pile d'activités
Fonctionnement de la pile d'activités

L'activité que voit l'utilisateur est celle qui se trouve au-dessus de la pile. Ainsi, lorsqu'un appel arrive, il se place au sommet de la pile et c'est lui qui s'affiche à la place de votre application, qui n'est plus qu'à la deuxième place. Votre activité ne reviendra qu'à partir du moment où toutes les activités qui se trouvent au-dessus d'elle seront arrêtées et sorties de la pile. On retrouve ainsi le principe expliqué précédemment, on ne peut avoir qu'une application visible en même temps sur le terminal, et ce qui est visible est l'interface graphique de l'activité qui se trouve au sommet de la pile.

Une activité peut se trouver dans trois états qui se différencient surtout par leur visibilité :

État

Visibilité

Description

Active
active » ou « running »)

L'activité est visible en totalité.

Elle est sur le dessus de la pile, c'est ce que l'utilisateur consulte en ce moment même et il peut l'utiliser dans son intégralité.
C'est cette application qui a le focus, c'est-à-dire que l'utilisateur agit directement sur l'application.

Suspendue
paused »)

L'activité est partiellement visible à l'écran.
C'est le cas quand vous recevez un SMS et qu'une fenêtre semi-transparente se pose devant votre activité pour afficher le contenu du message et vous permettre d'y répondre par exemple.

Ce n'est pas sur cette activité qu'agit l'utilisateur.
L'application n'a plus le focus, c'est l'application sus-jacente qui l'a. Pour que notre application récupère le focus, l'utilisateur devra se débarrasser de l'application qui l'obstrue, puis l'utilisateur pourra à nouveau interagir avec.
Si le système a besoin de mémoire, il peut très bien tuer l'application (cette affirmation n'est plus vraie si vous utilisez un SDK avec l'API 11 minimum).

Arrêtée
stopped »)

L'activité est tout simplement oblitérée par une autre activité, on ne peut plus la voir du tout.

L'application n'a évidemment plus le focus, et puisque l'utilisateur ne peut pas la voir, il ne peut pas agir dessus.
Le système retient son état pour pouvoir reprendre, mais il peut arriver que le système tue votre application pour libérer de la mémoire système.

Mais j'ai pourtant déjà vu des systèmes Android avec deux applications visibles en même temps !

Ah oui, c'est possible. Mais il s'agit d'un artifice, il n'y a vraiment qu'une application qui est active. Pour faciliter votre compréhension, je vous conseille d'oublier ces systèmes.

Cycle de vie d'une activité

Une activité n'a pas de contrôle direct sur son propre état (et par conséquent vous non plus en tant que programmeur), il s'agit plutôt d'un cycle rythmé par les interactions avec le système et d'autres applications. Voici un schéma qui présente ce que l'on appelle le cycle de vie d'une activité, c'est-à-dire qu'il indique les étapes que va traverser notre activité pendant sa vie, de sa naissance à sa mort. Vous verrez que chaque étape du cycle est représentée par une méthode. Nous verrons comment utiliser ces méthodes en temps voulu.

Cycle de vie d'une activité
Cycle de vie d'une activité

Pour rappel, un package est un répertoire qui permet d'organiser notre code source, un récipient dans lequel nous allons mettre nos classes de façon à pouvoir trier votre code et différencier des classes qui auraient le même nom. Concrètement, supposez que vous ayez à créer deux classes X — qui auraient deux utilisations différentes, bien sûr. Vous vous rendez bien compte que vous seriez dans l'incapacité totale de différencier les deux classes si vous deviez instancier un objet de l'une des deux classes X, et Java vous houspillera en déclarant qu'il ne peut pas savoir à quelle classe vous faites référence. C'est exactement comme avoir deux fichiers avec le même nom et la même extension dans un même répertoire : c'est impossible car c'est incohérent.

Pour contrer ce type de désagrément, on organise les classes à l'aide d'une hiérarchie. Si je reprends mon exemple des deux classes X, je peux les placer dans deux packages différents Y et Z par exemple, de façon à ce que vous puissiez préciser dans quel package se trouve la classe X sollicitée. On utilisera la syntaxe Y.X pour la classe X qui se trouve dans le package Y et Z.X pour la classe X qui se trouve dans le package Z. Dans le cas un peu farfelu du code source d'un navigateur internet, on pourrait trouver les packages Web.Affichage.Image, Web.Affichage.Video et Web.Telechargement.

Les vues (que nos amis anglais appellent view), sont ces fameux composants qui viendront se greffer sur notre échafaudage, il s'agit de l'unité de base de l'interface graphique. Leur rôle est de fournir du contenu visuel avec lequel il est éventuellement possible d'interagir. À l'instar de l'interface graphique en Java, il est possible de disposer les vues à l'aide de conteneurs, nous verrons comment plus tard.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Votre première application Création d'un projet

Création d'un projet

Activité et vue Un non-Hello world!

Création d'un projet

Une fois Eclipse démarré, repérez les icônes visibles à la figure suivante et cliquez sur le bouton le plus à gauche de la section consacrée à la gestion de projets Android.

Ces trois boutons permettent de gérer des projets Android
Ces trois boutons permettent de gérer des projets Android

La fenêtre visible à la figure suivante s'ouvre ; voyons ensemble ce qu'elle contient :

Création d'un nouveau projet
Création d'un nouveau projet

Tous ces champs nous permettent de définir certaines caractéristiques de notre projet :

Ces trois champs sont indispensables, vous devrez donc tous les renseigner.

Vous vous retrouvez ensuite confronté à deux listes défilantes :

Enfin, cette fenêtre se conclut par trois cases à cocher :

Pour passer à la page suivante, cliquez sur Next. Si vous avez cliqué sur Create custom launcher icon, alors c'est la fenêtre visible à la figure suivante qui s'affichera.

Cet outil facilite la création d'icônes
Cet outil facilite la création d'icônes

Je vous invite à jouer avec les boutons pour découvrir toutes les fonctionnalités de cet outil. Cliquez sur Next une fois obtenu un résultat satisfaisant et vous retrouverez la page que vous auriez eue si vous n'aviez pas cliqué sur Create custom launcher icon (voir figure suivante).

Vous pouvez ici choisir une mise en page standard
Vous pouvez ici choisir une mise en page standard

Il s'agit ici d'un outil qui vous demande si vous voulez qu'Eclipse crée une activité pour vous, et si oui à partir de quelle mise en page. On va déclarer qu'on veut qu'il crée une activité, cliquez sur la case à gauche de Create Activity, mais on va sélectionner BlankActivity parce qu'on veut rester maître de notre mise en page. Cliquez à nouveau sur Next.

Dans la fenêtre représentée à la figure suivante, il faut déclarer certaines informations relatives à notre nouvelle activité :

Permet de créer une première activité facilement
Permet de créer une première activité facilement

Ici encore une fois, on fait face à cinq champs à renseigner :

Pour finaliser la création, cliquez sur Finish.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Activité et vue Un non-Hello world!

Un non-Hello world!

Création d'un projet Lancement de l'application

Un non-Hello world!

Vous trouverez les fichiers créés dans le Package Explorer (voir figure suivante).

Le Package Explorer permet de naviguer entre vos projets
Le Package Explorer permet de naviguer entre vos projets

On y trouve notre premier grand répertoire src/, celui qui contiendra tous les fichiers sources .java. Ouvrez le seul fichier qui s'y trouve, chez moi MainActivity.java (en double cliquant dessus). Vous devriez avoir un contenu plus ou moins similaire à celui-ci :

package sdz.chapitreUn.premiere.application;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;

public class MainActivity extends Activity {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
  }
}

Ah ! On reconnaît certains termes que je viens tout juste d'expliquer ! Je vais prendre toutes les lignes une par une, histoire d'être certain de ne déstabiliser personne.

package sdz.chapitreUn.premiere.application;

Là, on déclare que notre programme se situe dans le package sdz.chapitreUn.premiere.application, comme expliqué précédemment. Si on veut faire référence à notre application, il faudra faire référence à ce package.

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;

On importe des classes qui se trouvent dans des packages différents : les classes Activity, Bundle, Menu et MenuItem qui se trouvent dans le même package, puis NavUtils. Chez moi, deux de ces packages sont inutiles car inutilisés dans le code, comme le montre la figure suivante.

Eclipse souligne les importations inutiles en jaune
Eclipse souligne les importations inutiles en jaune

Il existe trois manières de résoudre ces problèmes :

public class MainActivity extends Activity {
  //…
}

On déclare ici une nouvelle classe, MainActivity, et on la fait dériver de Activity, puisqu'il s'agit d'une activité.

@Override
public void onCreate(Bundle savedInstanceState) {
  //…
}

Le petit @Override permet d'indiquer que l'on va redéfinir une méthode qui existait auparavant dans la classe parente, ce qui est logique puisque vous saviez déjà qu'une activité avait une méthode void onCreate() et que notre classe héritait de Activity.

Cette méthode est la première qui est lancée au démarrage d'une application, mais elle est aussi appelée après qu'une application a été tuée par le système en manque de mémoire ! C'est à cela que sert le paramètre de type Bundle :

Dans cette méthode, vous devez définir ce qui doit être créé à chaque démarrage, en particulier l'interface graphique.

super.onCreate(savedInstanceState);

L'instruction super signifie qu'on fait appel à une méthode ou un attribut qui appartient à la superclasse de la méthode actuelle, autrement dit la classe juste au-dessus dans la hiérarchie de l'héritage — la classe parente, c'est-à-dire la classe Activity.

Ainsi, super.onCreate fait appel au onCreate de la classe Activity, mais pas au onCreate de MainActivity. Il gère bien entendu le cas où le Bundle est null. Cette instruction est obligatoire.

L'instruction suivante :

setContentView(R.layout.activity_main);

sera expliquée dans le prochain chapitre.

En revanche, l'instruction suivante :

@Override
public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.activity_main, menu);
  return true;
}

… sera expliquée bien, bien plus tard.

En attendant, vous pouvez remplacer le contenu du fichier par celui-ci :

//N'oubliez pas de déclarer le bon package dans lequel se trouve le fichier !

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {
  private TextView coucou = null;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        
    coucou = new TextView(this);
    coucou.setText("Bonjour, vous me devez 1 000 000€.");
    setContentView(coucou);
  }

}

Nous avons ajouté un attribut de classe que j'ai appelé coucou. Cet attribut est de type TextView, j'imagine que le nom est déjà assez explicite. :D Il s'agit d'une vue (View)… qui représente un texte (Text). J'ai changé le texte qu'affichera cette vue avec la méthode void setText(String texte).

La méthode void setContentView (View vue) permet d'indiquer l'interface graphique de notre activité. Si nous lui donnons un TextView, alors l'interface graphique affichera ce TextView et rien d'autre.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Création d'un projet Lancement de l'application

Lancement de l'application

Un non-Hello world! Les ressources

Lancement de l'application

Souvenez-vous, je vous ai dit précédemment qu'il était préférable de ne pas fermer l'AVD, celui-ci étant long à se lancer. Si vous l'avez fermé, ce n'est pas grave, il s'ouvrira tout seul. Mais ce sera loooong. :(

Pour lancer notre application, regardez la barre d'outils d'Eclipse et cherchez l'encart visible à la figure suivante.

Les outils pour exécuter votre code
Les outils pour exécuter votre code

Il vous suffit de cliquer sur le deuxième bouton (celui qui ressemble au symbole « play »). Une fenêtre s'ouvre (voir figure suivante) pour vous demander comment exécuter l'application. Sélectionnez Android Application.

Sélectionnez « Android Application »
Sélectionnez « Android Application »

Si vous avez plusieurs terminaux, l'écran visible à la figure suivante s'affichera (sauf si vous n'avez pas de terminal connecté).

Choisissez le terminal de test
Choisissez le terminal de test

On vous demande sur quel terminal vous voulez lancer votre application. Vous pouvez valider en cliquant sur OK. Le résultat devrait s'afficher sur votre terminal ou dans l'émulateur (voir figure suivante). Génial ! L'utilisateur (naïf) vous doit 1 000 000 € !

Les couleurs peuvent être différentes chez vous, ce n'est pas grave
Les couleurs peuvent être différentes chez vous, ce n'est pas grave

J'ai une erreur ! Apparemment liée au(x) @Override, le code ne fonctionne pas !

Le problème est que vous utilisez le JDK 7, alors que j'utilise le JDK 6 comme je l'ai indiqué dans le chapitre précédent. Ce n'est pas grave, il vous suffit de supprimer tous les @Override et le code fonctionnera normalement.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Un non-Hello world! Les ressources

Les ressources

Lancement de l'application Le format XML

Je vous ai déjà présenté le répertoire src/ qui contient toutes les sources de votre programme. On va maintenant s'intéresser à un autre grand répertoire : res/. Vous l'aurez compris, c'est dans ce répertoire que sont conservées les ressources, autrement dit les éléments qui s'afficheront à l'écran ou avec lesquels l'utilisateur pourra interagir.

Android est destiné à être utilisé sur un très grand nombre de supports différents, et il faut par conséquent s'adapter à ces supports. Imaginons qu'une application ait à afficher une image. Si on prend une petite image, il faut l’agrandir pour qu'elle n'ait pas une dimension ridicule sur un grand écran. Mais en faisant cela, l'image perdra en qualité. Une solution serait donc d'avoir une image pour les petits écrans, une pour les écrans moyens et une pour les grands écrans. C'est ce genre de précautions qu'il faut prendre quand on veut développer pour les appareils mobiles.

Un des moyens d'adapter nos applications à tous les terminaux est d'utiliser les ressources. Les ressources sont des fichiers organisés d'une manière particulière de façon à ce qu'Android sache quelle ressource utiliser pour s'adapter au matériel sur lequel s'exécute l’application. Comme je l'ai dit précédemment, adapter nos applications à tous les types de terminaux est indispensable. Cette adaptation passe par la maîtrise des ressources.

Pour déclarer des ressources, on passe très souvent par le format XML, c'est pourquoi un point sur ce langage est nécessaire.

Le format XML

Les ressources Les différents types de ressources

Le format XML

Les langages de balisage

Le XML est un langage de balisage un peu comme le HTML — le HTML est d'ailleurs indirectement un dérivé du XML. Le principe d'un langage de programmation (Java, C++, etc.) est d'effectuer des calculs, puis éventuellement de mettre en forme le résultat de ces calculs dans une interface graphique. À l'opposé, un langage de balisage (XML, donc) n'effectue ni calcul, ni affichage, mais se contente de mettre en forme des informations. Concrètement, un langage de balisage est une syntaxe à respecter, de façon à ce qu'on sache de manière exacte la structuration d'un fichier. Et si on connaît l'architecture d'un fichier, alors il est très facile de retrouver l'emplacement des informations contenues dans ce fichier et de pouvoir les exploiter. Ainsi, il est possible de développer un programme appelé interpréteur qui récupérera les données d'un fichier (structuré à l'aide d'un langage de balisage).

Par exemple pour le HTML, c'est un navigateur qui interprète le code afin de donner un sens aux instructions ; si vous lisez un document HTML sans interpréteur, vous ne verrez que les sources, pas l'interprétation des balises.

Un exemple pratique

Imaginons un langage de balisage très simple, que j'utilise pour stocker mes contacts téléphoniques :

Anaïs Romain Thomas Xavier

Ce langage est très simple : les prénoms de mes contacts sont séparés par une espace. Ainsi, quand je demanderai à mon interpréteur de lire le fichier, il saura que j'ai 4 contacts parce que les prénoms sont séparés par des espaces. Il lit une suite de caractères et dès qu'il tombe sur une espace, il sait qu'on va passer à un autre prénom.

On va maintenant rendre les choses plus complexes pour introduire les numéros de téléphone :

Anaïs : 1111111111
Romain: 2222222222
Thomas: 3333333333
Xavier: 4444444444

Là, l'interpréteur sait que pour chaque ligne, la première suite de caractères correspond à un prénom qui se termine par un deux-points, puis on trouve le numéro de téléphone qui se termine par un retour à la ligne. Et, si j'ai bien codé mon interpréteur, il sait que le premier prénom est « Anaïs » sans prendre l'espace à la fin, puisque ce n'est pas un caractère qui rentre dans la composition d'un prénom.

Si j'avais écrit mon fichier sans syntaxe particulière à respecter, alors il m'aurait été impossible de développer un interpréteur qui puisse retrouver les informations.

La syntaxe XML

Comme pour le format HTML, un fichier XML débute par une déclaration qui permet d'indiquer qu'on se trouve bien dans un fichier XML.

<?xml version="1.0" encoding="utf-8"?>

Cette ligne permet d'indiquer que :

Je vais maintenant vous détailler un fichier XML :

<?xml version="1.0" encoding="utf-8"?>
<bibliotheque>
  <livre style="fantaisie">
    <auteur>George R. R. MARTIN</auteur>
    <titre>A Game Of Thrones</titre>
    <langue>klingon</langue>
    <prix>10.17</prix>
  </livre>
  <livre style="aventure">
    <auteur>Alain Damasio</auteur>
    <titre>La Horde Du Contrevent</titre>
    <prix devise="euro">9.40</prix>
    <recommandation note="20"/>
  </livre>
</bibliotheque>

L'élément de base du format XML est la balise. Elle commence par un chevron ouvrant < et se termine par un chevron fermant >. Entre ces deux chevrons, on trouve au minimum un mot. Par exemple <bibliotheque>. Cette balise s'appelle balise ouvrante, et autant vous le dire tout de suite : il va falloir la fermer ! Il existe deux manières de fermer une balise ouvrante :

Ce type d'informations, qu'il soit fermé par une balise fermante ou qu'il n'en n'ait pas besoin, s'appelle un nœud. Vous voyez donc que l'on a un nœud appelé bibliotheque, deux nœuds appelés livre, etc.

Le nœud <bibliotheque>, qui est le nœud qui englobe tous les autres nœuds, s'appelle la racine. Il y a dans un fichier XML au moins une racine et au plus une racine. Oui ça veut dire qu'il y a exactement une racine par fichier. ;)

On peut établir toute une hiérarchie dans un fichier XML. En effet, entre la balise ouvrante et la balise fermante d'un nœud, il est possible de mettre d'autres nœuds. Les nœuds qui se trouvent dans un autre nœud s'appellent des enfants, et le nœud encapsulant s'appelle le parent.

Les nœuds peuvent avoir des attributs pour indiquer des informations. Dans notre exemple, le nœud <prix> a l'attribut devise afin de préciser en quelle devise est exprimé ce prix : <prix devise="euro">9.40</prix> pour La Horde Du Contrevent, qui vaut donc 9€40. Vous remarquerez que pour A Game Of Thrones on a aussi le nœud prix, mais il n'a pas l'attribut devise ! C'est tout à fait normal : dans l'interpréteur, si la devise est précisée, alors je considère que le prix est exprimé en cette devise ; mais si l'attribut devise n'est pas précisé, alors le prix est en dollars. A Game Of Thrones vaut donc $10.17. Le format XML en lui-même ne peut pas détecter si l'absence de l'attribut devise est une anomalie, cela retirerait toute la liberté que permet le format.

En revanche, le XML est intransigeant sur la syntaxe. Si vous ouvrez une balise, n'oubliez pas de la fermer par exemple !

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les ressources Les différents types de ressources

Les différents types de ressources

Le format XML L'organisation

Les différents types de ressources

Les ressources sont des éléments capitaux dans une application Android. On y trouve par exemple des chaînes de caractères ou des images. Comme Android est destiné à être utilisé sur une grande variété de supports, il fallait trouver une solution pour permettre à une application de s'afficher de la même manière sur un écran 7" que sur un écran 10", ou faire en sorte que les textes s'adaptent à la langue de l'utilisateur. C'est pourquoi les différents éléments qui doivent s'adapter de manière très précise sont organisés de manière tout aussi précise, de façon à ce qu'Android sache quels éléments utiliser pour quels types de terminaux.

On découvre les ressources à travers une hiérarchie particulière de répertoires. Vous pouvez remarquer qu'à la création d'un nouveau projet, Eclipse crée certains répertoires par défaut, comme le montre la figure suivante.

L'emplacement des ressources au sein d'un projet
L'emplacement des ressources au sein d'un projet

Je vous ai déjà dit que les ressources étaient divisées en plusieurs types. Pour permettre à Android de les retrouver facilement, chaque type de ressources est associé à un répertoire particulier. Voici un tableau qui vous indique les principales ressources que l'on peut trouver, avec le nom du répertoire associé. Vous remarquerez que seuls les répertoires les plus courants sont créés par défaut.

Type

Description

Analyse syntaxique

Dessin et image
(res/drawable)

On y trouve les images matricielles (les images de type PNG, JPEG ou encore GIF) ainsi que des fichiers XML qui permettent de décrire des dessins simples (par exemple des cercles ou des carrés).

Oui

Mise en page ou interface graphique
(res/layout)

Les fichiers XML qui représentent la disposition des vues (on abordera cet aspect, qui est très vaste, dans la prochaine partie).

Exclusivement

Menu
(res/menu)

Les fichiers XML pour pouvoir constituer des menus.

Exclusivement

Donnée brute
(res/raw)

Données diverses au format brut. Ces données ne sont pas des fichiers de ressources standards, on pourrait y mettre de la musique ou des fichiers HTML par exemple.

Le moins possible

Différentes variables
(res/values)

Il est plus difficile de cibler les ressources qui appartiennent à cette catégorie tant elles sont nombreuses. On y trouve entre autre des variables standards, comme des chaînes de caractères, des dimensions, des couleurs, etc.

Exclusivement

La colonne « Analyse syntaxique » indique la politique à adopter pour les fichiers XML de ce répertoire. Elle vaut :

Il existe d'autres répertoires pour d'autres types de ressources, mais je ne vais pas toutes vous les présenter. De toute manière, on peut déjà faire des applications complexes avec ces ressources-là.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Le format XML L'organisation

L'organisation

Les différents types de ressources Ajouter un fichier avec Eclipse

L'organisation

Si vous êtes observateurs, vous avez remarqué sur l'image précédente que nous avions trois répertoires res/drawable/, alors que dans le tableau que nous venons de voir, je vous disais que les drawables allaient tous dans le répertoire res/drawable/ et point barre ! C'est tout à fait normal et ce n'est pas anodin du tout.

Comme je vous le disais, nous avons plusieurs ressources à gérer en fonction du matériel. Les emplacements indiqués dans le tableau précédent sont les emplacements par défaut, c'est-à-dire qu'il s'agit des emplacements qui visent le matériel le plus générique possible. Par exemple, vous pouvez considérer que le matériel le plus générique est un système qui n'est pas en coréen, alors vous allez mettre dans le répertoire par défaut tous les fichiers qui correspondent aux systèmes qui ne sont pas en coréen (par exemple les fichiers de langue). Pour placer des ressources destinées aux systèmes en coréen, on va créer un sous-répertoire et préciser qu'il est destiné aux systèmes en coréen. Ainsi, automatiquement, quand un utilisateur français ou anglais utilisera votre application, Android choisira les fichiers dans l'emplacement par défaut, alors que si c'est un utilisateur coréen, il ira chercher dans les sous-répertoires consacrés à cette langue.

En d'autres termes, en partant du nom du répertoire par défaut, il est possible de créer d'autres répertoires qui permettent de préciser à quels types de matériels les ressources contenues dans ce répertoire sont destinées. Les restrictions sont représentées par des quantificateurs et ce sont ces quantificateurs qui vous permettront de préciser le matériel pour lequel les fichiers dans ce répertoire sont destinés. La syntaxe à respecter peut être représentée ainsi :
res/<type_de_ressource>[<-quantificateur 1><-quantificateur 2>…<-quantificateur N>]

Autrement dit, on peut n'avoir aucun quantificateur si l'on veut définir l'emplacement par défaut, ou en avoir un pour réduire le champ de destination, deux pour réduire encore plus, etc. Ces quantificateurs sont séparés par un tiret. Si Android ne trouve pas d'emplacement dont le nom corresponde exactement aux spécifications techniques du terminal, il cherchera parmi les autres répertoires qui existent la solution la plus proche. Je vais vous montrer les principaux quantificateurs (il y en a quatorze en tout, dont un bon paquet qu'on utilise rarement, j'ai donc décidé de les ignorer).

Langue et région

Priorité : 2
La langue du système de l'utilisateur. On indique une langue puis, éventuellement, on peut préciser une région avec « -r ».
Exemples :

Taille de l'écran

Priorité : 3
Il s'agit de la taille de la diagonale de l'écran :

Orientation de l'écran

Priorité : 5
Il existe deux valeurs :

Résolution de l'écran

Priorité : 8

Version d'Android

Priorité : 14
Il s'agit du niveau de l'API (v3, v5, v7 (c'est celle qu'on utilise nous !), etc.).

Regardez l'image précédente (qui de toute façon représente les répertoires créés automatiquement pour tous les projets), que se passe-t-il si l'écran du terminal de l'utilisateur a une grande résolution ? Android ira chercher dans res/drawable-hdpi ! L'écran du terminal de l'utilisateur a une petite résolution ? Il ira chercher dans res/drawable-ldpi/ ! L'écran du terminal de l'utilisateur a une très grande résolution ? Eh bien… il ira chercher dans res/drawable-hdpi puisqu'il s'agit de la solution la plus proche de la situation matérielle réelle.

Exemples et règles à suivre

Tous les répertoires de ressources qui sont différenciés par des quantificateurs devront avoir le même contenu : on indique à Android de quelle ressource on a besoin, sans se préoccuper dans quel répertoire aller le chercher, Android le fera très bien pour nous. Sur l'image précédente, vous voyez que l'icône se trouve dans les trois répertoires drawable/, sinon Android ne pourrait pas la trouver pour les trois types de configuration.

Mes recommandations

Voici les règles que je respecte pour la majorité de mes projets, quand je veux faire bien les choses :

Une mise en page pour chaque orientation et des images adaptées pour chaque résolution. Le quantificateur de l'orientation est surtout utile pour l'interface graphique. Le quantificateur de la résolution sert plutôt à ne pas avoir à ajuster une image et par conséquent à ne pas perdre de qualité.

Pour finir, sachez que les écrans de taille small et xlarge se font rares.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les différents types de ressources Ajouter un fichier avec Eclipse

Ajouter un fichier avec Eclipse

L'organisation Récupérer une ressource

Ajouter un fichier avec Eclipse

Heureusement, les développeurs de l'ADT ont pensé à nous en créant un petit menu qui vous aidera à créer des répertoires de manière simple, sans avoir à retenir de syntaxe. En revanche, il vous faudra parler un peu anglais, je le crains. Faites un clic droit sur n'importe quel répertoire ou fichier de votre projet. Vous aurez un menu un peu similaire à celui représenté à l'image suivante, qui s'affichera.

L'ADT permet d'ajouter des répertoires facilement
L'ADT permet d'ajouter des répertoires facilement

Dans le sous-menu New, soit vous cliquez directement sur Android XML File, soit, s'il n'est pas présent, vous devrez cliquer sur Other…, puis chercher Android XML File dans le répertoire Android. Cette opération ouvrira un assistant de création de fichiers XML visible à la figure suivante.

L'assistant de création de fichiers XML
L'assistant de création de fichiers XML

Le premier champ vous permet de sélectionner le type de ressources désiré. Vous retrouverez les noms des ressources que nous avons décrites dans le premier tableau, ainsi que d'autres qui nous intéressent moins, à l'exception de raw puisqu'il n'est pas destiné à contenir des fichiers XML. À chaque fois que vous changez de type de ressources, la seconde partie de l'écran change et vous permet de choisir plus facilement quel genre de ressources vous souhaitez créer. Par exemple pour Layout, vous pouvez choisir de créer un bouton (Button) ou un encart de texte (TextView). Vous pouvez ensuite choisir dans quel projet vous souhaitez ajouter le fichier. Le champ File vous permet quant à lui de choisir le nom du fichier à créer.

Une fois votre sélection faite, vous pouvez cliquer sur Next pour passer à l'écran suivant (voir figure suivante) qui vous permettra de choisir des quantificateurs pour votre ressource ou Finish pour que le fichier soit créé dans un répertoire sans quantificateurs.

Cette fenêtre vous permet de choisir des quantificateurs pour votre ressource
Cette fenêtre vous permet de choisir des quantificateurs pour votre ressource

Cette section contient deux listes. Celle de gauche présente les quantificateurs à appliquer au répertoire de destination. Vous voyez qu'ils sont rangés dans l'ordre de priorité que j'ai indiqué.

Mais il y a beaucoup plus de quantificateurs et de ressources que ce que tu nous as indiqué !

Oui. Je n'écris pas une documentation officielle pour Android. Si je le faisais, j'en laisserais plus d'un confus et vous auriez un nombre impressionnant d'informations qui ne vous serviraient pas ou peu. Je m'attelle à vous apprendre à faire de jolies applications optimisées et fonctionnelles, pas à faire de vous des encyclopédies vivantes d'Android. ;)

Le champ suivant, Folder, est le répertoire de destination. Quand vous sélectionnez des quantificateurs, vous pouvez avoir un aperçu en temps réel de ce répertoire. Si vous avez commis une erreur dans les quantificateurs, par exemple choisi une langue qui n’existe pas, le quantificateur ne s'ajoutera pas dans le champ du répertoire. Si ce champ ne vous semble pas correct vis-à-vis des quantificateurs sélectionnés, c'est que vous avez fait une faute d'orthographe. Si vous écrivez directement un répertoire dans Folder, les quantificateurs indiqués s'ajouteront dans la liste correspondante.

Cet outil peut gérer les erreurs et conflits. Si vous indiquez comme nom « strings » et comme ressource une donnée (« values »), vous verrez un petit avertissement qui s'affichera en haut de la fenêtre, puisque ce fichier existe déjà (il est créé par défaut).

Petit exercice

Vérifions que vous avez bien compris : essayez, sans passer par les outils d'automatisation, d'ajouter une mise en page destinée à la version 8, quand l'utilisateur penche son téléphone en mode portrait alors qu'il utilise le français des Belges (fr-rBE) et que son terminal a une résolution moyenne.

Le Folder doit contenir exactement/res/layout-fr-rBE-port-mdpi-v8.

Il vous suffit de cliquer sur Finish si aucun message d'erreur ne s'affiche.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

L'organisation Récupérer une ressource

Récupérer une ressource

Ajouter un fichier avec Eclipse Constitution des interfaces graphiques

Récupérer une ressource

La classe R

On peut accéder à cette classe qui se trouve dans le répertoire gen/ (comme generated, c'est-à-dire que tout ce qui se trouvera dans ce répertoire sera généré automatiquement), comme indiqué à la figure suivante.

On retrouve le fichier R.java dans gen/<votre_package>/
On retrouve le fichier R.java dans gen//

Ouvrez donc ce fichier et regardez le contenu.

public final class R {
  public static final class attr {
  }
  public static final class dimen {
    public static final int padding_large=0x7f040002;
    public static final int padding_medium=0x7f040001;
    public static final int padding_small=0x7f040000;
  }
  public static final class drawable {
    public static final int ic_action_search=0x7f020000;
    public static final int ic_launcher=0x7f020001;
  }
  public static final class id {
    public static final int menu_settings=0x7f080000;
  }
  public static final class layout {
    public static final int activity_main=0x7f030000;
  }
  public static final class menu {
    public static final int activity_main=0x7f070000;
  }
  public static final class string {
    public static final int app_name=0x7f050000;
    public static final int hello_world=0x7f050001;
    public static final int menu_settings=0x7f050002;
    public static final int title_activity_main=0x7f050003;
  }
  public static final class style {
    public static final int AppTheme=0x7f060000;
  }
}

Ça vous rappelle quelque chose ? Comparons avec l'ensemble des ressources que comporte notre projet (voir figure suivante).

Tiens, ces noms me disent quelque chose…
Tiens, ces noms me disent quelque chose…

On remarque en effet une certaine ressemblance, mais elle n'est pas parfaite ! Décryptons certaines lignes de ce code.

La classe layout
public static final class layout { 
  public static final int activity_main=0x7f030000; 
}

Il s'agit d'une classe déclarée dans une autre classe : c'est ce qui s'appelle une classe interne. La seule particularité d'une classe interne est qu'elle est déclarée dans une autre classe, mais elle peut agir comme toutes les autres classes. Cependant, pour y accéder, il faut faire référence à la classe qui la contient. Cette classe est de type public static final et de nom layout.

Cette classe contient un unique public int, affublé des modificateurs static et final. Il s'agit par conséquent d'une constante, à laquelle n'importe quelle autre classe peut accéder sans avoir à créer d'objet de type layout ni de type R.

Cet entier est de la forme 0xZZZZZZZZ. Quand un entier commence par 0x, c'est qu'il s'agit d'un nombre hexadécimal sur 32 bits. Si vous ignorez ce dont il s'agit, ce n'est pas grave, dites-vous juste que ce type de nombre est un nombre exactement comme un autre, sauf qu'il respecte ces règles-ci :

Regardez les exemples suivants :

int deuxNorm = 2; // Valide !
int deuxHexa = 0x00000002; // Valide, et vaut la même chose que « deuxNorm »
int deuxRed = 0x2; // Valide, et vaut la même chose que « deuxNorm » et « deuxHexa » (évidemment, 00000002, c'est la même chose que 2 !)
//Ici, nous allons toujours écrire les nombres hexadécimaux avec huit chiffres, même les 0 inutiles !
int beaucoup = 0x0AFA1B00; // Valide !
int marcheraPas = 1x0AFA1B00; // Non ! Un nombre hexadécimal commence toujours par « 0x » !
int marcheraPasNonPlus = 0xG00000000; // Non ! Un chiffre hexadécimal va de 0 à F, on n'accepte pas les autres lettres !
int caVaPasLaTete = 0x124!AZ5%; // Alors là c'est carrément n'importe quoi !

Cet entier a le même nom qu'un fichier de ressources (activity_main), tout simplement parce qu'il représente ce fichier (activity_main.xml). On ne peut donc avoir qu'un seul attribut de ce nom-là dans la classe, puisque deux fichiers qui appartiennent à la même ressource se trouvent dans le même répertoire et ne peuvent par conséquent pas avoir le même nom. Cet entier est un identifiant unique pour le fichier de mise en page qui s'appelle activity_main. Si un jour on veut utiliser ou accéder à cette mise en page depuis notre code, on y fera appel à l'aide de cet identifiant.

La classe drawable
public static final class drawable { 
  public static final int ic_action_search=0x7f020000; 
  public static final int ic_launcher=0x7f020001; 
}

Contrairement au cas précédent, on a un seul entier pour plusieurs fichiers qui ont le même nom ! On a vu dans la section précédente qu'il fallait nommer de façon identique ces fichiers qui ont la même fonction, pour une même ressource, mais avec des quantificateurs différents. Eh bien, quand vous ferez appel à l'identificateur, Android saura qu'il lui faut le fichier ic_launcher et déterminera automatiquement quel est le répertoire le plus adapté à la situation du matériel parmi les répertoires des ressources drawable, puisqu'on se trouve dans la classe drawable.

La classe string
public static final class string {
  public static final int app_name=0x7f050000; 
  public static final int hello_world=0x7f050001; 
  public static final int menu_settings=0x7f050002; 
  public static final int title_activity_main=0x7f050003;
}

Cette fois, si on a quatre entiers, c'est tout simplement parce qu'on a quatre chaînes de caractères dans le fichier res/values/strings.xml, qui contient les chaînes de caractères (qui sont des données). Vous pouvez le vérifier par vous-mêmes en fouillant le fichier strings.xml.

Il existe d'autres variables dont je n'ai pas discuté, mais vous avez tout compris déjà avec ce que nous venons d'étudier.

Application
Énoncé

J'ai créé un nouveau projet pour l'occasion, mais vous pouvez très bien vous amuser avec le premier projet. L'objectif ici est de récupérer la ressource de type chaîne de caractères qui s'appelle hello_world (créée automatiquement par Eclipse) afin de la mettre comme texte dans un TextView. On affichera ensuite le TextView.

On utilisera la méthode public final void setText (int id) (id étant l'identifiant de la ressource) de la classe TextView.

Solution
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class Main extends Activity {
  private TextView text = null;
	
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    text = new TextView(this);
    text.setText(R.string.hello_world);

    setContentView(text);
  }
}

Comme indiqué auparavant, on peut accéder aux identificateurs sans instancier de classe. On récupère dans la classe R l'identificateur de la ressource du nom hello_world qui se trouve dans la classe string, puisqu'il s'agit d'une chaîne de caractères, donc R.string.hello_world.

Et si je mets à la place de l'identifiant d'une chaîne de caractères un identifiant qui correspond à un autre type de ressources ?

Eh bien, les ressources sont des objets Java comme les autres. Par conséquent ils peuvent aussi posséder une méthode public String toString() ! Pour ceux qui l'auraient oublié, la méthode public String toString() est appelée sur un objet pour le transformer en chaîne de caractères, par exemple si on veut passer l'objet dans un System.out.println. Ainsi, si vous mettez une autre ressource qu'une chaîne de caractères, ce sera la valeur rendue par la méthode toString() qui sera affichée.

Essayez par vous-mêmes, vous verrez ce qui se produit. ;) Soyez curieux, c'est comme ça qu'on apprend !

Application
Énoncé

Je vous propose un autre exercice. Dans le précédent, le TextView a récupéré l'identifiant et a été chercher la chaîne de caractères associée pour l'afficher. Dans cet exercice, on va plutôt récupérer la chaîne de caractères pour la manipuler.

Instructions
Solution
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class Main extends Activity {
  private TextView text = null;
  private String hello = null;
	
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    hello = getResources().getString(R.string.hello_world);
    // Au lieu d'afficher "Hello World!" on va afficher "Hello les Zéros !"
    hello = hello.replace("world", "les Zéros ");
    
    text = new TextView(this);
    text.setText(hello);
    
    setContentView(text);
  }
}

J'ai une erreur à la compilation ! Et un fichier similaire à mon fichier XML mais qui se finit par .out vient d'apparaître !

Ah, ça veut dire que vous avez téléchargé une version d'Eclipse avec un analyseur syntaxique XML. En fait si vous lancez la compilation alors que vous étiez en train de consulter un fichier XML, alors c'est l'analyseur qui se lancera et pas le compilateur. La solution est donc de cliquer sur n'importe quel autre fichier que vous possédez qui ne soit pas un XML, puis de relancer la compilation.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Ajouter un fichier avec Eclipse Constitution des interfaces graphiques

Constitution des interfaces graphiques

Récupérer une ressource L'interface d'Eclipse

Bien, maintenant que vous avez compris le principe et l'utilité des ressources, voyons comment appliquer nos nouvelles connaissances aux interfaces graphiques. Avec la diversité des machines sous lesquelles fonctionne Android, il faut vraiment exploiter toutes les opportunités offertes par les ressources pour développer des applications qui fonctionneront sur la majorité des terminaux.

Une application Android polyvalente possède un fichier XML pour chaque type d'écran, de façon à pouvoir s'adapter. En effet, si vous développez une application uniquement à destination des petits écrans, les utilisateurs de tablettes trouveront votre travail illisible et ne l'utiliseront pas du tout. Ici on va voir un peu plus en profondeur ce que sont les vues, comment créer des ressources d'interface graphique et comment récupérer les vues dans le code Java de façon à pouvoir les manipuler.

L'interface d'Eclipse

Constitution des interfaces graphiques Règles générales sur les vues

L'interface d'Eclipse

La bonne nouvelle, c'est qu'Eclipse nous permet de créer des interfaces graphiques à la souris. Il est en effet possible d'ajouter un élément et de le positionner grâce à sa souris. La mauvaise, c'est que c'est beaucoup moins précis qu'un véritable code et qu'en plus l'outil est plutôt buggé. Tout de même, voyons voir un peu comment cela fonctionne.

Ouvrez le seul fichier qui se trouve dans le répertoire res/layout. Il s'agit normalement du fichier activity_main.xml. Une fois ouvert, vous devriez avoir quelque chose qui ressemble à la figure suivante.

Le fichier est ouvert
Le fichier est ouvert

Cet outil vous aide à mettre en place les vues directement dans le layout de l'application, représenté par la fenêtre du milieu. Comme il ne peut remplacer la manipulation de fichiers XML, je ne le présenterai pas dans les détails. En revanche, il est très pratique dès qu'il s'agit d'afficher un petit aperçu final de ce que donnera un fichier XML.

Présentation de l'outil

C'est à l'aide du menu en haut, celui visible à la figure suivante, que vous pourrez observer le résultat avec différentes options.

Menu d'options
Menu d'options

Ce menu est divisé en deux parties : les icônes du haut et celles du bas. Nous allons nous concentrer sur les icônes du haut pour l'instant (voir figure suivante).

Les icônes du haut du menu d'options
Les icônes du haut du menu d'options

Occupons-nous maintenant de la deuxième partie, tout d'abord avec les icônes de gauche, visibles à la figure suivante.

Les icônes de gauche du bas menu
Les icônes de gauche du bas menu

Ces boutons sont spécifiques à un composant et à son layout parent, contrairement aux boutons précédents qui étaient spécifiques à l'outil. Ainsi, si vous ne sélectionnez aucune vue, ce sera la vue racine qui sera sélectionnée par défaut. Comme les boutons changent en fonction du composant et du layout parent, je ne vais pas les présenter en détail.

Enfin l'ensemble de boutons de droite, visibles à la figure suivante.

Les icônes de droite du bas menu
Les icônes de droite du bas menu
Utilisation

Autant cet outil n'est pas aussi précis, pratique et surtout dénué de bugs que le XML, autant il peut s'avérer pratique pour certaines manipulations de base. Il permet par exemple de modifier les attributs d'une vue à la volée. Sur la figure suivante, vous voyez au centre de la fenêtre une activité qui ne contient qu'un TextView. Si vous effectuez un clic droit dessus, vous pourrez voir les différentes options qui se présentent à vous, comme le montre la figure suivante.

Un menu apparaît lors d'un clic droit sur une vue
Un menu apparaît lors d'un clic droit sur une vue

Vous comprendrez plus tard la signification de ces termes, mais retenez bien qu'il est possible de modifier les attributs via un clic droit. Vous pouvez aussi utiliser l'encart Properties en bas à droite (voir figure suivante).

L'encart « Properties »
L'encart « Properties »

De plus, vous pouvez placer différentes vues en cliquant dessus depuis le menu de gauche, puis en les déposant sur l'activité, comme le montre la figure suivante.

Il est possible de faire un cliquer/glisser
Il est possible de faire un cliquer/glisser

Il vous est ensuite possible de les agrandir, de les rapetisser ou de les déplacer en fonction de vos besoins, comme le montre la figure suivante.

Vous pouvez redimensionner les vues
Vous pouvez redimensionner les vues

Nous allons maintenant voir la véritable programmation graphique. Pour accéder au fichier XML correspondant à votre projet, cliquez sur le deuxième onglet activity_main.xml.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Constitution des interfaces graphiques Règles générales sur les vues

Règles générales sur les vues

L'interface d'Eclipse Identifier et récupérer des vues

Règles générales sur les vues

Différenciation entre un layout et un widget

Normalement, Eclipse vous a créé un fichier XML par défaut :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent" >

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:padding="@dimen/padding_medium"
    android:text="@string/hello_world"
    tools:context=".MainActivity" />

</RelativeLayout>

La racine possède deux attributs similaires : xmlns:android="http://schemas.android.com/apk/res/android" et xmlns:tools="http://schemas.android.com/tools". Ces deux lignes permettent d'utiliser des attributs spécifiques à Android. Si vous ne les mettez pas, vous ne pourrez pas utiliser les attributs et le fichier XML sera un fichier XML banal au lieu d'être un fichier spécifique à Android. De plus, Eclipse refusera de compiler.

On trouve ensuite une racine qui s'appelle RelativeLayout. Vous voyez qu'elle englobe un autre nœud qui s'appelle TextView. Ah ! Ça vous connaissez ! Comme indiqué précédemment, une interface graphique pour Android est constituée uniquement de vues. Ainsi, tous les nœuds de ces fichiers XML seront des vues.

Revenons à la première vue qui en englobe une autre. Avec Swing vous avez déjà rencontré ces objets graphiques qui englobent d'autres objets graphiques. On les appelle en anglais des layouts et en français des gabarits. Un layout est donc une vue spéciale qui peut contenir d'autres vues et qui n'est pas destinée à fournir du contenu ou des contrôles à l'utilisateur. Les layouts se contentent de disposer les vues d'une certaine façon. Les vues contenues sont les enfants, la vue englobante est le parent, comme en XML. Une vue qui ne peut pas en englober d'autres est appelée un widget (composant, en français).

Vous pouvez bien sûr avoir en racine un simple widget si vous souhaitez que votre mise en page consiste en cet unique widget.

Attributs en commun

Comme beaucoup de nœuds en XML, une vue peut avoir des attributs, qui permettent de moduler certains de ses aspects. Certains de ces attributs sont spécifiques à des vues, d'autres sont communs. Parmi ces derniers, les deux les plus courants sont layout_width, qui définit la largeur que prend la vue (la place sur l'axe horizontal), et layout_height, qui définit la hauteur qu'elle prend (la place sur l'axe vertical). Ces deux attributs peuvent prendre une valeur parmi les trois suivantes :

Je vous conseille de ne retenir que deux unités :

Il y a quelque chose que je trouve étrange : la racine de notre layout, le nœud RelativeLayout, utilise fill_parent en largeur et en hauteur. Or, tu nous avais dit que cet attribut signifiait qu'on prenait toute la place du parent… Mais il n'a pas de parent, puisqu'il s'agit de la racine !

C'est parce qu'on ne vous dit pas tout, on vous cache des choses, la vérité est ailleurs. En fait, même notre racine a une vue parent, c'est juste qu'on n'y a pas accès. Cette vue parent invisible prend toute la place possible dans l'écran.

Vous pouvez aussi définir une marge interne pour chaque widget, autrement dit l'espacement entre le contour de la vue et son contenu (voir figure suivante).

Il est possible de définir une marge interne pour chaque widget
Il est possible de définir une marge interne pour chaque widget

Ci-dessous avec l'attribut android:padding dans le fichier XML pour définir un carré d'espacement ; la valeur sera suivie d'une unité, 10.5dp par exemple.

<TextView
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:padding="10.5dp"
  android:text="@string/hello" />

La méthode Java équivalente est public void setPadding (int left, int top, int right, int bottom).

textView.setPadding(15, 105, 21, 105);

En XML on peut aussi utiliser des attributs android:paddingBottom pour définir uniquement l'espacement avec le plancher, android:paddingLeft pour définir uniquement l'espacement entre le bord gauche du widget et le contenu, android:paddingRight pour définir uniquement l'espacement de droite et enfin android:paddingTop pour définir uniquement l'espacement avec le plafond.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

L'interface d'Eclipse Identifier et récupérer des vues

Identifier et récupérer des vues

Règles générales sur les vues Les widgets les plus simples

Identifier et récupérer des vues

Identification

Vous vous rappelez certainement qu'on a dit que certaines ressources avaient un identifiant. Eh bien, il est possible d'accéder à une ressource à partir de son identifiant à l'aide de la syntaxe @X/Y. Le @ signifie qu'on va parler d'un identifiant, le X est la classe où se situe l'identifiant dans R.java et enfin, le Y sera le nom de l'identifiant. Bien sûr, la combinaison X/Y doit pointer sur un identifiant qui existe. Reprenons notre classe créée par défaut :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent" >

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:padding="@dimen/padding_medium"
    android:text="@string/hello_world"
    tools:context=".MainActivity" />

</RelativeLayout>

On devine d'après la ligne surlignée que le TextView affichera le texte de la ressource qui se trouve dans la classe String de R.java et qui s'appelle hello_world. Enfin, vous vous rappelez certainement aussi que l'on a récupéré des ressources à l'aide de l'identifiant que le fichier R.java créait automatiquement dans le chapitre précédent. Si vous allez voir ce fichier, vous constaterez qu'il ne contient aucune mention à nos vues, juste au fichier activity_main.xml. Eh bien, c'est tout simplement parce qu'il faut créer cet identifiant nous-mêmes (dans le fichier XML hein, ne modifiez jamais R.java par vous-mêmes, malheureux !).

Afin de créer un identifiant, on peut rajouter à chaque vue un attribut android:id. La valeur doit être de la forme @+X/Y. Le + signifie qu'on parle d'un identifiant qui n'est pas encore défini. En voyant cela, Android sait qu'il doit créer un attribut.

Le X est la classe dans laquelle sera créé l'identifiant. Si cette classe n'existe pas, alors elle sera créée. Traditionnellement, X vaut id, mais donnez-lui la valeur qui vous plaît. Enfin, le Y sera le nom de l'identifiant. Cet identifiant doit être unique au sein de la classe, comme d'habitude.

Par exemple, j'ai décidé d'appeler mon TextView « text » et de changer le padding pour qu'il vaille 25.7dp, ce qui nous donne :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent" >

  <TextView
    android:id="@+id/text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:padding="25.7dp"
    android:text="@string/hello_world"
    tools:context=".MainActivity" />

</RelativeLayout>

Dès que je sauvegarde, mon fichier R sera modifié automatiquement :

public final class R {
  public static final class attr {
  }
  public static final class dimen {
    public static final int padding_large=0x7f040002;
    public static final int padding_medium=0x7f040001;
    public static final int padding_small=0x7f040000;
  }
  public static final class drawable {
    public static final int ic_action_search=0x7f020000;
    public static final int ic_launcher=0x7f020001;
  }
  public static final class id {
    public static final int menu_settings=0x7f080000;
  }
  public static final class layout {
    public static final int activity_main=0x7f030000;
  }
  public static final class menu {
    public static final int activity_main=0x7f070000;
  }
  public static final class string {
    public static final int app_name=0x7f050000;
    public static final int hello_world=0x7f050001;
    public static final int menu_settings=0x7f050002;
    public static final int title_activity_main=0x7f050003;
  }
  public static final class style {
    public static final int AppTheme=0x7f060000;
  }
}
Instanciation des objets XML

Enfin, on peut utiliser cet identifiant dans le code, comme avec les autres identifiants. Pour cela, on utilise la méthode public View findViewById (int id). Attention, cette méthode renvoie une View, il faut donc la « caster » dans le type de destination.

On caste ? Aucune idée de ce que cela peut vouloir dire !

Petit rappel en ce qui concerne la programmation objet : quand une classe Classe_1 hérite (ou dérive, on trouve les deux termes) d'une autre classe Classe_2, il est possible d'obtenir un objet de type Classe_1 à partir d'un de Classe_2 avec le transtypage. Pour dire qu'on convertit une classe mère (Classe_2) en sa classe fille (Classe_1) on dit qu'on casteClasse_2 en Classe_1, et on le fait avec la syntaxe suivante :

//avec « class Class_1 extends Classe_2 »
Classe_2 objetDeux = null;
Classe_1 objetUn = (Classe_1) objetDeux;

Ensuite, et c'est là que tout va devenir clair, vous pourrez déclarer que votre activité utilise comme interface graphique la vue que vous désirez à l'aide de la méthode void setContentView (View view). Dans l'exemple suivant, l'interface graphique est référencée par R.layout.activity_main, il s'agit donc du layout d'identifiant main, autrement dit celui que nous avons manipulé un peu plus tôt.

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class TroimsActivity extends Activity {
  TextView monTexte = null;
	
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    	
    monTexte = (TextView)findViewById(R.id.text);
    monTexte.setText("Le texte de notre TextView");
  }
}

Je peux tout à fait modifier le padding a posteriori.

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class TroimsActivity extends Activity {
  TextView monTexte = null;
	
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    	
    monTexte = (TextView)findViewById(R.id.text);
    // N'oubliez pas que cette fonction n'utilise que des entiers
    monTexte.setPadding(50, 60, 70, 90);
  }
}

Y a-t-il une raison pour laquelle on accède à la vue après le setContentView ?

Oui ! Essayez de le faire avant, votre application va planter.

En fait, à chaque fois qu'on récupère un objet depuis un fichier XML dans notre code Java, on procède à une opération qui s'appelle la désérialisation. Concrètement, la désérialisation, c'est transformer un objet qui n'est pas décrit en Java − dans notre cas l'objet est décrit en XML − en un objet Java réel et concret. C'est à cela que sert la fonction View findViewById (int id). Le problème est que cette méthode va aller chercher dans un arbre de vues, qui est créé automatiquement par l'activité. Or, cet arbre ne sera créé qu'après le setContentView ! Donc le findViewById retournera null puisque l'arbre n'existera pas et l'objet ne sera donc pas dans l'arbre. On va à la place utiliser la méthode static View inflate (Context context, int id, ViewGroup parent). Cette méthode va désérialiser l'arbre XML au lieu de l'arbre de vues qui sera créé par l'activité.

import android.app.Activity;
import android.os.Bundle;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class TroimsActivity extends Activity {
  RelativeLayout layout = null;
  TextView text = null;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // On récupère notre layout par désérialisation. La méthode inflate retourne un View
    // C'est pourquoi on caste (on convertit) le retour de la méthode avec le vrai type de notre layout, c'est-à-dire RelativeLayout
    layout = (RelativeLayout) RelativeLayout.inflate(this, R.layout.activity_main, null);
    // … puis on récupère TextView grâce à son identifiant
    text = (TextView) layout.findViewById(R.id.text);
    text.setText("Et cette fois, ça fonctionne !");
    setContentView(layout);
    // On aurait très bien pu utiliser « setContentView(R.layout.activity_main) » bien sûr !
  }
}

C'est un peu contraignant ! Et si on se contentait de faire un premier setContentView pour « inflater » (désérialiser) l'arbre et récupérer la vue pour la mettre dans un second setContentView ?

Un peu comme cela, voulez-vous dire ?

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class TroimsActivity extends Activity {
  TextView monTexte = null;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    monTexte = (TextView)findViewById(R.id.text);
    monTexte.setPadding(50, 60, 70, 90);

    setContentView(R.layout.activity_main);
  }
}

Ah d'accord, comme cela l'arbre sera inflate et on n'aura pas à utiliser la méthode compliquée vue au-dessus…

C'est une idée… mais je vous répondrais que vous avez oublié l'optimisation ! Un fichier XML est très lourd à parcourir, donc construire un arbre de vues prend du temps et des ressources. À la compilation, si on détecte qu'il y a deux setContentView dans onCreate, eh bien on ne prendra en compte que la dernière ! Ainsi, toutes les instances de setContentView précédant la dernière sont rendues caduques.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Règles générales sur les vues Les widgets les plus simples

Les widgets les plus simples

Identifier et récupérer des vues Les widgets

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

Les widgets les plus simples Gérer les évènements sur les widgets

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

Exemple en XML
<EditText 
  android:layout_width="fill_parent"
  android:layout_height="wrap_content" 
  android:hint="@string/editText"
  android:inputType="textMultiLine"
  android:lines="5" />
Exemple en Java
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
Rendu d'un EditText
Button

Un simple bouton, même s'il s'agit en fait d'un TextView cliquable.

Exemple en XML
<Button 
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="@string/button" />
Exemple en Java
Button button = new Button(this);
editText.setText(R.string.button);
Rendu

Le rendu se trouve à la figure suivante.

Rendu d'un Button
Rendu d'un Button
CheckBox

Une case qui peut être dans deux états : cochée ou pas.

Exemple en XML
<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
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
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.

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
<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
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é
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
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
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) :

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

Pour l'instant, on va se contenter de faire l'interface graphique. Elle ressemblera à la figure suivante.

Notre programme ressemblera à ça
Notre programme ressemblera à ça
Instructions

Avant de commencer, voici quelques instructions :

Voici le layout de base :

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

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les widgets les plus simples Gérer les évènements sur les widgets

Gérer les évènements sur les widgets

Les widgets Organiser son interface avec des layouts

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 callbackvoid 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) :

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 callbackonTouch 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 :

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 :

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 :

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 :

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 :

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.

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

Je vous donne le code pour faire en sorte d'avoir le bouton bien au milieu du layout :

<?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
// 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 :

Consignes
Ma solution
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 !

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les widgets Organiser son interface avec des layouts

Organiser son interface avec des layouts

Gérer les évènements sur les widgets LinearLayout : placer les éléments sur une ligne

Pour l'instant, la racine de tous nos layouts a toujours été la même, ce qui fait que toutes nos applications avaient exactement le même squelette ! Mais il vous suffit de regarder n'importe quelle application Android pour réaliser que toutes les vues ne sont pas forcément organisées comme cela et qu'il existe une très grande variété d'architectures différentes. C'est pourquoi nous allons maintenant étudier les différents layouts, afin d'apprendre à placer nos vues comme nous le désirons. Nous pourrons ainsi concevoir une application plus attractive, plus esthétique et plus ergonomique ! :D

LinearLayout : placer les éléments sur une ligne

Organiser son interface avec des layouts RelativeLayout : placer les éléments les uns en fonction des autres

LinearLayout : placer les éléments sur une ligne

Comme son nom l'indique, ce layout se charge de mettre les vues sur une même ligne, selon une certaine orientation. L'attribut pour préciser cette orientation est android:orientation. On peut lui donner deux valeurs :

On va faire quelques expériences pour s'amuser !

Premier exemple
<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/premier"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="Premier bouton" />
    
  <Button  
    android:id="@+id/second"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="Second bouton" />
</LinearLayout>

Le rendu de ce code se trouve à la figure suivante.

Les deux boutons prennent toute la largeur
Les deux boutons prennent toute la largeur
Deuxième exemple
<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/premier"
    android:layout_width="wrap_content" 
    android:layout_height="fill_parent" 
    android:text="Premier bouton" />
    
  <Button  
    android:id="@+id/second"
    android:layout_width="wrap_content" 
    android:layout_height="fill_parent" 
    android:text="Second bouton" />
</LinearLayout>

Le rendu de ce code se trouve à la figure suivante.