Bonjour à tous et bienvenue dans le monde merveilleux du développement d'applications 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 :
Les applications Android étant presque essentiellement codées en Java, il vous faut connaître ce langage. Heureusement, le Site du Zéro propose un cours et même un livre sur le Java.
Connaître un minimum de SQL pour les requêtes (ça tombe bien, le Site du Zéro propose un cours sur MySQL). Si vous ne connaissez absolument rien en SQL, vous pourrez tout de même suivre le cours dans son intégralité, mais constituer votre propre base de données sans théorie me semble risqué.
Et enfin, être un minimum autonome en informatique : vous devez par exemple être capables d'installer Eclipse tout seul (vous voyez, je ne vous demande pas la lune).
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 !
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... ;)
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
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.
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.
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 :
Il faut pouvoir interagir avec un système complet sans l'interrompre. Android fait des choses pendant que votre application est utilisée, il reçoit des SMS et des appels, entre autres. Il faut respecter une certaine priorité dans l'exécution des tâches. Sincèrement, vous allez bloquer les appels de l'utilisateur pour qu'il puisse terminer sa partie de votre jeu de sudoku ? :-°
Comme je l'ai déjà dit, le système n'est pas aussi puissant qu'un ordinateur classique, il faudra exploiter tous les outils fournis afin de débusquer les portions de code qui nécessitent des optimisations.
La taille de l'écran est réduite, et il existe par ailleurs plusieurs tailles et résolutions différentes. Votre interface graphique doit s'adapter à toutes les tailles et toutes les résolutions, ou vous risquez de laisser de côté un bon nombre d'utilisateurs.
Autre chose qui est directement lié, les interfaces tactiles sont peu pratiques en cas d'utilisation avec un stylet et/ou peu précises en cas d'utilisation avec les doigts, d'où des contraintes liées à la programmation événementielle plus rigides. En effet, il est possible que l'utilisateur se trompe souvent de bouton. Très souvent s'il a de gros doigts. ^^
Enfin, en plus d'avoir une variété au niveau de la taille de l'écran, on a aussi une variété au niveau de la langue, des composants matériels présents et des versions d'Android. Il y a une variabilité entre chaque téléphone et même parfois entre certains téléphones identiques. C'est un travail en plus à prendre en compte.
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.
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é :
public, pour qu'un attribut ou une méthode soit accessible à tous.
protected, pour que les éléments ne soient accessibles qu'aux classes filles.
Enfin private, pour que les éléments ne soient accessibles à personne si ce n'est la classe elle-même.
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é
Google n'est pas le seul à l'initiative du projet Android. C'est en 2007 que l'Open Handset Alliance (OHA) a été créé et elle comptait 35 entreprises à ses débuts.
La philosophie du système réside sur 6 points importants : il fallait qu'il soit open source, gratuit dans la mesure du possible, facile à développer, facile à vendre, flexible et ingénieux.
Il ne faut jamais perdre à l'esprit que vos smartphones sont (pour l'instant) moins puissants et possèdent moins de mémoire que vos ordinateurs !
Il existe un certain nombre de bonnes pratiques qu'il faut absolument respecter dans le développement de vos applications. Sans quoi, l'utilisateur aura tendance à vouloir les désinstaller.
Ne bloquez jamais le smartphone. N'oubliez pas qu'il fait aussi autre chose lorsque vous exécutez vos applications.
Optimisez vos algorithmes : votre smartphone n'est pas comparable à votre ordinateur en terme de performance.
Adaptez vos interfaces à tous les types d'écran : les terminaux sont nombreux.
Pensez vos interfaces pour les doigts de l'utilisateur final. S'il possède des gros doigts et que vous faites des petits boutons, l'expérience utilisateur en sera altérée.
Si possible, testez vos applications sur un large choix de smartphones. Il existe des variations entre les versions, les constructeurs et surtout entre les matériels.
Une bonne compréhension du langage Java est nécessaire pour suivre ce cours, et plus généralement pour développer sur Android.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
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.
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.
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 :
Le JRE (Java Runtime Environment), qui contient la JVM (Java Virtual Machine, rappelez-vous, j'ai expliqué le concept de machine virtuelle dans le premier chapitre), les bibliothèques de base du langage ainsi que tous les composants nécessaires au lancement d'applications ou d'applets Java. En gros, c'est l'ensemble d'outils qui vous permettra d’exécuter des applications Java.
Le JDK (Java Development Kit), qui contient le JRE (afin d’exécuter les applications Java), mais aussi un ensemble d'outils pour compiler et déboguer votre code ! Vous trouverez un peu plus de détails sur la compilation dans l'annexe sur l'architecture d'Android.
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 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.
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
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
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.
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
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
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
Android DDMS est l'Android Dalvik Debug Monitor Server, il permet d’exécuter quelques fonctions pour vous aider à déboguer votre application (simuler un appel ou une position géographique par exemple) et d'avoir accès à d'autres informations utiles.
L'ADT.
Android Hierarchy Viewer, qui permet d'optimiser et de déboguer son interface graphique.
Android Traceview, qui permet d'optimiser et de déboguer son application.
Il existe d'autres modules que nous n'utiliserons pas pendant ce cours :
Tracer for OpenGL ES, qui permet de déboguer des applications OpenGL ES.
Android Native Development Tools est utilisé pour développer des applications Android en C++, mais ce cours est axé sur Java.
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.
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
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
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
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
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 :
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 :
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
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 :
Edit… vous permet de changer les caractéristiques de l'AVD sélectionné.
Delete… vous permet de supprimer l'AVD sélectionné.
Repair… ne vous sera peut-être jamais d'aucune utilité, il vous permet de réparer un AVD quand le gestionnaire vous indique qu'il faut le faire.
Details… lancera une nouvelle fenêtre qui listera les caractéristiques de l'AVD sélectionné.
Start… est le bouton qui nous intéresse maintenant, il vous permet de lancer l'AVD.
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
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 :
: Prendre une photo.
: Diminuer le volume de la sonnerie ou de la musique.
: Augmenter le volume de la sonnerie ou de la musique.
: Arrêter l'émulateur.
: Décrocher le téléphone.
: Raccrocher le téléphone.
: Retourner sur le dashboard (l'équivalent du bureau, avec les icônes et les widgets).
: Ouvrir le menu.
: Retour arrière.
: Effectuer une recherche (de moins en moins utilisé).
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
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 »
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
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.
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 :
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.
Le système vous demandera de vous identifier en tant qu'utilisateur root.
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.
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 :
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 !).
Il est essentiel d'installer l'environnement Java sur votre ordinateur pour pouvoir développer vos applications Android.
Vous devez également installer le SDK d'Android pour pouvoir développer vos applications. Ce kit de développement vous offrira, entre autres, les outils pour télécharger les paquets de la version d'Android pour lequel vous voulez développer.
Eclipse n'est pas l'environnement de travail obligatoire pour développer vos applications mais c'est une recommandation de Google pour sa gratuité et sa puissance. De plus, le SDK d'Android est prévu pour s'y intégrer et les codes sources de ce cours seront développés grâce à cet IDE.
Si vous n'avez pas de smartphone Android, Google a pensé à vous et mis à votre disposition des AVD pour tester vos applications. Ces machines virtuelles lancent un véritable système Android mais prenez garde à ne pas vous y fier à 100%, il n'y a rien de plus concret que les tests sur des terminaux physiques.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
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 ! ;)
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é
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
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 :
La navigation sur internet, permise par une interface graphique (la barre d'adresse et le contenu de la page web, au moins) ;
La musique, qui est diffusée en fond sonore, mais qui n'affiche pas d'interface graphique à l'heure actuelle puisque l'utilisateur consulte le navigateur.
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 :
À tout moment votre application peut laisser place à une autre application, qui a une priorité plus élevée. Si votre application utilise trop de ressources système, alors elle empêchera le système de fonctionner correctement et Android l'arrêtera sans vergogne.
Votre activité existera dans plusieurs états au cours de sa vie, par exemple un état actif pendant lequel l'utilisateur l'exploite, et un état de pause quand l'utilisateur reçoit un appel.
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
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é
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.
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
La fenêtre visible à la figure suivante s'ouvre ; voyons ensemble ce qu'elle contient :
Création d'un nouveau projet
Tous ces champs nous permettent de définir certaines caractéristiques de notre projet :
Tout d'abord, vous pouvez choisir le nom de votre application avec Application name. Il s'agit du nom qui apparaîtra sur l'appareil et sur Google Play pour vos futures applications ! Choisissez donc un nom qui semble à la fois judicieux, assez original pour attirer l'attention et qui reste politiquement correct au demeurant.
Project name est le nom de votre projet pour Eclipse. Ce champ n'influence pas l'application en elle-même, il s'agit juste du nom sous lequel Eclipse la connaîtra. Le vrai nom de notre application, celui que reconnaîtra Android et qui a été défini dans Application name, peut très bien n'avoir aucune similitude avec ce que vous mettrez dans ce champ.
Il faudra ensuite choisir dans quel package ira votre application, je vous ai déjà expliqué l'importance des packages précédemment. Sachez que ce package agira comme une sorte d'identifiant pour votre application sur le marché d'applications, alors faites en sorte qu'il soit unique et constant pendant tout le développement de votre application.
Ces trois champs sont indispensables, vous devrez donc tous les renseigner.
Vous vous retrouvez ensuite confronté à deux listes défilantes :
La liste Build SDK vous permet de choisir pour quelle version du SDK vous allez compiler votre application. Comme indiqué précédemment, on va choisir l'API 7.
La liste suivante, Minimum Required SDK, est un peu plus subtile. Elle vous permet de définir à partir de quelle version d'Android votre application sera visible sur le marché d'applications. Ce n'est pas parce que vous compilez votre application pour l'API 7 que vous souhaitez que votre application fonctionne sous les téléphones qui utilisent Android 2.1, vous pouvez très bien viser les téléphones qui exploitent des systèmes plus récents que la 2.2 pour profiter de leur stabilité par exemple, mais sans exploiter les capacités du SDK de l'API 8. De même, vous pouvez très bien rendre disponibles aux utilisateurs d'Android 1.6 vos applications développées avec l'API 7 si vous n'exploitez pas les nouveautés introduites par l'API 7, mais c'est plus complexe.
Enfin, cette fenêtre se conclut par trois cases à cocher :
La première, intitulée Create custom launcher icon, ouvrira à la fenêtre suivante un outil pour vous aider à construire une icône pour votre application à partir d'une image préexistante.
Cochez la deuxième, Mark this project as a library, si votre projet est uniquement une bibliothèque de fonctions. Si vous ne comprenez pas, laissez cette case décochée.
Et la dernière, celle qui s'appelle Create Project in Workspace, si vous souhaitez que soit créé pour votre projet un répertoire dans votre espace de travail (workspace), vous savez, l'emplacement qu'on a défini au premier lancement d'Eclipse ! Si vous décochez cette case, vous devrez alors spécifier où vous souhaitez que vos fichiers soient créés.
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
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
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
Ici encore une fois, on fait face à cinq champs à renseigner :
Activity Name permet d'indiquer le nom de la classe Java qui contiendra votre activité, ce champ doit donc respecter la syntaxe Java standard.
Le champ suivant, Layout Name, renseignera sur le nom du fichier qui contiendra l'interface graphique qui correspondra à cette activité.
En ce qui concerne Navigation Type, son contenu est trop complexe pour être analysé maintenant. Sachez qu'il permet de définir facilement comment s'effectueront les transitions entre plusieurs activités.
Un peu inutile ici, Hierarchical Parent permet d'indiquer vers quelle activité va être redirigé l'utilisateur quand il utilisera le bouton Retour de son terminal. Comme il s'agit de la première activité de notre application, il n'y a pas de navigation à gérer en cas de retour en arrière.
Enfin, Title est tout simplement le titre qui s'affichera en haut de l'activité.
Pour finaliser la création, cliquez sur Finish.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Vous trouverez les fichiers créés dans le Package Explorer (voir figure suivante).
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.
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
Il existe trois manières de résoudre ces problèmes :
Vous pouvez tout simplement ignorer ces avertissements. Votre application fonctionnera toujours, et les performances n'en souffrirons pas. Mais je vois au moins deux raisons de le faire tout de même : pour entretenir un code plus lisible et pour éviter d'avoir par inadvertance deux classes avec le même nom, ce qui peut provoquer des conflits.
Supprimer les lignes manuellement, mais comme nous avons un outil puissant entre les mains, autant laisser Eclipse s'en charger pour nous !
Demander à Eclipse d'organiser les importations automatiquement. Il existe un raccourci qui fait cela : CTRL + SHIFT + O. Hop ! Tous les imports inutilisés sont supprimés !
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 :
S'il s'agit du premier lancement de l'application ou d'un démarrage alors qu'elle avait été quittée normalement, il vaut null.
Mais s'il s'agit d'un retour à l'application après qu'elle a perdu le focus et redémarré, alors il se peut qu'il ne soit pas null si vous avez fait en sorte de sauvegarder des données dedans, mais nous verrons comment dans quelques chapitres, puisque ce n'est pas une chose indispensable à savoir pour débuter.
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.
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.
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
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 »
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
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
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.
Pour avoir des applications fluides et optimisées, il est essentiel de bien comprendre le cycle de vie des activités.
Chaque écran peut être considéré comme une Activity, qui est constitué d'un contexte et d'une interface graphique. Le contexte fait le lien entre l'application et le système alors que l'interface graphique se doit d'afficher à l'écran des données et permettre à l'utilisateur d'interagir avec l'activité.
Pour concevoir une navigation impeccable entre vos différentes activités, vous devez comprendre comment fonctionne la pile des activités. Cette structure retirera en premier la dernière activité qui aura été ajoutée.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
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 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 :
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 :
On utilise la version 1.0 de XML.
On utilise l'encodage des caractères qui s'appelle utf-8 ; c'est une façon de décrire les caractères que contiendra notre fichier.
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 :
Soit par une balise fermante</bibliotheque>, auquel cas vous pourrez avoir du contenu entre la balise ouvrante et la balise fermante. Étant donné que notre bibliothèque est destinée à contenir plusieurs livres, nous avons opté pour cette solution.
Soit on ferme la balise directement dans son corps : <bibliotheque />. La seule différence est qu'on ne peut pas mettre de contenu entre deux balises… puisqu'il n'y en a qu'une. Dans notre exemple, nous avons mis la balise <recommandation note="20"/> sous cette forme par choix, mais nous aurions tout aussi bien pu utiliser <recommandation>20</recommandation>, cela n'aurait pas été une erreur.
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 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
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 :
« Exclusivement », si les fichiers de cette ressource sont tout le temps des fichiers XML.
« Oui », si les fichiers peuvent être d'un autre type que XML, en fonction de ce qu'on veut faire. Ainsi, dans le répertoire drawable/, on peut mettre des images ou des fichiers XML dont le contenu sera utilisé par un interpréteur pour dessiner des images.
« Le moins possible », si les fichiers doivent de préférence ne pas être de type XML. Pourquoi ? Parce que tous les autres répertoires sont suffisants pour stocker des fichiers XML. Alors, si vous voulez placer un fichier XML dans le répertoire raw/, c'est qu'il ne trouve vraiment pas sa place dans un autre répertoire.
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.
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 :
en pour l'anglais ;
fr pour le français ;
fr-rFR pour le français mais uniquement celui utilisé en France ;
fr-rCA pour le français mais uniquement celui utilisé au Québec ;
Etc.
Taille de l'écran
Priorité : 3 Il s'agit de la taille de la diagonale de l'écran :
small pour les écrans de petite taille ;
normal pour les écrans standards ;
large pour les grands écrans, comme dans les tablettes tactiles ;
xlarge pour les très grands écrans, là on pense carrément aux téléviseurs.
Orientation de l'écran
Priorité : 5 Il existe deux valeurs :
port : c'est le diminutif de portrait, donc quand le terminal est en mode portrait ;
land : c'est le diminutif de landscape, donc quand le terminal est en mode paysage.
Résolution de l'écran
Priorité : 8
ldpi : environ 120 dpi ;
mdpi : environ 160 dpi ;
hdpi : environ 240 dpi ;
xhdpi : environ 320 dpi (disponible à partir de l'API 8 uniquement) ;
nodpi : pour ne pas redimensionner les images matricielles (vous savez, JPEG, PNG et GIF !).
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
res/drawable-small pour avoir des images spécifiquement pour les petits écrans.
res/drawable-large pour avoir des images spécifiquement pour les grands écrans.
res/layout-fr pour avoir une mise en page spécifique destinée à tous ceux qui ont un système en français.
res/layout-fr-rFR pour avoir une mise en page spécifique destinée à ceux qui ont choisi la langue Français (France).
res/values-fr-rFR-port pour des données qui s'afficheront uniquement à ceux qui ont choisi la langue Français (France) et dont le téléphone se trouve en orientation portrait.
res/values-port-fr-rFR n'est pas possible, c'est à ça que servent les priorités : il faut impérativement mettre les quantificateurs par ordre croissant de priorité. La priorité de la langue est 2, celle de l'orientation est 5, comme 2 < 5 on doit placer les langues avant l'orientation.
res/layout-fr-rFR-en n'est pas possible puisqu'on a deux quantificateurs de même priorité et qu'il faut toujours respecter l'ordre croissant des priorités. Il nous faudra créer un répertoire pour le français et un répertoire pour l'anglais.
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 :
res/drawable-hdpi ;
res/drawable-ldpi ;
res/drawable-mdpi ;
Pas de res/drawable ;
res/layout-land ;
res/layout.
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.
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
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
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 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.
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//
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…
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.
Un élément public est un élément auquel tout le monde peut accéder sans aucune restriction.
Le mot-clé static, dans le cas d'une classe interne, signifie que la classe n'est pas liée à une instanciation de la classe qui l'encapsule. Pour accéder à layout, on ne doit pas nécessairement créer un objet de type R. On peut y accéder par R.layout.
Le mot-clé final signifie que l'on ne peut pas créer de classe dérivée de 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 :
Il commence par 0x.
Après le 0x, on trouve huit chiffres (ou moins, mais on préfère mettre des 0 pour arriver à 8 chiffres) : 0x123 est équivalent à 0x00000123, tout comme 123 est la même chose que 00000123.
Ces chiffres peuvent aller de 0 à… F. C'est-à-dire qu'au lieu de compter « 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 » on compte « 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F ». A, B, C, D, E et F sont des chiffres normaux, banals, même s'ils n'en n'ont pas l'air, c'est juste qu'il n'y a pas de chiffre après 9, alors il a fallu improviser avec les moyens du bord . ^^ Ainsi, après 9 on a A, après A on a B, après E on a F, et après F on a 10 ! Puis à nouveau 11, 12, 13, 14, 15, 16, 17, 18, 19, 1A, 1B, etc.
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
On va récupérer le gestionnaire de ressources afin d'aller chercher la chaîne de caractères. C'est un objet de la classe Resource que possède notre activité et qui permet d'accéder aux ressources de cette activité. On peut le récupérer grâce à la méthode public Resources getResources().
On récupère la chaîne de caractères hello_world grâce à la méthode string getString(int id), avec id l'identifiant de la ressource.
Et on modifie la chaîne récupérée.
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.
Au même titre que le langage Java est utile pour développer vos application, le langage XML l'est tout autant puisqu'il a été choisi pour mettre en place les différentes ressources de vos projets.
Il existe 5 types de ressources que vous utiliserez majoritairement :
drawable qui contient toutes les images matricielles et les fichiers XML décrivant des dessins simples.
layout qui contient toutes les interfaces que vous attacherez à vos activités pour mettre en place les différentes vues.
menu qui contient toutes les déclarations d'éléments pour confectionner des menus.
raw qui contient toutes les autres ressources au format brut.
values qui contient des valeurs pour un large choix comme les chaînes de caractères, les dimensions, les couleurs, etc.
Les quantificateurs sont utilisés pour cibler précisément un certain nombre de priorités ; à savoir la langue et la région, la taille de l'écran, l'orientation de l'écran, la résolution de l'écran et la version d'Android.
Chaque ressource présente dans le dossier res de votre projet génère un identifiant unique dans le fichier R.java pour permettre de les récupérer dans la partie Java de votre application.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
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.
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
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
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
La première liste déroulante vous permet de naviguer rapidement entre les répertoires de layouts. Vous pouvez ainsi créer des versions alternatives à votre layout actuel en créant des nouveaux répertoires différenciés par leurs quantificateurs.
La deuxième permet d'observer le résultat en fonction de différentes résolutions. Le chiffre indique la taille de la diagonale en pouces (sachant qu'un pouce fait 2,54 centimètres, la diagonale du Nexus One fait 3,7 imes 2,54 = 9 ,4 cm) et la suite de lettres en majuscules la résolution de l'écran. Pour voir à quoi correspondent ces termes en taille réelle, n'hésitez pas à consulter cette image prise sur Wikipédia.
La troisième permet d'observer l'interface graphique en fonction de certains facteurs. Se trouve-t-on en mode portrait ou en mode paysage ? Le périphérique est-il attaché à un matériel d'amarrage ? Enfin, fait-il jour ou nuit ?
La suivante permet d'associer un thème à votre activité. Nous aborderons plus tard les thèmes et les styles.
L'avant-dernière permet de choisir une langue si votre interface graphique change en fonction de la langue.
Et enfin la dernière vérifie le comportement en fonction de la version de l'API, si vous aviez défini des quantificateurs à ce niveau-là.
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
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
Le premier bouton permet de modifier l'affichage en fonction d'une résolution que vous choisirez. Très pratique pour tester, si vous n'avez pas tous les terminaux possibles.
Le deuxième fait en sorte que l'interface graphique fasse exactement la taille de la fenêtre dans laquelle elle se trouve.
Le suivant remet le zoom à 100%.
Enfin les deux suivants permettent respectivement de dézoomer et de zoomer.
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
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 »
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 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
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.
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 :
fill_parent : signifie qu'elle prendra autant de place que son parent sur l'axe concerné ;
wrap_content : signifie qu'elle prendra le moins de place possible sur l'axe concerné. Par exemple si votre vue affiche une image, elle prendra à peine la taille de l'image, si elle affiche un texte, elle prendra juste la taille suffisante pour écrire le texte ;
Une valeur numérique précise avec une unité.
Je vous conseille de ne retenir que deux unités :
dp ou dip : il s'agit d'une unité qui est indépendante de la résolution de l'écran. En effet, il existe d'autres unités comme le pixel (px) ou le millimètre (mm), mais celles-ci varient d'un écran à l'autre… Par exemple si vous mettez une taille de 500 dp pour un widget, il aura toujours la même dimension quelque soit la taille de l'écran. Si vous mettez une dimension de 500 mm pour un widget, il sera grand pour un grand écran… et énorme pour un petit écran.
sp : cette unité respecte le même principe, sauf qu'elle est plus adaptée pour définir la taille d'une police de caractères.
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
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.
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.
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 :
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 :
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 :
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 ?
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.
Eclipse vous permet de confectionner des interfaces à la souris, mais cela ne sera jamais aussi précis que de travailler directement dans le code.
Tous les layouts héritent de la super classe ViewGroup qui elle même hérite de la super classe View. Puisque les widgets héritent aussi de View et que les ViewGroup peuvent contenir des View, les layouts peuvent contenir d'autres layouts et des widgets. C'est là toute la puissance de la hiérarchisation et la confection des interfaces.
View regroupe un certain nombre de propriétés qui deviennent communes aux widgets et aux layouts.
Lorsque vous désérialisez (ou inflatez) un layout dans une activité, vous devez récupérer les widgets et les layouts pour lesquels vous désirez rajouter des fonctionnalités. Cela se fait grâce à la classe R.java qui liste l'ensemble des identifiants de vos ressources.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
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.
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.
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
EditText
Ce composant est utilisé pour permettre à l'utilisateur d'écrire des textes. Il s'agit en fait d'un TextView éditable.
Au lieu d'utiliser android:text, on utilise android:hint. Le problème avec android:text est qu'il remplit l'EditText avec le texte demandé, alors qu'android:hint affiche juste un texte d'indication, qui n'est pas pris en compte par l'EditText en tant que valeur (si vous avez du mal à comprendre la différence, essayez les deux).
On précise quel type de texte contiendra notre EditText avec android:inputType. Dans ce cas précis un texte sur plusieurs lignes. Cet attribut change la nature du clavier qui est proposé à l'utilisateur, par exemple si vous indiquez que l'EditText servira à écrire une adresse e-mail, alors l'arobase sera proposé tout de suite à l'utilisateur sur le clavier. Vous trouverez une liste de tous les inputTypes possibles ici.
Enfin, on peut préciser la taille en lignes que doit occuper l'EditText avec android:lines.
Exemple en Java
EditText editText = new EditText(this);
editText.setHint(R.string.editText);
editText.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE);
editText.setLines(5);
Rendu
Le rendu se trouve à la figure suivante.
Rendu d'un EditText
Button
Un simple bouton, même s'il s'agit en fait d'un TextView cliquable.
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
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.
RadioGroup radioGroup = new RadioGroup(this);
RadioButton radioButton1 = new RadioButton(this);
RadioButton radioButton2 = new RadioButton(this);
RadioButton radioButton3 = new RadioButton(this);
// On ajoute les boutons au RadioGroup
radioGroup.addView(radioButton1, 0);
radioGroup.addView(radioButton2, 1);
radioGroup.addView(radioButton3, 2);
// On sélectionne le premier bouton
radioGroup.check(0);
// On récupère l'identifiant du bouton qui est coché
int id = radioGroup.getCheckedRadioButtonId();
Rendu
Le rendu se trouve à la figure suivante.
Le bouton radio de droite est sélectionné
Utiliser la documentation pour trouver une information
Je fais un petit aparté afin de vous montrer comment utiliser la documentation pour trouver les informations que vous recherchez, parce que tout le monde en a besoin. Que ce soit vous, moi, des développeurs Android professionnels ou n'importe qui chez Google, nous avons tous besoin de la documentation. Il n'est pas possible de tout savoir, et surtout, je ne peux pas tout vous dire ! La documentation est là pour ça, et vous ne pourrez pas devenir de bons développeurs Android — voire de bons développeurs tout court — si vous ne savez pas chercher des informations par vous-mêmes.
Je vais procéder à l'aide d'un exemple. Je me demande comment faire pour changer la couleur du texte de ma TextView. Pour cela, je me dirige vers la documentation officielle : http://developer.android.com/.
Vous voyez un champ de recherche en haut à gauche. Je vais insérer le nom de la classe que je recherche : TextView. Vous voyez une liste qui s'affiche et qui permet de sélectionner la classe qui pourrait éventuellement vous intéresser, comme à la figure suivante.
Une liste s'affiche afin que vous sélectionniez ce qui vous intéresse
J'ai bien sûr cliqué sur Android.widget.TextView puisque c'est celle qui m'intéresse. Nous arrivons alors sur une page qui vous décrit toutes les informations possibles et imaginables sur la classe TextView (voir figure suivante).
Vous avez accès à beaucoup d'informations sur la classe
On voit par exemple qu'il s'agit d'une classe, publique, qui dérive de View et implémente une interface.
La partie suivante représente un arbre qui résume la hiérarchie de ses superclasses.
Ensuite, on peut voir les classes qui dérivent directement de cette classe (Known Direct Subclasses) et les classes qui en dérivent indirectement, c'est-à-dire qu'un des ancêtres de ces classes dérive de View (Known Indirect Subclasses).
Enfin, on trouve en haut à droite un résumé des différentes sections qui se trouvent dans le document (je vais aussi parler de certaines sections qui ne se trouvent pas dans cette classe mais que vous pourrez rencontrer dans d'autres classes) :
Nested Classes est la section qui regroupe toutes les classes internes. Vous pouvez cliquer sur une classe interne pour ouvrir une page similaire à celle de la classe View.
XML Attrs est la section qui regroupe tous les attributs que peut prendre un objet de ce type en XML. Allez voir le tableau, vous verrez que pour chaque attribut XML on trouve associé un équivalent Java.
Constants est la section qui regroupe toutes les constantes dans cette classe.
Fields est la section qui regroupe toutes les structures de données constantes dans cette classe (listes et tableaux).
Ctors est la section qui regroupe tous les constructeurs de cette classe.
Methods est la section qui regroupe toutes les méthodes de cette classe.
Protected Methods est la section qui regroupe toutes les méthodes protégées (accessibles uniquement par cette classe ou les enfants de cette classe).
Ainsi, si je cherche un attribut XML, je peux cliquer sur XML Attrs et parcourir la liste des attributs pour découvrir celui qui m'intéresse (voir figure suivante), ou alors je peux effectuer une recherche sur la page (le raccourci standard pour cela est Ctrl + F ).
Apprenez à utiliser les recherches
J'ai trouvé ! Il s'agit de android:textColor ! Je peux ensuite cliquer dessus pour obtenir plus d'informations et ainsi l'utiliser correctement dans mon code.
Calcul de l'IMC - Partie 1
Énoncé
On va commencer un mini-TP (TP signifie « travaux pratiques » ; ce sont des exercices pour vous entraîner à programmer). Vous voyez ce qu'est l'IMC ? C'est un nombre qui se calcule à partir de la taille et de la masse corporelle d'un individu, afin qu'il puisse déterminer s'il est trop svelte ou trop corpulent.
Pour l'instant, on va se contenter de faire l'interface graphique. Elle ressemblera à la figure suivante.
Notre programme ressemblera à ça
Instructions
Avant de commencer, voici quelques instructions :
On utilisera uniquement le XML.
Pour mettre plusieurs composants dans un layout, on se contentera de mettre les composants entre les balises de ce layout.
On n'utilisera qu'un seul layout.
Les deux EditText permettront de n'insérer que des nombres. Pour cela, on utilise l'attribut android:inputType auquel on donne la valeur numbers.
Les TextView qui affichent « Poids : » et « Taille : » sont centrés, en rouge et en gras.
Pour mettre un TextView en gras on utilisera l'attribut android:textStyle en lui attribuant comme valeur bold.
Pour mettre un TextView en rouge on utilisera l'attribut android:textColor en lui attribuant comme valeur #FF0000. Vous pourrez trouver d'autres valeurs pour indiquer une couleur à cet endroit.
Afin de centrer du texte dans un TextView, on utilise l'attribut android:gravity="center".
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.
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) :
View.OnLongClickListener pour les clics qui durent longtemps, avec la méthode boolean onLongClick(View vue). Cette méthode doit retourner true une fois que l'action associée a été effectuée.
View.OnKeyListener pour gérer l'appui sur une touche. On y associe la méthode boolean onKey(View vue, int code, KeyEvent event). Cette méthode doit retourner true une fois que l'action associée a été effectuée.
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.