mardi 16 juin 2015

L'univers Android

Dans ce tout premier chapitre, je vais vous présenter ce que j'appelle l'« univers Android ». Le système, dans sa genèse, part d'une idée de base simple, et très vite son succès fut tel qu'il a su devenir un incontournable du monde mobile, bien qu'on retrouve ce système d'exploitation dans bien d'autres domaines que la mobilité.
Nous allons rapidement revenir sur cette aventure et sur la philosophie d'Android, puis je rappellerai rapidement les bases de la programmation en Java, pour ceux qui auraient besoin d'une petite piqûre de rappel.

La création d'Android

Quand on pense à Android, on pense immédiatement à Google, et pourtant il faut savoir que cette multinationale n'est pas à l'initiative du projet. D'ailleurs, elle n'est même pas la seule à contribuer à plein temps à son évolution. À l'origine, « Android » était le nom d'une PME américaine, Android Incorporated, 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. L'objectif d'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). Ses principaux concurrents à l'époque étaient Symbian et Windows Mobile. 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 les choses s'envenimèrent. À cette époque, les constructeurs concevaient tous un système d'exploitation spécifique pour leurs téléphones, et il n'y avait aucune base commune entre les systèmes d'exploitation mobiles de deux constructeurs différents. Ce système entravait la possibilité de développer facilement des applications qui s'adapteraient à tous les téléphones, surtout entre constructeurs, 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, capable d'aller sur internet, de lire des vidéos, d'aller sur internet, etc. 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, le système d'exploitation pour iPhone), 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, 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, en particulier iOS. Cette alliance a pour logiciel vedette Android, mais il ne s'agit pas de sa seule activité.
L'OHA compte à l'heure actuelle 80 membres.
Le logo de l'OHA, une organisation qui cherche à développer des standards open source pour les appareils mobiles
Le logo de l'OHA, une organisation qui cherche à développer des standards open source pour les appareils mobiles
Depuis sa création, la popularité d'Android a toujours été croissante. C'est au quatrième trimestre 2010 qu'Android devient le système d'exploitation mobile le plus utilisé au monde, devançant Symbian (le système d'exploitation de Nokia avant qu'ils optent pour Windows Phone). Désormais, on le retrouve non seulement dans les tablettes et smartphones, mais aussi dans les téléviseurs, les consoles de jeux, les appareils photos, etc.

La philosophie et les avantages d'Android

Open source
Le contrat de licence pour Android respecte les principes de l'open source, c'est-à-dire que vous pouvez à tout moment télécharger les sources et les modifier selon vos goûts ! Bon, je ne vous le recommande vraiment pas, à moins que vous sachiez ce que vous faites... Notez au passage qu'Android utilise des bibliothèques open source puissantes, comme par exemple SQLite pour les bases de données et OpenGL pour la gestion d'images 2D et 3D (pour faire des jeux !).
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 avec 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$ (je donnerais bien la valeur équivalente en euro, mais je ne sais pas pourquoi, le taux de change change constamment) 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 veut diffuser une application dessus.
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).
Complémentaire
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.

Le langage Java

Cette petite section permettra à ceux fâchés avec le Java de se remettre un peu dans le bain et surtout de réviser le vocabulaire de base. Notez qu'il ne s'agit que d'un rappel, il est indispensable de bien connaître le Java (ou au moins la notion de programmation orientée objet) avant de pouvoir développer pour Android.

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 dans la mémoire de l'appareil des informations avec lesquelles on va pouvoir faire des opérations. Ainsi, on peut avoir une variable radis qui vaut 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.
En Java, il existe deux types de variable : les primitives et les objets.
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).
int legumes = 6// int est pour les nombres entiers
// boolean peut valoir vrai ou faux
boolean plusDeSixLegumes = true// true veut dire vrai
boolean moinsDeSixLegumes = false// false veut dire faux
float prixLegume = 1.5// float est pour les nombres à virgule
Les objets
A 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éerais un objet qui contient une variable roue qui vaudrait 4, une variable vitesse qui varierait en fonction de la vitesse et une variable carrosserie pour la couleur de la carrosserie et qui pourra valoir « rouge », « bleu », ou « rose et verte à petits pois » pour les fans de Dorothée ! 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 nous ramène au fait qu'un objet peut contenir des primitives, mais aussi 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. Au sein d'une méthode, pour faire appelle à un attribut de la classe, on peut utiliser le mot-clé this afin de différentier l'attribut des variables locales. Le code d'une méthode doit être écrit dans une classe.
Par exemple, voici une méthode qui permet d'arrêter mon véhicule :
class Voiture {
    int roue = 4;
  
    float vitesse;
    
    Couleur carrosserie;
  
    //Je déclare une méthode qui s'appelle "arreter"
    void arreter() {
        int vitesse = 0;
        //Pour s'arrêter, je passe la vitesse à 0 
        this.vitesse = vitesse;
    }
}
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 :
// Je n'écrirai pas toujours tout le contenu de la classe, juste la méthode
// 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 changerVitesse(float facteurDeVitesseboolean acceleration) {
    // S'il s'agit d'une accelération
    if (acceleration == true) {
        // On augmente la vitesse
        vitesse = vitesse + facteurDeVitesse;
    } else {
        // On diminue la vitesse
        vitesse = vitesse - facteurDeVitesse;
    }
    
    // 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 carrosserie) {
    // Quand on construit une voiture, elle a une vitesse nulle
    vitesse = 0;
    this.carrosserie = carrosserie;
}
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 le fichier vehicule.java
// Classe qui ne peut être instanciée
abstract class Vehicule {
    int nombre_de_roues;
    float vitesse;
}
// Dans le fichier voiture.java
// Une Voiture est un Vehicule
class Voiture extends Vehicule { }
// Dans un autre moto.java
// Une Moto est aussi un Vehicule
class Moto extends Vehicule { }
// Dans le même fichier, il s'agit seulement d'une petite classe
// Un Cabriolet est une Voiture (et par conséquent un Véhicule)
class Cabriolet extends Voiture { }
// 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 !
  • Une bonne compréhension du langage Java est nécessaire pour suivre ce cours, et plus généralement pour développer sur Android.

    Installation et configuration des outils

                                   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. Et si vous possédez un appareil sous Android, je vous montrerai comment le configurer de façon à pouvoir travailler directement avec.
    Encore un peu de patience, les choses sérieuses démarreront dès le prochain chapitre.

    Conditions initiales

    Voyons si votre système d'exploitation et votre matériel sont suffisants pour vous mettre au travail.

    Besoin pour tout le monde

    • 2 Go de mémoire RAM, le mieux étant d'avoir plus de 4Go de mémoire RAM.
    • Plus de 1,5 Go de mémoire disque pour tout installer.

    Pour Windows

    Pour un environnement Windows, sont toutes les version ultérieures à Windows Vista inclu, soit Windows Vista, Windows 2003Windows 7 et Windows 8.

    Pour Mac OS

    Sous Mac, il vous faudra Mac OS 10.8.5 ou plus récent.

    Pour Linux

    Sous GNU/Linux, Google conseille d'utiliser une distribution Ubuntu plus récente que la 12.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.11. Si vous avez une distribution 64 bits, elle devra être capable de lancer des applications 32 bits.

    Le Java Development Kit

    En tant que développeur Java vous avez certainement déjà installé le JDK (pour « Java Development Kit »), cependant je vais tout de même vous rappeler comment l'installer. 
    Rendez-vous ici et cliquez sur Download en dessous de JDK :
    Cliquez pour télécharger le JDK
    Cliquez pour télécharger le JDK
    On vous demande ensuite d'accepter (Accept License Agreement) avant de continuer.
    Cliquez sur Accept License Agreement
    Cliquez sur Accept License Agreement
    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 350 Mo de libre sur le disque ciblé.

    Android Studio et le SDK Android

    On va maintenant télécharger un fichier qui contient un ensemble d'outils indispensables pour développer nos applications Android. Ce paquet contient Android Studio , un environnement de développement spécialisé dans le développement d'applications Android, et un outil pour gérer l'installation du SDK Android sur votre système (plus d'explications à ce sujet ci-dessous).
    Pour se procurer ces outils, rendez-vous ici et cliquez sur Download Android Studio :
    Cliquez sur Download Android Studio pour commencer le téléchargement
    Cliquez sur Download Android Studio pour commencer le téléchargement
    Pendant que le télécharge s'effectue, je vais répondre aux questions éventuelles que vous pourriez avoir :
    C'est quoi un environnement de développement ?
    Vous connaissez peut-être plutôt le mot IDE. Un IDE est un logiciel dont l'objectif est de faciliter le développement. En d'autres termes, il vous est possible de développer sans un IDE, mais en utiliser un est beaucoup plus pratique. En effet, 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.
    Il existe des alternatives à Android Studio, mais nous ne les verrons pas, puisqu'Android Studio est l'IDE privilégié par Google pour le développement Android. Dans tous les cas, ce que vous devez comprendre, c'est que le code sera pareil quel que soit l'IDE que vous choisirez, l'IDE n'est qu'un outil, il ne fera pas de travail de développement à votre place, il ne fera que vous aider dans cette tâche.
    C'est quoi un SDK ?
    Les applications Android sont développées en Java, mais un appareil sous Android ne comprend pas le Java tel quel, il comprend une variante du Java adaptée pour Android. Un SDK, un kit de développement dans notre langue, est un ensemble d'outils permettant de développer pour une cible particulière. Par exemple pour développer pour une console de jeu vidéo, on utilise un SDK spécifique pour développer des applications pour cette console. Le SDK Android est donc un ensemble d'outils que met à disposition Google afin de vous permettre de développer des applications pour Android.
    Une fois le téléchargement terminé, lancez l'installation. Pendant l'installation, vous serez confrontés à certaines questions, dont les suivantes :
    Sur cet écran, conservez les options Android SDK et Android Virtual Device, mais ne sélectionnez Performance (Intel HAXM) que si vous avez un processeur Intel capable d'émulation.
    Le premier répertoire est celui qui indique où sera installé Android Studio et le deuxième celui où sera installé le SDK. Ce second répertoire risque de peser assez lourd alors choisissez un disque qui a assez d'espace disque.
    Si votre ordinateur est suffisamment puissant, cet écran vous propose d'indiquer quel mémoire vive (RAM) vous souhaitez accorder à l'émulateur Android. La valeur par défaut est 2 Go mais vous pouvez indiquer une valeur en choisissant Custom.           
    Une fois l'installation terminée, lancez Android Studio.
    Au premier lancement, une boîte de dialogue va s'afficher et vous demander si vous aviez déjà une version d'Android Studio installée précédemment. Si ce n'est pas le cas, sélectionnez la seconde option :
    kjkjlk
    Sélectionnez I do not have a previous version of Android Studio
    Une fênetre s'ouvrira pour vous demander ce que vous souhaitez faire. On va commencer par lui demander de télécharger le SDK d'Android. Pour cela, cliquez sur Configure :
    Pour télécharger le SDK, cliquez sur Configure
    Pour télécharger le SDK, cliquez sur Configure
    Cliquez ensuite sur SDK Manager :
    Ouvrez l'Android SDK Manager
    Ouvrez l'Android SDK Manager
    Le Android SDK Manager s'ouvre et vous tomberez sur un écran similaire à celui-ci :
    L'Android SDK Manager
    L'Android SDK Manager
    Dans le tableau Packages, vous trouverez deux types de lignes :
    • Celles dont l'icône n'est pas un répertoire (une clé anglaise, un erlenmeyer, un petit Bugdroid, ...) correspondent à des paquets, c'est-à-dire des fichiers qui seront téléchargés pour ajouter de nouvelles fonctionnalités au SDK d'Android. A chaque ligne est associé un statut à l'aide de la colonne Status. Par exemple vous pouvez voir que le paquet Android SDK Tools est déjà installé (Installed). En revanche, Documentation for Android SDK n'est pas installé (Not installed). Chez moi, Android SDK Platform-tools est installé, mais n'est pas à jour, l'Android SDK Manager me l'indique avec le status Update available. Il se peut qu'on trouve plusieurs fois des paquets avec le même nom, dans ce cas c'est qu'il s'agit de versions différentes du même paquet, comme vous pouvez le voir dans la colonne Rev. : on trouve la version 19.1 d'Android SDK Platform-tools mais aussi la version 17 par exemple.
    • Les lignes dont l'icône est un répertoire représentent des groupes de paquets, qui appartiennent tous à une même catégories. On trouve la catérogies des outils (Tools) par exemple. Regardez le nom des autres groupes,  vous remarquerez que certains suivent un certain 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 fonctionnera sur les versions suivantes d'Android mais pas sur les versions précédentes.  A des fins pédagogiques, j'ai choisi de n'installer que la dernière version d'Android pour vous présenter toutes les possibilités de développement. Mais dans vos développement réels, il vous faudra bien réfléchir à quelle version viser, en fonction du public visé et de ce que vous aurez besoin dans les API d'Android. De manière générale, il semble sage de délaisser les versions précédant la version 4.0.3 (l'API 15). Vous penserez peut-être qu'il est injuste de laisser de côté les personnes qui utilisent encore les anciennes versions, mais sachez qu'ils ne représentent que 10% du parc mondial des utilisateurs d'Android. Ainsi, toutes les applications que nous développerons fonctionneront sous Android 4.0.3 Ice Cream Sandwich minimum.
    Pour choisir les fichiers nécessaires pour suivre ce cours, il vous suffit de sélectionner les éléments suivants :
    Choisissez ces paquets là
    Choisissez ces paquets là
    Puis cliquez sur Install xx packages... en bas de la page:
    Ici j'ai 8 paquets à installer
    Ici j'ai 8 paquets à installer, mais vous pouvez avoir un chiffre différent
    Il vous faudra ensuite valider les licences pour les fichiers que vous allez télécharger :
    Choisissez Accept License pour chaque package puis cliquez sur Install
    Choisissez Accept License pour chaque package puis cliquez sur Install
    Si vous installez tous ces paquets, vous aurez besoin de 1 Go sur le disque de destination.

    Création de votre premier projet

    Ouvrez à nouveau Android Studio si vous l'avez fermé. Cliquez sur New Project...  pour ouvrir l'assistant de création de projet.
    Cliquez sur New Project...
    Cliquez sur New Project...
    Une nouvelle fenêtre s'ouvrira. Elle contient trois champs, comme vous pourrez le voir ici :
    Voici à quoi servent ces trois champs :
    • Application Name  : il s'agit du nom qui apparaîtra sur l'appareil et sur Google Play ! Choisissez donc un nom qui semble à la fois judicieux, assez original pour attirer l'attention, mais qui reste politiquement correct au demeurant.
    • Company Domain  : on se base sur le nom de domaine de son entreprise pour constituer ce champ, c'est pourquoi il commence par fr.openclassrooms.com  chez moi (mais comme vous ne travaillez pas OpenClassrooms il vous faudra utiliser un autre nom ;)). Mais à quoi sert ce champ ? Il permet à Android Studio de déduire automatiquement un Package Name. Et qu'est-ce qu'un Package Name  ? C'est une chaîne de caractères qui sera utilisée pour déterminer dans quel package se trouvera votre projet. 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. De plus, il ne pourra pas être changé une fois votre application publiée.
      Vous pouvez si vous le voulez changer directement le Package Name  en cliquant sur Edit .
    • Project Location  : indiquez ici l'emplacement où les fichiers de votre projet seront créés.

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

    L'Android Virtual Device, aussi appelé AVD, est un émulateur de terminal sous Android, c'est-à-dire que c'est un logiciel qui se fait passer pour un appareil sous Android à votre ordinateur. 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.
    Repérez où se trouve la barre d'outils. Vous voyez le couple d'icônes représenté dans la figure suivante ?
    Les deux icônes réservées au SDK et à l'AVD
    Les deux icônes réservées au SDK et à l'AVD
    Celle de gauche permet d'ouvrir les outils du SDK et celle de droite permet d'ouvrir l'interface de gestion d'AVD. Vous aurez ainsi un écran qui ressemble à celui-ci :
    Cet écran vous permettra de créer des AVD
    Cet écran vous permettra de créer des AVD
    Vous pouvez voir deux onglets : Android Virtual Devices et Device Definitions. Le premier onglet recense tous vos AVD et vous permet d'en créer avec précision, alors que le second onglet vous permet de créer des onglets qui ressemblent à des machines qui existent réellement, par exemple le Nexus S de Google. Vous pouvez donc créer facilement un AVD comme ça, mais je vais aussi vous présenter le méthode compliquée, parce qu'on est des vrais ici !
    Donc, dans le premier onglet, cliquez sur celui de droite puis sur New… pour ajouter un nouvel AVD.
    Une fenêtre s'ouvre (voir figure suivante), vous proposant de créer votre propre émulateur. On va commencer par indiquer un nom pour notre AVD dans AVD Name, histoire de pouvoir différencier vos AVD. Pour ma part, j'ai choisi 3.2_QVGA_API_7 : la taille de l'écran et la version de l'API sous laquelle fonctionnera cet AVD. Notez que certains caractères comme les caractères accentués et les espaces ne sont pas autorisés. Vous pouvez choisir la taille de l'écran à l'aide de Device. Par exemple, j'ai choisi un écran qui fait 3.2" pour une résolution de 320 * 480.
    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 OK et vous aurez une machine prête à l'emploi !
    Créez votre propre émulateur
    Créez votre propre émulateur
    Si vous utilisez Windows et que votre nom de session contient un caractère spécial, par exemple un accent, alors Eclipse vous enverra paître en déclarant qu'il ne trouve pas le fichier de configuration de l'AVD. Par exemple, un de nos lecteur avait une session qui s'appelait « Jérémie » et avait ce problème. Heureusement, il existe une solution à ce problème. Si vous utilisez Windows 7 ou Windows Vista, appuyez en même temps sur la touche Windows et sur la touche R. Si vous êtes sous Windows XP, il va falloir cliquer sur Démarrer puis sur Exécuter.
    Dans la nouvelle fenêtre qui s'ouvre, tapez « cmd » puis appuyez sur la touche Entrée de votre clavier. Une nouvelle fenêtre va s'ouvrir, elle permet de manipuler Windows en ligne de commande. Tapez cd .. puis Entrée. Maintenant, tapez dir /x. Cette commande permet de lister tous les répertoires et fichiers présents dans le répertoire actuel et aussi d'afficher le nom abrégé de chaque fichier ou répertoire. Par exemple, pour la session Administrator on obtient le nom abrégé ADMINI~1, comme le montre la figure suivante.
    La valeur à gauche est le nom réduit, alors que celle de droite est le nom entier
    La valeur à gauche est le nom réduit, alors que celle de droite est le nom entier
    Maintenant, repérez le nom réduit qui correspond à votre propre session, puis dirigez-vous vers le fichier X:\Utilisateurs\<Votre session>\.android\avd\<nom_de_votre_avd>.ini et ouvrez ce fichier. Il devrait ressembler au code suivant :
    target=android-7
    path=X:\Users\<Votre session>\.android\avd\SDZ_2.1.avd
    S'il n'y a pas de retour à la ligne entre target=android-7 et path=X:\Users\<Votre session>\.android\avd\SDZ_2.1.avd, c'est que vous n'utilisez pas un bon éditeur de texte. Utilisez le lien que j'ai donné ci-dessus.
    Enfin, il vous suffit de remplacer <Votre session> par le nom abrégé de la session que nous avions trouvé précédemment. Par exemple pour le cas de la session Administrator, je change :
    target=android-7
    path=C:\Users\Administrator\.android\avd\SDZ_2.1.avd
    en
    target=android-7
    path=C:\Users\ADMINI~1\.android\avd\SDZ_2.1.avd

    Test et configuration

    Bien, maintenant que vous avez créé un AVD, on va pouvoir vérifier qu'il fonctionne bien.
    La liste des émulateurs que connaît votre AVD Manager
    La liste des émulateurs que connaît votre AVD Manager
    Vous y voyez l'AVD que nous venons tout juste de créer. Cliquez dessus pour déverrouiller le menu de droite. Comme je n'ai pas l'intention de vraiment détailler ces options moi-même, je vais rapidement vous expliquer à quoi elles correspondent pour que vous sachiez les utiliser en cas de besoin. Les options du menu de droite sont les suivantes :
    • 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
    Les différentes options pour l'exécution de cet AVD
    Laissez les options vierges, on n'a absolument pas besoin de ce genre de détails ! Cliquez juste sur Launch.
    Enfin, votre terminal se lancera.
    Vous trouverez à droite une liste de boutons permettant d'imiter les boutons qu'aurait en temps normal un téléphone, mais que votre ordinateur n'a pas bien sûr ! Ils sont divisés en deux catégories. La première sont les contrôles de base :
    • Diminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volumeDiminuer le volume
      : Diminuer le volume.
    • Augmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volumeAugmenter le volume
      : Augmenter le volume.
    • Arrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateurArrêter l'émulateur
      : Arrêter l'émulateur.
    Et la seconde les boutons de navigation :
    • Retourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboardRetourner sur le dashboard
      : Retourner sur le dashboard (l'équivalent du bureau, avec les icônes et les widgets).
    • Ouvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menuOuvrir le menu
      : Ouvrir le menu.
    • Retour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrièreRetour arrière
      : Retour arrière.
    • Effectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une rechercheEffectuer une recherche
      : 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.
    Ouvre le menu pour accéder aux options
    Ouvre le menu pour accéder aux options
    Cliquez sur l'option Settings pour ouvrir le menu de configuration d'Android. Vous pouvez y naviguer soit en faisant glisser avec la souris (un clic, puis en laissant appuyé on dirige le curseur vers le haut ou vers le bas), soit avec la molette de votre souris. Si par mégarde vous entrez dans un menu non désiré, appuyez sur le bouton Retour présenté précédemment (une flèche qui effectue un demi-tour).
    Cliquez sur l'option Language & keyboard (voir figure suivante) ; c'est le menu qui vous permet de choisir dans quelle langue utiliser le terminal et quel type de clavier utiliser (par exemple, vous avez certainement un clavier dont les premières lettres forment le mot AZERTY, c'est ce qu'on s'appelle un clavier AZERTY. Oui, oui, les informaticiens ont beaucoup d'imagination ;) ).
    On va sélectionner Language & keyboard
    On va sélectionner Language & keyboard
    Puis, vous allez cliquer sur Select locale. Dans le prochain menu, il vous suffit de sélectionner la langue dans laquelle vous préférez utiliser Android. J'ai personnellement choisi Français (France). Voilà, un problème de réglé ! Maintenant j'utiliserai les noms français des menus pour vous orienter. Pour revenir en arrière, il faut appuyer sur le bouton Retour du menu de droite.
    Votre prochaine mission, si vous l'acceptez, sera de changer l'heure pour qu'elle s'adapte à la zone dans laquelle vous vous trouvez, et ce, par vous-mêmes. En France, nous vivons dans la zone GMT + 1. À l'heure où j'écris ces lignes, nous sommes en heure d'été, il y a donc une heure encore à rajouter. Ainsi, si vous êtes en France, en Belgique ou au Luxembourg et en heure d'été, vous devez sélectionner une zone à GMT + 2. Sinon GMT + 1 pour l'heure d'hiver. Cliquez d'abord sur Date & heure, désélectionnez Automatique, puis cliquez sur Définir fuseau horaire et sélectionnez le fuseau qui vous concerne.
    De cette manière, votre terminal sera à l'heure
    De cette manière, votre terminal sera à l'heure
    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.

    Configuration du vrai terminal

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

    Configuration du terminal

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

    Pour les utilisateurs de Windows

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

    Pour les utilisateurs de Mac

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

    Pour les utilisateurs de Linux

    La gestion des drivers USB de Linux étant beaucoup moins chaotique que celle de Windows, vous n'avez pas à télécharger de drivers. Il y a cependant une petite démarche à accomplir. On va en effet devoir ajouter au gestionnaire de périphériques une règle spécifique pour chaque appareil qu'on voudra relier. Je vais vous décrire cette démarche pour les utilisateurs d'Ubuntu :
    1. On va d'abord créer le fichier qui contiendra ces règles à l'aide de la commande sudo touch /etc/udev/rules.d/51-android.rules. touch est la commande qui permet de créer un fichier, et udev est l'emplacement des fichiers du gestionnaire de périphériques. udev conserve ses règles dans le répertoire ./rules.d.
    2. Le système vous demandera de vous identifier en tant qu'utilisateur root.
    3. Puis on va modifier les autorisations sur le fichier afin d'autoriser la lecture et l'écriture à tous les utilisateurs chmod a+rw /etc/udev/rules.d/51-android.rules.
    4. Enfin, il faut rajouter les règles dans notre fichier nouvellement créé. Pour cela, on va ajouter une instruction qui ressemblera à : SUBSYSTEM=="usb", ATTR{idVendor}=="XXXX", MODE="0666", GROUP="plugdev". Attention, on n'écrira pas exactement cette phrase.
    Est-il possible d'avoir une explication ?
    SUBSYSTEM est le mode de connexion entre le périphérique et votre ordinateur, dans notre cas on utilisera une interface USB. MODE détermine qui peut faire quoi sur votre périphérique, et la valeur « 0666 » indique que tous les utilisateurs pourront lire des informations mais aussi en écrire. GROUP décrit tout simplement quel groupe UNIX possède le périphérique. Enfin, ATTR{idVendor} est la ligne qu'il vous faudra modifier en fonction du constructeur de votre périphérique. On peut trouver quelle valeur indiquer sur la documentation. Par exemple pour mon HTC Desire, j'indique la ligne suivante :
    SUBSYSTEM=="usb", ATTR{idVendor}=="0bb4", MODE="0666", GROUP="plugdev"
    … ce qui entraîne que je tape dans la console :
    echo "SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0bb4\", MODE=\"0666\", GROUP=\"plugdev\"" >> /etc/udev/rules.d/51-android.rules
    Si cette configuration ne vous correspond pas, je vous invite à lire la documentation de udev afin de créer votre propre règle.

    Et après ?

    Maintenant que votre ordinateur peut reconnaître votre téléphone, on va faire en sorte que votre téléphone puisse exécuter des applications que vous avez développé et exécuter un debugger. Pour cela, faites comme pour l'AVD et allez dans les options.
    Pour les versions les plus anciennes d'Android
    Commencez par vous diriger vers l'option Application :
    Choisissez l'option Applications
    Choisissez l'option Applications
    Dans le menu qui vient de s'ouvrir, il vous faudra activer les Sources Inconnues. Une fois que c'est fait, allez dans le menu Développement :
    Activez les Sources Inconnues et allez dans le menu Développement
    Activez les Sources Inconnues et allez dans le menu Développement
    Enfin, dans l'écran qui s'ouvre, sélectionnez les options pour que votre écran ressemble à celui-là :
    Votre écran doit ressembler à celui-là
    Votre écran doit ressembler à celui-là
    Pour les autres, avec une version plus récente
    Vous vous trouvez aussi dans les options, mais elles ont un look différent. Dirigez-vous vers le menu Sécurité :
    Le menu ressemble à ça sous Android Jelly Bean
    Le menu ressemble à ça sous Android Jelly Bean
    C'est ici que vous pourrez activer les applications de sources inconnues en cliquant sur l'option prévues à cet effet :
    Ne touchez pas aux autres options
    Ne touchez pas aux autres options
    Retournez maintenant au menu des options. Attention ça va devenir un peu bizarre. Si vous ne voyez pas l'option Options pour les développeurs, sélectionnez A propos du téléphone, le dernier item de la liste :
    Il s'agit de la toute dernière option du menu, mais pas besoin de l'ouvrir si Options pour les développeurs est déjà là
    Il s'agit de la toute dernière option du menu, mais pas besoin de l'ouvrir si Options pour les développeurs est déjà là
    Naviguez tout en bas de cette page et appuyez sur Numéro de Build. Sept fois. C'est pas une blague, appuyez sur ce bouton sept fois :
    Il faut appuyer sept fois sur ce bouton là, même si c'est bizarre
    Il faut appuyer sept fois sur ce bouton là, même si c'est bizarre
    Félicitations ! Votre téléphone vous considère comme un développeur ! On va maintenant lui montrer qui est le patron (vous pour ceux qui suivent pas :euh: ). Retournez dans le menu précédent et une nouvelle option est apparue : Options pour les développeurs. C'est votre prochaine destination :
    Ce nouveau menu est ouvert, entrez-y
    Ce nouveau menu est ouvert, entrez-y
    Et enfin, dans ce menu, sélectionnez l'option Débogage USB et vous serez prêt :
    Activez cette option et vous aurez fini
    Activez cette option et vous aurez fini
    • 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.

      Votre première application

      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 débute forcément par un main, vous risquez d'être surpris !
      On va tout d'abord voir ce qu'on appelle des activités et comment les manipuler. Sachant que la majorité de vos applications (si ce n'est toutes) contiendront plusieurs activités, il est indispensable que vous maîtrisiez ce concept ! Nous verrons aussi ce que sont les vues et nous créerons enfin notre premier projet — le premier d'une grande série — qui n'est pas, de manière assez surprenante, un Hello World! . Enfin presque !

      Activité et vue

      Qu'est-ce qu'une activité ?

      Si vous observez un peu l'architecture de la majorité des applications Android, vous remarquerez une construction toujours à peu près similaire. Prenons par exemple l'application du Play Store. Vous avez plusieurs fenêtres à l'intérieur même de cette application : si vous effectuez une recherche, une liste de résultats s'affichera dans une première fenêtre et si vous cliquez sur un résultat, une nouvelle fenêtre s'ouvre pour vous afficher la page de présentation de l'application sélectionnée. Au final, on remarque qu'une application est un assemblage de fenêtres entre lesquelles il est possible de naviguer.
      Ces différentes fenêtres sont appelées des activités. Un moyen efficace de différencier des activités est de comparer leur interface graphique : si elles sont radicalement différentes, c'est qu'il s'agit d'activités différentes. De plus, comme une activité remplit tout l'écran, votre application ne peut en afficher qu'une à la fois. La figure suivante illustre ce concept.
      En cliquant sur un élément de la liste à gauche, on ouvre une nouvelle activité
      En cliquant sur un élément de la liste à gauche, on ouvre une nouvelle activité
      Faire une recherche ouvre une liste de resultats, comme dans l'écran de gauche. Toucher un résultat de la recherche ouvre un nouvel écran, dont l'interface graphique n'a rien à voir avec le précédent. Il s'agit donc de deux activités différentes.
      Voici un autre example. Imaginez que vous naviguiez sur OpenClassrooms 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.
      En d'autres termes, 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 et elle va établir les liens entre l'interface graphique et le logique derrière les éléments qui la constitue.
      De plus, une activité contient des informations sur l'état actuel de l'application : ces informations s'appellent le context. Ce context constitue un lien avec le système Android ainsi que les autres activités de l'application, comme le montre la figure suivante.
      Une activité est constituée du contexte de l'application et d'une seule et unique interface graphique
      Une activité est constituée du contexte de l'application et d'une seule et unique interface graphique

      États d'une activité

      Si un utilisateur reçoit un appel, il devient plus important qu'il puisse y répondre que de faire en sorte qu'il puisse écouter 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
      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 au-dessus qui l'a. Pour que notre application récupère le focus, l'utilisateur devra se débarrasser de l'application partiellement au-dessus pour que l'utilisateur puisse à nouveau interagir avec notre activité.
      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 invisible pour l'utilisateur, car une autre activité prend toute la place sur l'écran.
      L'application n'a évidemment plus le focus, et puisque l'utilisateur ne peut pas la voir, il ne peut pas agir dessus.
      Le système retient son état pour pouvoir reprendre, mais il peut arriver que le système tue votre application pour libérer de la mémoire système.
      Mais j'ai pourtant déjà vu des systèmes Android avec deux applications visibles en même temps !
      Ah oui, c'est possible. Mais il s'agit d'un artifice, il n'y a vraiment qu'une application qui est active. Pour faciliter votre compréhension, je vous conseille d'oublier ces systèmes.

      Cycle de vie d'une activité

      Une activité n'a pas de contrôle direct sur son propre état (et par conséquent vous non plus en tant que programmeur), il s'agit plutôt d'un cycle rythmé par les interactions avec le système et d'autres applications. Voici un schéma qui présente ce que l'on appelle le cycle de vie d'une activité, c'est-à-dire qu'il indique les étapes que va traverser notre activité pendant sa vie, de sa naissance à sa mort. Vous verrez que chaque étape du cycle est représentée par une méthode. Nous verrons comment utiliser ces méthodes en temps voulu.
      Cycle de vie d'une activité
      Cycle de vie d'une activité
      Pour rappel, un package est un répertoire qui permet d'organiser notre code source, un récipient dans lequel nous allons mettre nos classes de façon à pouvoir trier votre code et différencier des classes qui auraient le même nom. Concrètement, supposez que vous ayez à créer deux classes X — qui auraient deux utilisations différentes, bien sûr. Vous vous rendez bien compte que vous seriez dans l'incapacité totale de différencier les deux classes si vous deviez instancier un objet de l'une des deux classes X, et Java vous houspillera en déclarant qu'il ne peut pas savoir à quelle classe vous faites référence. C'est exactement comme avoir deux fichiers avec le même nom et la même extension dans un même répertoire : c'est impossible car c'est incohérent.
      Pour contrer ce type de désagrément, on organise les classes à l'aide d'une hiérarchie. Si je reprends mon exemple des deux classes X, je peux les placer dans deux packages différents Y et Z par exemple, de façon à ce que vous puissiez préciser dans quel package se trouve la classe X sollicitée. On utilisera la syntaxe Y.X pour la classe X qui se trouve dans le package Y et Z.X pour la classe X qui se trouve dans le package Z. Dans le cas un peu farfelu du code source d'un navigateur internet, on pourrait trouver les packages Web.Affichage.Image, Web.Affichage.Video et Web.Telechargement.
      Les vues (que nos amis anglais appellent view), sont ces fameux composants qui viendront se greffer sur notre échafaudage, il s'agit de l'unité de base de l'interface graphique. Leur rôle est de fournir du contenu visuel avec lequel il est éventuellement possible d'interagir. À l'instar de l'interface graphique en Java, il est possible de disposer les vues à l'aide de conteneurs, nous verrons comment plus tard.

      Un non-Hello world!

      Vous trouverez les fichiers créés dans le panneau de gauche (voir figure suivante). Vous pouvez aussi rechercher la classe à laquelle vous souhaitez accéder avec le raccourcis CTRL + N.
      Le navigateur de fichiers
      Le navigateur de fichiers
      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 PremiereActivite.java (en double cliquant dessus). Vous devriez avoir un contenu plus ou moins similaire à celui-ci :
      package com.openclassrooms.fr.premierprojet;
      import android.app.Activity;
      import android.os.Bundle;
      import android.view.Menu;
      import android.view.MenuItem;
      public class PremiereActivite extends Activity {
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_premiere_activite);
          }
          @Override
          public boolean onCreateOptionsMenu(Menu menu) {
              // Inflate the menu; this adds items to the action bar if it is present.
              getMenuInflater().inflate(R.menu.premiere_activitemenu);
              return true;
          }
          @Override
          public boolean onOptionsItemSelected(MenuItem item) {
              // Handle action bar item clicks here. The action bar will
              // automatically handle clicks on the Home/Up button, so long
              // as you specify a parent activity in AndroidManifest.xml.
              int id = item.getItemId();
              if (id == R.id.action_settings) {
                  return true;
              }
              return super.onOptionsItemSelected(item);
          }
      }
      Ah ! On reconnaît certains termes que je viens tout juste d'expliquer ! Cependant, Android Studio a fait du zèle et a créé des choses que je n'expliquerai que plus tard, alors je vais faire quelque chose que je n'aime pas faire, mais je vais vous demander de modifier du code sans comprendre pourquoi. Supprimez les méthodes onCreateOptionsMenu et onOptionsItemSelected afin d'obtenir :
      package com.openclassrooms.fr.premierprojet;
      import android.app.Activity;
      import android.os.Bundle;
      import android.view.Menu;
      import android.view.MenuItem;
      public class PremiereActivite extends Activity {
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_premiere_activite);
          }
      }
      Je vais prendre toutes les lignes une par une, histoire d'être certain de ne déstabiliser personne.
      package com.openclassrooms.fr.premierprojet;
      Là, on déclare que notre programme se situe dans le package com.openclassrooms.fr.premierprojet. Si on veut faire référence à notre application, il faudra faire référence à ce package.
      import android.app.Activity;
      import android.os.Bundle;
      import android.view.Menu;
      import android.view.MenuItem;
      On importe des classes qui se trouvent dans des packages différents : les classes Activity, Bundle, et Menu et MenuItem qui se trouvent dans le même package. Deux de ces packages sont inutiles car nous avons supprimé des fonctions dans le code, comme le montre l'image suivante.
      Les imports inutiles sont en gris
      Les imports inutiles sont en gris
      Pour résoudre ce problème, on va utiliser le raccourci clavier le plus magique d'Android Studio : ALT + ENTREE. Ce raccourci permet d'effectuer des correstions d'erreurs rapidement, ainsi que d'autres actions rapides. Ici, on vous propose de Optimize Imports. Appuyez sur ENTREE pour valider votre sélection.
      public class PremiereActivite extends Activity {
        //…
      }
      On déclare ici une nouvelle classe, PremiereActivity, et on la fait dériver de Activity, puisqu'il s'agit d'une activité.
      @Override
      protected 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érite 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.
    L'instruction suivante :
    setContentView(R.layout.activity_premiere_activite);
    sera expliquée en partie dans quelques instants.
    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 PremiereActivite extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            TextView texte = new TextView(this);
            texte.setText("Bonjour, vous me devez 1 000 000€.");
            setContentView(texte);
        }
    }
    Nous avons créé un object que j'ai appelé texte . Cet object 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.

    Lancement de l'application

    Souvenez-vous, je vous ai dit précédemment qu'il était préférable de ne pas fermer l'AVD, celui-ci étant long à se lancer. Si vous l'avez fermé, ce n'est pas grave, il s'ouvrira tout seul.
    Pour lancer notre application, regardez la barre d'outils d'Eclipse et cherchez l'encart visible à la figure suivante.
    Les outils pour exécuter votre code
    Les outils pour exécuter votre code
    Il vous suffit de cliquer sur le deuxième bouton (celui qui ressemble au symbole « play »). Si une fenêtre s'ouvre (voir figure suivante), sélectionnez Android Application.
    Sélectionnez « Android Application »
    Sélectionnez « Android Application »
    Si vous avez un ou plusieurs AVD lancés et/ou un terminal de connecté, alors cette écran apparaîtra :
    Vous pouvez choisir où lancer l'application
    Vous pouvez choisir où lancer l'application
    A partir de celui-ci, il vous est possible de choisir où vous souhaitez que l'application soit exécutée. Par exemple, moi je désire qu'elle le soit sur mon téléphone, je clique donc sur samsung-gt puis sur ok.
    Et voilà ! L'utilisateur vous doit 1 000 000 € !
    La présentation peut être différente chez vous en fonction de votre version d'Android
    La présentation peut être différente chez vous en fonction de votre version d'Android
    • 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.

      Les ressources

      <
      Je vous ai déjà présenté le répertoire src/ qui contient toutes les sources de votre programme. On va maintenant s'intéresser à un autre grand répertoire : res/. Vous l'aurez compris, c'est dans ce répertoire que sont conservées les ressources, autrement dit les éléments qui s'afficheront à l'écran ou avec lesquels l'utilisateur pourra interagir.
      Android est destiné à être utilisé sur un très grand nombre de supports différents, et il faut par conséquent s'adapter à ces supports. Imaginons qu'une application ait à afficher une image. Si on prend une petite image, il faut l’agrandir pour qu'elle n'ait pas une dimension ridicule sur un grand écran. Mais en faisant cela, l'image perdra en qualité. Une solution serait donc d'avoir une image pour les petits écrans, une pour les écrans moyens et une pour les grands écrans. C'est ce genre de précautions qu'il faut prendre quand on veut développer pour les appareils mobiles.
      Un des moyens d'adapter nos applications à tous les terminaux est d'utiliser les ressources. Les ressources sont des fichiers organisés d'une manière particulière de façon à ce qu'Android sache quelle ressource utiliser pour s'adapter au matériel sur lequel s'exécute l’application. Comme je l'ai dit précédemment, adapter nos applications à tous les types de terminaux est indispensable. Cette adaptation passe par la maîtrise des ressources.
      Pour déclarer des ressources, on passe très souvent par le format XML, c'est pourquoi un point sur ce langage est nécessaire.

      Le format XML

      Les langages de balisage

      Le XML est un langage de balisage un peu comme le HTML — le HTML est d'ailleurs indirectement un dérivé du XML. Le principe d'un langage de programmation (Java, C++, etc.) est d'effectuer des calculs, puis éventuellement de mettre en forme le résultat de ces calculs dans une interface graphique. À l'opposé, un langage de balisage (XML, donc) n'effectue ni calcul, ni affichage, mais se contente de mettre en forme des informations. Concrètement, un langage de balisage est une syntaxe à respecter, de façon à ce qu'on sache de manière exacte la structuration d'un fichier.
      Et si on connaît l'architecture d'un fichier, alors il est très facile de retrouver l'emplacement des informations contenues dans ce fichier et de pouvoir les exploiter. Ainsi, il est possible de développer un programme appelé interpréteur qui récupérera les données d'un fichier (structuré à l'aide d'un langage de balisage).
      Par exemple pour le HTML, c'est un navigateur qui interprète le code afin de donner un sens aux instructions ; si vous lisez un document HTML sans interpréteur, vous ne verrez que les sources, pas l'interprétation des balises.
      Un exemple pratique
      Imaginons un langage de balisage très simple, que j'utilise pour stocker mes contacts téléphoniques :
      Anaïs Romain Thomas Xavier
      Ce langage est très simple : les prénoms de mes contacts sont séparés par une espace. Ainsi, je peux dire à mon ordinateur "Ce texte ne contient que des prénoms. Si tu trouves un espace, c'est qu'on est arrivé à la fin d'un prénom et qu'on va en trouver un autre". Quand je demanderai à mon interpréteur de lire le fichier, il saura que j'ai 4 contacts parce que les prénoms sont séparés par des espaces. Il lit une suite de caractères et dès qu'il tombe sur une espace, il sait qu'on va passer à un autre prénom.
      On va maintenant rendre les choses plus complexes pour introduire les numéros de téléphone :
      Anaïs : 1111111111
      Romain: 2222222222
      Thomas: 3333333333
      Xavier: 4444444444
      Là, l'interpréteur sait que pour chaque ligne, la première suite de caractères correspond à un prénom qui se termine par un deux-points, puis on trouve le numéro de téléphone qui se termine par un retour à la ligne. Et, si j'ai bien codé mon interpréteur, il sait que le premier prénom est « Anaïs » sans prendre l'espace à la fin, puisque ce n'est pas un caractère qui rentre dans la composition d'un prénom.
      Si j'avais écrit mon fichier sans syntaxe particulière à respecter, alors il m'aurait été impossible de développer un interpréteur qui puisse retrouver les informations.

      La syntaxe XML

      Comme pour le format HTML, un fichier XML débute par une déclaration qui permet d'indiquer qu'on se trouve bien dans un fichier XML.
      <?xml version="1.0" encoding="utf-8"?>
      Cette ligne permet d'indiquer que :
    • 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 Song Of Ice And Fire: 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 (dans notre cas </bibliotheque>), auquel cas vous pourrez avoir du texte ou d'autres balises 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 !

    Les différents types de ressources

    Les ressources sont des éléments capitaux dans une application Android. On y trouve par exemple des chaînes de caractères ou des images. Comme Android est destiné à être utilisé sur une grande variété de supports, il fallait trouver une solution pour permettre à une application de s'afficher de la même manière sur un écran 7" que sur un écran 10", ou faire en sorte que les textes s'adaptent à la langue de l'utilisateur. C'est pourquoi les différents éléments qui doivent s'adapter de manière très précise sont organisés de manière tout aussi précise, de façon à ce qu'Android sache quels éléments utiliser pour quels types de terminaux.
    On découvre les ressources à travers une hiérarchie particulière de répertoires. Vous pouvez remarquer qu'à la création d'un nouveau projet, Eclipse crée certains répertoires par défaut, comme le montre la figure suivante.
    L'emplacement des ressources au sein du projet
    L'emplacement des ressources au sein du 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
    Présence de fichiers XML
    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 Présence de fichiers XML  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, les principales sont déjà là.

    L'organisation

    Si vous êtes observateurs, vous avez remarqué sur l'image précédente que nous avions quatre 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, v13, 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 résolution moyenne ? Il ira chercher dans res/drawable-mdpi/ ! L'écran du terminal de l'utilisateur a une petite résolution ? Eh bien… il ira chercher dans res/drawable-mdpi 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 quatre répertoires drawable/, sinon Android ne pourrait pas la trouver pour les quatre types de configuration.

    Ajouter un fichier avec Eclipse

    Heureusement, les développeurs d'Android Studio 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 le répertoire src/. Vous aurez un menu un peu similaire à celui représenté à l'image suivante, qui s'affichera.
    L'ADT permet d'ajouter des répertoires facilement
    L'ADT permet d'ajouter des répertoires facilement
    Dans le sous-menu New, cliquez sur Android Resource File. Cette opération ouvrira un assistant de création de fichiers XML visible à la figure suivante.
    L'assistant de création de fichiers XML
    L'assistant de création de fichiers XML
    Le premier champ vous permet de sélectionner le type de ressources désiré. Vous retrouverez les noms des ressources que nous avons décrites dans le premier tableau, ainsi que d'autres qui nous intéressent moins, à l'exception de raw puisqu'il n'est pas destiné à contenir des fichiers XML. À chaque fois que vous changez de type de ressources, la seconde partie de l'écran change et vous permet de choisir plus facilement quel genre de ressources vous souhaitez créer. Par exemple pour Layout, vous pouvez choisir de créer un bouton (Button) ou un encart de texte (TextView). Vous pouvez ensuite choisir dans quel projet vous souhaitez ajouter le fichier. Le champ File vous permet quant à lui de choisir le nom du fichier à créer.
    Une fois votre sélection faite, vous pouvez cliquer sur Next pour passer à l'écran suivant (voir figure suivante) qui vous permettra de choisir des quantificateurs pour votre ressource ou Finish pour que le fichier soit créé dans un répertoire sans quantificateurs.
    Cette fenêtre vous permet de choisir des quantificateurs pour votre ressource
    Cette fenêtre vous permet de choisir des quantificateurs pour votre ressource
    Cette section contient deux listes. Celle de gauche présente les quantificateurs à appliquer au répertoire de destination. Vous voyez qu'ils sont rangés dans l'ordre de priorité que j'ai indiqué.
    Mais il y a beaucoup plus de quantificateurs et de ressources que ce que tu nous as indiqué !
    Oui. Je n'écris pas une documentation officielle pour Android. Si je le faisais, j'en laisserais plus d'un confus et vous auriez un nombre impressionnant d'informations qui ne vous serviraient pas ou peu. Je m'attelle à vous apprendre à faire de jolies applications optimisées et fonctionnelles, pas à faire de vous des encyclopédies vivantes d'Android. ;)
    Le champ suivant, Folder, est le répertoire de destination. Quand vous sélectionnez des quantificateurs, vous pouvez avoir un aperçu en temps réel de ce répertoire. Si vous avez commis une erreur dans les quantificateurs, par exemple choisi une langue qui n’existe pas, le quantificateur ne s'ajoutera pas dans le champ du répertoire. Si ce champ ne vous semble pas correct vis-à-vis des quantificateurs sélectionnés, c'est que vous avez fait une faute d'orthographe. Si vous écrivez directement un répertoire dans Folder, les quantificateurs indiqués s'ajouteront dans la liste correspondante.
    Cet outil peut gérer les erreurs et conflits. Si vous indiquez comme nom « strings » et comme ressource une donnée (« values »), vous verrez un petit avertissement qui s'affichera en haut de la fenêtre, puisque ce fichier existe déjà (il est créé par défaut).

    Petit exercice

    Vérifions que vous avez bien compris : essayez, sans passer par les outils d'automatisation, d'ajouter une mise en page destinée à la version 8, quand l'utilisateur penche son téléphone en mode portrait alors qu'il utilise le français des Belges (fr-rBE) et que son terminal a une résolution moyenne.
    Le Folder doit contenir exactement/res/layout-fr-rBE-port-mdpi-v8.
    Il vous suffit de cliquer sur Finish si aucun message d'erreur ne s'affiche.

    Récupérer une ressource

    La classe R

    On peut accéder à cette classe qui se trouve dans le répertoire gen/ (comme generated, c'est-à-dire que tout ce qui se trouvera dans ce répertoire sera généré automatiquement), comme indiqué à la figure suivante.
    On retrouve le fichier R.java dans gen/<votre_package>/
    On retrouve le fichier R.java dans gen/<votre_package>/
    Ouvrez donc ce fichier et regardez le contenu.
    public final class R {
      public static final class attr {
      }
      public static final class dimen {
        public static final int padding_large=0x7f040002;
        public static final int padding_medium=0x7f040001;
        public static final int padding_small=0x7f040000;
      }
      public static final class drawable {
        public static final int ic_action_search=0x7f020000;
        public static final int ic_launcher=0x7f020001;
      }
      public static final class id {
        public static final int menu_settings=0x7f080000;
      }
      public static final class layout {
        public static final int activity_main=0x7f030000;
      }
      public static final class menu {
        public static final int activity_main=0x7f070000;
      }
      public static final class string {
        public static final int app_name=0x7f050000;
        public static final int hello_world=0x7f050001;
        public static final int menu_settings=0x7f050002;
        public static final int title_activity_main=0x7f050003;
      }
      public static final class style {
        public static final int AppTheme=0x7f060000;
      }
    }
    Ça vous rappelle quelque chose ? Comparons avec l'ensemble des ressources que comporte notre projet (voir figure suivante).
    Tiens, ces noms me disent quelque chose…
    Tiens, ces noms me disent quelque chose…
    On remarque en effet une certaine ressemblance, mais elle n'est pas parfaite ! Décryptons certaines lignes de ce code.
    La classe layout
    public static final class layout { 
      public static final int activity_main=0x7f030000
    }
    Il s'agit d'une classe déclarée dans une autre classe : c'est ce qui s'appelle une classe interne. La seule particularité d'une classe interne est qu'elle est déclarée dans une autre classe, mais elle peut agir comme toutes les autres classes. Cependant, pour y accéder, il faut faire référence à la classe qui la contient. Cette classe est de type public static final et de nom layout.
    • 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.

      Constitution des interfaces graphiques

      Bien, maintenant que vous avez compris le principe et l'utilité des ressources, voyons comment appliquer nos nouvelles connaissances aux interfaces graphiques. Avec la diversité des machines sous lesquelles fonctionne Android, il faut vraiment exploiter toutes les opportunités offertes par les ressources pour développer des applications qui fonctionneront sur la majorité des terminaux.
      Une application Android polyvalente possède un fichier XML pour chaque type d'écran, de façon à pouvoir s'adapter. En effet, si vous développez une application uniquement à destination des petits écrans, les utilisateurs de tablettes trouveront votre travail illisible et ne l'utiliseront pas du tout. Ici on va voir un peu plus en profondeur ce que sont les vues, comment créer des ressources d'interface graphique et comment récupérer les vues dans le code Java de façon à pouvoir les manipuler.

      L'interface d'Eclipse

      La bonne nouvelle, c'est qu'Eclipse nous permet de créer des interfaces graphiques à la souris. Il est en effet possible d'ajouter un élément et de le positionner grâce à sa souris. La mauvaise, c'est que c'est beaucoup moins précis qu'un véritable code et qu'en plus l'outil est plutôt buggé. Tout de même, voyons voir un peu comment cela fonctionne.
      Ouvrez le seul fichier qui se trouve dans le répertoire res/layout. Il s'agit normalement du fichier activity_main.xml. Une fois ouvert, vous devriez avoir quelque chose qui ressemble à la figure suivante.
      Le fichier est ouvert
      Le fichier est ouvert
      Cet outil vous aide à mettre en place les vues directement dans le layout de l'application, représenté par la fenêtre du milieu. Comme il ne peut remplacer la manipulation de fichiers XML, je ne le présenterai pas dans les détails. En revanche, il est très pratique dès qu'il s'agit d'afficher un petit aperçu final de ce que donnera un fichier XML.

      Présentation de l'outil

      C'est à l'aide du menu en haut, celui visible à la figure suivante, que vous pourrez observer le résultat avec différentes options.
      Menu d'options
      Menu d'options
      Ce menu est divisé en deux parties : les icônes du haut et celles du bas. Nous allons nous concentrer sur les icônes du haut pour l'instant (voir figure suivante).
      Les icônes du haut du menu d'options
      Les icônes du haut du menu d'options
      • 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×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
      Les icônes de gauche du bas menu
      Ces boutons sont spécifiques à un composant et à son layout parent, contrairement aux boutons précédents qui étaient spécifiques à l'outil. Ainsi, si vous ne sélectionnez aucune vue, ce sera la vue racine qui sera sélectionnée par défaut. Comme les boutons changent en fonction du composant et du layout parent, je ne vais pas les présenter en détail.
      Enfin l'ensemble de boutons de droite, visibles à la figure suivante.
      Les icônes de droite du bas menu
      Les icônes de droite du bas menu
      • 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
      Un menu apparaît lors d'un clic droit sur une vue
      Vous comprendrez plus tard la signification de ces termes, mais retenez bien qu'il est possible de modifier les attributs via un clic droit. Vous pouvez aussi utiliser l'encart Properties en bas à droite (voir figure suivante).
      L'encart « Properties »
      L'encart « Properties »
      De plus, vous pouvez placer différentes vues en cliquant dessus depuis le menu de gauche, puis en les déposant sur l'activité, comme le montre la figure suivante.
      Il est possible de faire un cliquer/glisser
      Il est possible de faire un cliquer/glisser
      Il vous est ensuite possible de les agrandir, de les rapetisser ou de les déplacer en fonction de vos besoins, comme le montre la figure suivante.
      Vous pouvez redimensionner les vues
      Vous pouvez redimensionner les vues
      Nous allons maintenant voir la véritable programmation graphique. Pour accéder au fichier XML correspondant à votre projet, cliquez sur le deuxième onglet activity_main.xml.

      Règles générales sur les vues

      Différenciation entre un layout et un widget

      Normalement, Eclipse vous a créé un fichier XML par défaut :
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
      
        <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_centerHorizontal="true"
          android:layout_centerVertical="true"
          android:padding="@dimen/padding_medium"
          android:text="@string/hello_world"
          tools:context=".MainActivity" />
      
      </RelativeLayout>
      
      La racine possède deux attributs similaires : xmlns:android="http://schemas.android.com/apk/res/android" et xmlns:tools="http://schemas.android.com/tools". Ces deux lignes permettent d'utiliser des attributs spécifiques à Android. Si vous ne les mettez pas, vous ne pourrez pas utiliser les attributs et le fichier XML sera un fichier XML banal au lieu d'être un fichier spécifique à Android. De plus, Eclipse refusera de compiler.
      On trouve ensuite une racine qui s'appelle RelativeLayout. Vous voyez qu'elle englobe un autre nœud qui s'appelle TextView. Ah ! Ça vous connaissez ! Comme indiqué précédemment, une interface graphique pour Android est constituée uniquement de vues. Ainsi, tous les nœuds de ces fichiers XML seront des vues.
      Revenons à la première vue qui en englobe une autre. Avec Swing vous avez déjà rencontré ces objets graphiques qui englobent d'autres objets graphiques. On les appelle en anglais des layouts et en français des gabarits. Un layout est donc une vue spéciale qui peut contenir d'autres vues et qui n'est pas destinée à fournir du contenu ou des contrôles à l'utilisateur. Les layouts se contentent de disposer les vues d'une certaine façon. Les vues contenues sont les enfants, la vue englobante est le parent, comme en XML. Une vue qui ne peut pas en englober d'autres est appelée un widget (composant, en français).
      Vous pouvez bien sûr avoir en racine un simple widget si vous souhaitez que votre mise en page consiste en cet unique widget.

      Attributs en commun

      Comme beaucoup de nœuds en XML, une vue peut avoir des attributs, qui permettent de moduler certains de ses aspects. Certains de ces attributs sont spécifiques à des vues, d'autres sont communs. Parmi ces derniers, les deux les plus courants sont layout_width, qui définit la largeur que prend la vue (la place sur l'axe horizontal), et layout_height, qui définit la hauteur qu'elle prend (la place sur l'axe vertical). Ces deux attributs peuvent prendre une valeur parmi les trois suivantes :
      • 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
      Il est possible de définir une marge interne pour chaque widget
      Ci-dessous avec l'attribut android:padding dans le fichier XML pour définir un carré d'espacement ; la valeur sera suivie d'une unité, 10.5dp par exemple.
      <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="10.5dp"
        android:text="@string/hello" />
      
      La méthode Java équivalente est public void setPadding (int left, int top, int right, int bottom).
      textView.setPadding(15, 105, 21, 105);
      
      En XML on peut aussi utiliser des attributs android:paddingBottom pour définir uniquement l'espacement avec le plancher, android:paddingLeft pour définir uniquement l'espacement entre le bord gauche du widget et le contenu, android:paddingRight pour définir uniquement l'espacement de droite et enfin android:paddingTop pour définir uniquement l'espacement avec le plafond.

      Identifier et récupérer des vues

      Identification

      Vous vous rappelez certainement qu'on a dit que certaines ressources avaient un identifiant. Eh bien, il est possible d'accéder à une ressource à partir de son identifiant à l'aide de la syntaxe @X/Y. Le @ signifie qu'on va parler d'un identifiant, le X est la classe où se situe l'identifiant dans R.java et enfin, le Y sera le nom de l'identifiant. Bien sûr, la combinaison X/Y doit pointer sur un identifiant qui existe. Reprenons notre classe créée par défaut :
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
      
        <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_centerHorizontal="true"
          android:layout_centerVertical="true"
          android:padding="@dimen/padding_medium"
          android:text="@string/hello_world"
          tools:context=".MainActivity" />
      
      </RelativeLayout>
      
      On devine d'après la ligne surlignée que le TextView affichera le texte de la ressource qui se trouve dans la classe String de R.java et qui s'appelle hello_world. Enfin, vous vous rappelez certainement aussi que l'on a récupéré des ressources à l'aide de l'identifiant que le fichier R.java créait automatiquement dans le chapitre précédent. Si vous allez voir ce fichier, vous constaterez qu'il ne contient aucune mention à nos vues, juste au fichier activity_main.xml. Eh bien, c'est tout simplement parce qu'il faut créer cet identifiant nous-mêmes (dans le fichier XML hein, ne modifiez jamais R.java par vous-mêmes, malheureux !).
      Afin de créer un identifiant, on peut rajouter à chaque vue un attribut android:id. La valeur doit être de la forme @+X/Y. Le + signifie qu'on parle d'un identifiant qui n'est pas encore défini. En voyant cela, Android sait qu'il doit créer un attribut.
      Le X est la classe dans laquelle sera créé l'identifiant. Si cette classe n'existe pas, alors elle sera créée. Traditionnellement, X vaut id, mais donnez-lui la valeur qui vous plaît. Enfin, le Y sera le nom de l'identifiant. Cet identifiant doit être unique au sein de la classe, comme d'habitude.
      Par exemple, j'ai décidé d'appeler mon TextView « text » et de changer le padding pour qu'il vaille 25.7dp, ce qui nous donne :
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
      
        <TextView
          android:id="@+id/text"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_centerHorizontal="true"
          android:layout_centerVertical="true"
          android:padding="25.7dp"
          android:text="@string/hello_world"
          tools:context=".MainActivity" />
      
      </RelativeLayout>
      
      Dès que je sauvegarde, mon fichier R sera modifié automatiquement :
      public final class R {
        public static final class attr {
        }
        public static final class dimen {
          public static final int padding_large=0x7f040002;
          public static final int padding_medium=0x7f040001;
          public static final int padding_small=0x7f040000;
        }
        public static final class drawable {
          public static final int ic_action_search=0x7f020000;
          public static final int ic_launcher=0x7f020001;
        }
        public static final class id {
          public static final int menu_settings=0x7f080000;
        }
        public static final class layout {
          public static final int activity_main=0x7f030000;
        }
        public static final class menu {
          public static final int activity_main=0x7f070000;
        }
        public static final class string {
          public static final int app_name=0x7f050000;
          public static final int hello_world=0x7f050001;
          public static final int menu_settings=0x7f050002;
          public static final int title_activity_main=0x7f050003;
        }
        public static final class style {
          public static final int AppTheme=0x7f060000;
        }
      }
      

      Instanciation des objets XML

      Enfin, on peut utiliser cet identifiant dans le code, comme avec les autres identifiants. Pour cela, on utilise la méthode public View findViewById (int id). Attention, cette méthode renvoie une View, il faut donc la « caster » dans le type de destination.
      On caste ? Aucune idée de ce que cela peut vouloir dire !
      Petit rappel en ce qui concerne la programmation objet : quand une classe Classe_1 hérite (ou dérive, on trouve les deux termes) d'une autre classe Classe_2, il est possible d'obtenir un objet de type Classe_1 à partir d'un de Classe_2 avec le transtypage. Pour dire qu'on convertit une classe mère (Classe_2) en sa classe fille (Classe_1) on dit qu'on casteClasse_2 en Classe_1, et on le fait avec la syntaxe suivante :
      //avec « class Class_1 extends Classe_2 »
      Classe_2 objetDeux = null;
      Classe_1 objetUn = (Classe_1) objetDeux;
      
      Ensuite, et c'est là que tout va devenir clair, vous pourrez déclarer que votre activité utilise comme interface graphique la vue que vous désirez à l'aide de la méthode void setContentView (View view). Dans l'exemple suivant, l'interface graphique est référencée par R.layout.activity_main, il s'agit donc du layout d'identifiant main, autrement dit celui que nous avons manipulé un peu plus tôt.
      import android.app.Activity;
      import android.os.Bundle;
      import android.widget.TextView;
      
      public class TroimsActivity extends Activity {
        TextView monTexte = null;
       
        @Override
        public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
           
          monTexte = (TextView)findViewById(R.id.text);
          monTexte.setText("Le texte de notre TextView");
        }
      }
      
      Je peux tout à fait modifier le padding a posteriori.
      import android.app.Activity;
      import android.os.Bundle;
      import android.widget.TextView;
      
      public class TroimsActivity extends Activity {
        TextView monTexte = null;
       
        @Override
        public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
           
          monTexte = (TextView)findViewById(R.id.text);
          // N'oubliez pas que cette fonction n'utilise que des entiers
          monTexte.setPadding(50, 60, 70, 90);
        }
      }
      
      Y a-t-il une raison pour laquelle on accède à la vue après le setContentView ?
      Oui ! Essayez de le faire avant, votre application va planter.
      En fait, à chaque fois qu'on récupère un objet depuis un fichier XML dans notre code Java, on procède à une opération qui s'appelle la désérialisation. Concrètement, la désérialisation, c'est transformer un objet qui n'est pas décrit en Java − dans notre cas l'objet est décrit en XML − en un objet Java réel et concret. C'est à cela que sert la fonction View findViewById (int id). Le problème est que cette méthode va aller chercher dans un arbre de vues, qui est créé automatiquement par l'activité. Or, cet arbre ne sera créé qu'après le setContentView ! Donc le findViewById retournera null puisque l'arbre n'existera pas et l'objet ne sera donc pas dans l'arbre. On va à la place utiliser la méthode static View inflate (Context context, int id, ViewGroup parent). Cette méthode va désérialiser l'arbre XML au lieu de l'arbre de vues qui sera créé par l'activité.
      import android.app.Activity;
      import android.os.Bundle;
      import android.widget.RelativeLayout;
      import android.widget.TextView;
      
      public class TroimsActivity extends Activity {
        RelativeLayout layout = null;
        TextView text = null;
      
        @Override
        public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
      
          // On récupère notre layout par désérialisation. La méthode inflate retourne un View
          // C'est pourquoi on caste (on convertit) le retour de la méthode avec le vrai type de notre layout, c'est-à-dire RelativeLayout
          layout = (RelativeLayout) RelativeLayout.inflate(this, R.layout.activity_main, null);
          // … puis on récupère TextView grâce à son identifiant
          text = (TextView) layout.findViewById(R.id.text);
          text.setText("Et cette fois, ça fonctionne !");
          setContentView(layout);
          // On aurait très bien pu utiliser « setContentView(R.layout.activity_main) » bien sûr !
        }
      }
      
      C'est un peu contraignant ! Et si on se contentait de faire un premier setContentView pour « inflater » (désérialiser) l'arbre et récupérer la vue pour la mettre dans un second setContentView ?
      Un peu comme cela, voulez-vous dire ?
      import android.app.Activity;
      import android.os.Bundle;
      import android.widget.TextView;
      
      public class TroimsActivity extends Activity {
        TextView monTexte = null;
      
        @Override
        public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
      
          monTexte = (TextView)findViewById(R.id.text);
          monTexte.setPadding(50, 60, 70, 90);
      
          setContentView(R.layout.activity_main);
        }
      }
      
      Ah d'accord, comme cela l'arbre sera inflate et on n'aura pas à utiliser la méthode compliquée vue au-dessus…
      C'est une idée… mais je vous répondrais que vous avez oublié l'optimisation ! Un fichier XML est très lourd à parcourir, donc construire un arbre de vues prend du temps et des ressources. À la compilation, si on détecte qu'il y a deux setContentView dans onCreate, eh bien on ne prendra en compte que la dernière ! Ainsi, toutes les instances de setContentView précédant la dernière sont rendues caduques.
    • 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.

      Les widgets les plus simples

      Maintenant qu'on sait comment est construite une interface graphique, on va voir avec quoi il est possible de la peupler. Ce chapitre traitera uniquement des widgets, c'est-à-dire des vues qui fournissent un contenu et non qui le mettent en forme — ce sont les layouts qui s'occupent de ce genre de choses.
      Fournir un contenu, c'est permettre à l'utilisateur d'interagir avec l'application, ou afficher une information qu'il est venu consulter.

      Les widgets

      Un widget est un élément de base qui permet d'afficher du contenu à l'utilisateur ou lui permet d'interagir avec l'application. Chaque widget possède un nombre important d'attributs XML et de méthodes Java, c'est pourquoi je ne les détaillerai pas, mais vous pourrez trouver toutes les informations dont vous avez besoin sur la documentation officielle d'Android (cela tombe bien, j'en parle à la fin du chapitre ;) ).

      TextView

      Vous connaissez déjà cette vue, elle vous permet d'afficher une chaîne de caractères que l'utilisateur ne peut modifier. Vous verrez plus tard qu'on peut aussi y insérer des chaînes de caractères formatées, à l'aide de balises HTML, ce qui nous servira à souligner du texte ou à le mettre en gras par exemple.
      Exemple en XML
      <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/textView"
        android:textSize="8sp"
        android:textColor="#112233" />
      Vous n'avez pas encore vu comment faire, mais cette syntaxe @string/textView signifie qu'on utilise une ressource de type string. Il est aussi possible de passer directement une chaîne de caractères dans android:text, mais ce n'est pas recommandé. On précise également la taille des caractères avec android:textSize, puis on précise la couleur du texte avec android:textColor. Cette notation avec un # permet de décrire des couleurs à l'aide de nombres hexadécimaux.
      Exemple en Java
      TextView textView = new TextView(this);
      textView.setText(R.string.textView);
      textView.setTextSize(8);
      textView.setTextColor(0x112233);
      Vous remarquerez que l'équivalent de #112233 est 0x112233 (il suffit de remplacer le # par 0x).
      Rendu
      Le rendu se trouve à la figure suivante.
      Rendu d'un TextView
      Rendu d'un TextView

      EditText

      Ce composant est utilisé pour permettre à l'utilisateur d'écrire des textes. Il s'agit en fait d'un TextView éditable.
      Exemple en XML
      <EditText 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:hint="@string/editText"
        android:inputType="textMultiLine"
        android:lines="5" />
    • 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
    Rendu d'un EditText

    Button

    Un simple bouton, même s'il s'agit en fait d'un TextView cliquable.
    Exemple en XML
    <Button 
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="@string/button" />
    Exemple en Java
    Button button = new Button(this);
    editText.setText(R.string.button);
    Rendu
    Le rendu se trouve à la figure suivante.
    Rendu d'un Button
    Rendu d'un Button

    CheckBox

    Une case qui peut être dans deux états : cochée ou pas.
    Exemple en XML
    <CheckBox 
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="@string/checkBox"
      android:checked="true" />
    android:checked="true" signifie que la case est cochée par défaut.
    Exemple en Java
    CheckBox checkBox = new CheckBox(this);
    checkBox.setText(R.string.checkBox);
    checkBox.setChecked(true)
    if(checkBox.isChecked())
      // Faire quelque chose si le bouton est coché
    Rendu
    Le rendu se trouve à la figure suivante.
    Rendu d'une CheckBox : cochée à gauche, non cochée à droite
    Rendu d'une CheckBox : cochée à gauche, non cochée à droite

    RadioButton et RadioGroup

    Même principe que la CheckBox, à la différence que l'utilisateur ne peut cocher qu'une seule case. Il est plutôt recommandé de les regrouper dans un RadioGroup.
    Un RadioGroup est en fait un layout, mais il n'est utilisé qu'avec des RadioButton, c'est pourquoi on le voit maintenant. Son but est de faire en sorte qu'il puisse n'y avoir qu'un seul RadioButton sélectionné dans tout le groupe.
    Exemple en XML
    <RadioGroup
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:orientation="horizontal" >
      <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"  
        android:checked="true" />
      <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
      <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    </RadioGroup>
    Exemple en Java
    RadioGroup radioGroup = new RadioGroup(this);
    RadioButton radioButton1 = new RadioButton(this);
    RadioButton radioButton2 = new RadioButton(this);
    RadioButton radioButton3 = new RadioButton(this);
    // On ajoute les boutons au RadioGroup
    radioGroup.addView(radioButton10);
    radioGroup.addView(radioButton21);
    radioGroup.addView(radioButton32);
    // On sélectionne le premier bouton
    radioGroup.check(0);
    // On récupère l'identifiant du bouton qui est coché
    int id = radioGroup.getCheckedRadioButtonId();
    Rendu
    Le rendu se trouve à la figure suivante.
    Le bouton radio de droite est sélectionné
    Le bouton radio de droite est sélectionné

    Utiliser la documentation pour trouver une information

    Je fais un petit aparté afin de vous montrer comment utiliser la documentation pour trouver les informations que vous recherchez, parce que tout le monde en a besoin. Que ce soit vous, moi, des développeurs Android professionnels ou n'importe qui chez Google, nous avons tous besoin de la documentation. Il n'est pas possible de tout savoir, et surtout, je ne peux pas tout vous dire ! La documentation est là pour ça, et vous ne pourrez pas devenir de bons développeurs Android — voire de bons développeurs tout court — si vous ne savez pas chercher des informations par vous-mêmes.
    Je vais procéder à l'aide d'un exemple. Je me demande comment faire pour changer la couleur du texte de ma TextView. Pour cela, je me dirige vers la documentation officielle : http://developer.android.com/.
    Vous voyez un champ de recherche en haut à gauche. Je vais insérer le nom de la classe que je recherche : TextView. Vous voyez une liste qui s'affiche et qui permet de sélectionner la classe qui pourrait éventuellement vous intéresser, comme à la figure suivante.
    Une liste s'affiche afin que vous sélectionniez ce qui vous intéresse
    Une liste s'affiche afin que vous sélectionniez ce qui vous intéresse
    J'ai bien sûr cliqué sur Android.widget.TextView puisque c'est celle qui m'intéresse. Nous arrivons alors sur une page qui vous décrit toutes les informations possibles et imaginables sur la classe TextView (voir figure suivante).
    Vous avez accès à beaucoup d'informations sur la classe
    Vous avez accès à beaucoup d'informations sur la classe
    On voit par exemple qu'il s'agit d'une classe, publique, qui dérive de View et implémente une interface.
    La partie suivante représente un arbre qui résume la hiérarchie de ses superclasses.
    Ensuite, on peut voir les classes qui dérivent directement de cette classe (Known Direct Subclasses) et les classes qui en dérivent indirectement, c'est-à-dire qu'un des ancêtres de ces classes dérive de View (Known Indirect Subclasses).
    Enfin, on trouve en haut à droite un résumé des différentes sections qui se trouvent dans le document (je vais aussi parler de certaines sections qui ne se trouvent pas dans cette classe mais que vous pourrez rencontrer dans d'autres classes) :
    • 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
    Apprenez à utiliser les recherches
    J'ai trouvé ! Il s'agit de android:textColor ! Je peux ensuite cliquer dessus pour obtenir plus d'informations et ainsi l'utiliser correctement dans mon code.

    Calcul de l'IMC - Partie 1

    Énoncé
    On va commencer un mini-TP (TP signifie « travaux pratiques » ; ce sont des exercices pour vous entraîner à programmer). Vous voyez ce qu'est l'IMC ? C'est un nombre qui se calcule à partir de la taille et de la masse corporelle d'un individu, afin qu'il puisse déterminer s'il est trop svelte ou trop corpulent.
    Pour l'instant, on va se contenter de faire l'interface graphique. Elle ressemblera à la figure suivante.
    Notre programme ressemblera à ça
    Notre programme ressemblera à ça
    Instructions
    Avant de commencer, voici quelques instructions :
    • On utilisera uniquement le XML.
    • Pour mettre plusieurs composants dans un layout, on se contentera de mettre les composants entre les balises de ce layout.
    • On n'utilisera qu'un seul layout.
    • Les deux EditText permettront de n'insérer que des nombres. Pour cela, on utilise l'attribut android:inputType auquel on donne la valeur numbers.
    • Les TextView qui affichent « Poids : » et « Taille : » sont centrés, en rouge et en gras.
    • Pour mettre un TextView en gras on utilisera l'attribut android:textStyle en lui attribuant comme valeur bold.
    • Pour mettre un TextView en rouge on utilisera l'attribut android:textColor en lui attribuant comme valeur #FF0000. Vous pourrez trouver d'autres valeurs pour indiquer une couleur à cet endroit.
    • Afin de centrer du texte dans un TextView, on utilise l'attribut android:gravity="center".
    Voici le layout de base :
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" 
      android:orientation="vertical">
      <!-- mettre les composants ici -->
    </LinearLayout>
    Solution
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" 
      android:orientation="vertical">
      <TextView 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:text="Poids : "
        android:textStyle="bold"
        android:textColor="#FF0000"
        android:gravity="center"
      />
      <EditText 
        android:id="@+id/poids"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:hint="Poids"
        android:inputType="numberDecimal"
      />
      <TextView 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:text="Taille : "
        android:textStyle="bold"
        android:textColor="#FF0000"
        android:gravity="center"
      />
      <EditText 
        android:id="@+id/taille"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:hint="Taille"
        android:inputType="numberDecimal"
      />
      <RadioGroup
        android:id="@+id/group"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checkedButton="@+id/radio2"
        android:orientation="horizontal"
      >
        <RadioButton 
          android:id="@+id/radio1"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Mètre"
        />
        <RadioButton 
          android:id="@+id/radio2"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Centimètre"
        />
      </RadioGroup>
      <CheckBox 
        android:id="@+id/mega"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Mega fonction !"
      />
      <Button 
        android:id="@+id/calcul"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Calculer l'IMC"
      />
      <Button 
        android:id="@+id/raz"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="RAZ"
      />
      <TextView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="Résultat:"
      />
      <TextView 
        android:id="@+id/result"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" 
        android:text="Vous devez cliquer sur le bouton « Calculer l'IMC » pour obtenir un résultat."
      />
    </LinearLayout>
    Et voilà, notre interface graphique est prête ! Bon pour le moment, elle ne fait rien : si vous appuyez sur les différents élements, rien ne se passe. Mais nous allons y remédier d'ici peu, ne vous inquiétez pas. :)

    Gérer les évènements sur les widgets

    On va voir ici comment gérer les interactions entre l'interface graphique et l'utilisateur.

    Les listeners

    Il existe plusieurs façons d'interagir avec une interface graphique. Par exemple cliquer sur un bouton, entrer un texte, sélectionner une portion de texte, etc. Ces interactions s'appellent des évènements. Pour pouvoir réagir à l'apparition d'un évènement, il faut utiliser un objet qui va détecter l'évènement et afin de vous permettre le traiter. Ce type d'objet s'appelle un listener. Un listener est une interface qui vous oblige à redéfinir des méthodes de callback et chaque méthode sera appelée au moment où se produira l'évènement associé.
    Par exemple, pour intercepter l'évènement clic sur un Button, on appliquera l'interface View.OnClickListener sur ce bouton. Cette interface contient la méthode de callbackvoid onClick(View vue) — le paramètre de type View étant la vue sur laquelle le clic a été effectué, qui sera appelée à chaque clic et qu'il faudra implémenter pour déterminer que faire en cas de clic. Par exemple pour gérer d'autres évènements, on utilisera d'autres méthodes (liste non exhaustive) :
    • 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.OnTouchListenerView.OnClickListener {
      private Button b = null;
        
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
            
        setContentView(R.layout.main);
            
        b = (ButtonfindViewById(R.id.boutton);
        b.setOnTouchListener(this);
        b.setOnClickListener(this);
      }
      @Override
      public boolean onTouch(View vMotionEvent event) {
        /* Réagir au toucher */
        return true;
      }
      @Override
      public void onClick(View v) {
        /* Réagir au clic */
      }
    }
    Cependant, un problème se pose. À chaque fois qu'on appuiera sur un bouton, quel qu'il soit, on rentrera dans la même méthode, et on exécutera donc le même code… C'est pas très pratique, si nous avons un bouton pour rafraîchir un onglet dans une application de navigateur internet et un autre pour quitter un onglet, on aimerait bien que cliquer sur le bouton de rafraîchissement ne quitte pas l'onglet et vice-versa. Heureusement, la vue passée dans la méthode onClick(View) permet de différencier les boutons. En effet, il est possible de récupérer l'identifiant de la vue (vous savez, l'identifiant défini en XML et qu'on retrouve dans le fichier R !) sur laquelle le clic a été effectué. Ainsi, nous pouvons réagir différemment en fonction de cet identifiant :
    public void onClick(View v) {
      // On récupère l'identifiant de la vue, et en fonction de cet identifiant…
      switch(v.getId()) {
        // Si l'identifiant de la vue est celui du premier bouton
        case R.id.bouton1:
        /* Agir pour bouton 1 */
        break;
        // Si l'identifiant de la vue est celui du deuxième bouton      
        case R.id.bouton2:
        /* Agir pour bouton 2 */
        break;
      
        /* etc. */
      }
    }

    Par une classe anonyme

    L'inconvénient principal de la technique précédente est qu'elle peut très vite allonger les méthodes des listeners, ce qui fait qu'on s'y perd un peu s'il y a beaucoup d'éléments à gérer. C'est pourquoi il est préférable de passer par une classe anonyme dès qu'on a un nombre élevé d'éléments qui réagissent au même évènement.
    Pour rappel, une classe anonyme est une classe interne qui dérive d'une superclasse ou implémente une interface, et dont on ne précise pas le nom. Par exemple pour créer une classe anonyme qui implémente View.OnClickListener() je peux faire :
    widget.setTouchListener(new View.OnTouchListener() {
      /**
       * Contenu de ma classe
       * Comme on implémente une interface, il y aura des méthodes à implémenter, dans ce cas-ci  
       * « public boolean onTouch(View v, MotionEvent event) »
      */
    }); // Et on n'oublie pas le point-virgule à la fin ! C'est une instruction comme les autres !
    Voici un exemple de code :
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    public class AnonymousExampleActivity extends Activity {
      // On cherchera à détecter les touchers et les clics sur ce bouton
      private Button touchAndClick = null;
      // On voudra détecter uniquement les clics sur ce bouton
      private Button clickOnly = null;
        
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
            
        touchAndClick = (Button)findViewById(R.id.touchAndClick);
        clickOnly = (Button)findViewById(R.id.clickOnly);
            
        touchAndClick.setOnLongClickListener(new View.OnLongClickListener() {
          @Override
          public boolean onLongClick(View v) {
            // Réagir à un long clic
            return false;
          }
        });
            
        touchAndClick.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            // Réagir au clic
          }
        });
        clickOnly.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            // Réagir au clic
          }
        });
      }
    }

    Par un attribut

    C'est un dérivé de la méthode précédente : en fait on implémente des classes anonymes dans des attributs de façon à pouvoir les utiliser dans plusieurs éléments graphiques différents qui auront la même réaction pour le même évènement. C'est la méthode que je privilégie dès que j'ai, par exemple, plusieurs boutons qui utilisent le même code.
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.Button;
    public class Main extends Activity {
      private OnClickListener clickListenerBoutons = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
          /* Réagir au clic pour les boutons 1 et 2*/
        }
      };
      private OnTouchListener touchListenerBouton1 = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View vMotionEvent event) {
          /* Réagir au toucher pour le bouton 1*/
          return onTouch(vevent);
        }
      };
      private OnTouchListener touchListenerBouton3 = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View vMotionEvent event) {
          /* Réagir au toucher pour le bouton 3*/
          return super.onTouch(vevent);
        }
      };
      Button b1 = null;
      Button b2 = null;
      Button b3 = null;
        
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
      
        setContentView(R.layout.main);
        
        b1 = (ButtonfindViewById(R.id.bouton1);
        b2 = (ButtonfindViewById(R.id.bouton2);
        b3 = (ButtonfindViewById(R.id.bouton3);
        b1.setOnTouchListener(touchListenerBouton1);
        b1.setOnClickListener(clickListenerBoutons);
        b2.setOnClickListener(clickListenerBoutons);
        b3.setOnTouchListener(touchListenerBouton3);
      }
    }

    Application

    Énoncé
    On va s'amuser un peu : nous allons créer un bouton qui prend tout l'écran et faire en sorte que le texte à l'intérieur du bouton grossisse quand on s'éloigne du centre du bouton, et rétrécisse quand on s'en rapproche.
    Instructions
    • On va se préoccuper non pas du clic mais du toucher, c'est-à-dire l'évènement qui débute dès qu'on touche le bouton jusqu'au moment où on le relâche (contrairement au clic qui ne se déclenche qu'au moment où on relâche la pression).
    • La taille du TextView sera fixée avec la méthode setTextSize(Math.abs(coordonnee_x - largeur_du_bouton / 2) + Math.abs(coordonnee_y - hauteur_du_bouton / 2)).
    • Pour obtenir la coordonnée en abscisse (X) on utilise float getX() d'un MotionEvent, et pour obtenir la coordonnée en ordonnée (Y) on utilise float getY().
    Je vous donne le code pour faire en sorte d'avoir le bouton bien au milieu du layout :
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" >
      <Button
        android:id="@+id/bouton"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" 
        android:layout_gravity="center"
        android:text="@string/hello" />
    </LinearLayout>
    Maintenant, c'est à vous de jouer !
    Solution
    // On fait implémenter OnTouchListener par notre activité
    public class Main extends Activity implements View.OnTouchListener {
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
            
        setContentView(R.layout.main);
            
        // On récupère le bouton par son identifiant
        Button b = (ButtonfindViewById(R.id.bouton);
        // Puis on lui indique que cette classe sera son listener pour l'évènement Touch
        b.setOnTouchListener(this);
      }
      // Fonction qui sera lancée à chaque fois qu'un toucher est détecté sur le bouton rattaché
      @Override
      public boolean onTouch(View viewMotionEvent event) {
        // Comme l'évènement nous donne la vue concernée par le toucher, on le récupère et on le caste en Button
        Button bouton = (Button)view;
        // On récupère la largeur du bouton
        int largeur = bouton.getWidth();
        // On récupère la hauteur du bouton
        int hauteur = bouton.getHeight();
        // On récupère la coordonnée sur l'abscisse (X) de l'évènement
        float x = event.getX();
        // On récupère la coordonnée sur l'ordonnée (Y) de l'évènement
        float y = event.getY();
        // Puis on change la taille du texte selon la formule indiquée dans l'énoncé
        bouton.setTextSize(Math.abs(x - largeur / 2+ Math.abs(y - hauteur / 2));
        // Le toucher est fini, on veut continuer à détecter les touchers d'après
        return true;
      }
    }
    On a procédé par héritage puisqu'on a qu'un seul bouton sur lequel agir.

    Calcul de l'IMC - Partie 2

    Énoncé
    Il est temps maintenant de relier tous les boutons de notre application pour pouvoir effectuer tous les calculs, en respectant les quelques règles suivantes :
    • La CheckBox de megafonction permet de changer le résultat du calcul en un message élogieux pour l'utilisateur.
    • La formule pour calculer l'IMC est poids (en kilogrammes)taille (en metres)2.
    • Le bouton RAZ remet à zéro tous les champs (sans oublier le texte pour le résultat).
    • Les éléments dans le RadioGroup permettent à l'utilisateur de préciser en quelle unité il a indiqué sa taille. Pour obtenir la taille en mètres depuis la taille en centimètres il suffit de diviser par 100 : 171 centimetres100=1.71 metres.
    • Dès qu'on change les valeurs dans les champs Poids et Taille, on remet le texte du résultat par défaut puisque la valeur calculée n'est plus valable pour les nouvelles valeurs.
    • On enverra un message d'erreur si l'utilisateur essaie de faire le calcul avec une taille égale à zéro grâce à un Toast.
    Consignes
    • Voici la syntaxe pour construire un Toast : static Toast makeText(Context context, CharSequence texte, int duration). La durée peut être indiquée à l'aide de la constante Toast.LENGTH_SHORT pour un message court et Toast.LENGTH_LONG pour un message qui durera plus longtemps.
      Enfin, il est possible d'afficher le Toast avec la méthode void show ().
    • Pour savoir si une CheckBox est sélectionnée, on utilisera la méthode boolean isChecked() qui renvoie true le cas échéant.
    • Pour récupérer l'identifiant du RadioButton qui est sélectionné dans un RadioGroup il faut utiliser la méthode int getCheckedRadioButtonId ().
    • On peut récupérer le texte d'un EditText à l'aide de la fonction Editable getText (). On peut ensuite vider le contenu de cet objet Editable à l'aide de la fonction void clear(). Plus d'informations sur Editable.
    • Parce que c'est déjà bien assez compliqué comme cela, on se simplifie la vie et on ne prend pas en compte les cas extrêmes (taille ou poids < 0 ou null par exemple).
    • Pour détecter le moment où l'utilisateur écrit dans un EditText, on peut utiliser l'évènement onKey. Problème, cette technique ne fonctionne que sur les claviers virtuels, alors si l'utilisateur a un clavier physique, ce qu'il écrit n'enclenchera pas la méthode de callback... Je vais quand même vous présenter cette solution, mais pour faire ce genre de surveillance, on préférera utiliser un TextWatcher. C'est comme un listener, mais ça n'en porte pas le nom !
    Ma solution
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.KeyEvent;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.view.View.OnKeyListener;
    import android.widget.Button;
    import android.widget.CheckBox;
    import android.widget.EditText;
    import android.widget.RadioGroup;
    import android.widget.TextView;
    import android.widget.Toast;
    public class IMCActivity extends Activity {
      // La chaîne de caractères par défaut
      private final String defaut = "Vous devez cliquer sur le bouton « Calculer l'IMC » pour obtenir un résultat.";
      // La chaîne de caractères de la megafonction
      private final String megaString = "Vous faites un poids parfait ! Wahou ! Trop fort ! On dirait Brad Pitt (si vous êtes un homme)/Angelina Jolie (si vous êtes une femme)/Willy (si vous êtes un orque) !"
        
      Button envoyer = null;
      Button raz = null;
        
      EditText poids = null;
      EditText taille = null;
        
      RadioGroup group = null;
        
      TextView result = null;
        
      CheckBox mega = null;
        
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
            
        // On récupère toutes les vues dont on a besoin
        envoyer = (Button)findViewById(R.id.calcul);
            
        raz = (Button)findViewById(R.id.raz);
            
        taille = (EditText)findViewById(R.id.taille);
        poids = (EditText)findViewById(R.id.poids);
            
        mega = (CheckBox)findViewById(R.id.mega);
            
        group = (RadioGroup)findViewById(R.id.group);
        result = (TextView)findViewById(R.id.result);
        // On attribue un listener adapté aux vues qui en ont besoin
        envoyer.setOnClickListener(envoyerListener);
        raz.setOnClickListener(razListener);
        taille.addTextChangedListener(textWatcher);
        poids.addTextChangedListener(textWatcher);
        // Solution avec des onKey
        //taille.setOnKeyListener(modificationListener);
        //poids.setOnKeyListener(modificationListener);
        mega.setOnClickListener(checkedListener);
      }
      /*
      // Se lance à chaque fois qu'on appuie sur une touche en étant sur un EditText
      private OnKeyListener modificationListener = new OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
          // On remet le texte à sa valeur par défaut pour ne pas avoir de résultat incohérent
          result.setText(defaut);
          return false;
        }
      };*/
      private TextWatcher textWatcher = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence sint startint beforeint count) {
          result.setText(defaut);
        }
            
        @Override
        public void beforeTextChanged(CharSequence sint startint count,
          int after) {
      
        }
      
        @Override
        public void afterTextChanged(Editable s) {
      
        }
      };
        
      // Uniquement pour le bouton "envoyer"
      private OnClickListener envoyerListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
          if(!mega.isChecked()) {
            // Si la megafonction n'est pas activée
            // On récupère la taille
            String t = taille.getText().toString();
            // On récupère le poids
            String p = poids.getText().toString();
                
            float tValue = Float.valueOf(t);
                
            // Puis on vérifie que la taille est cohérente
            if(tValue == 0)
              Toast.makeText(IMCActivity.this"Hého, tu es un Minipouce ou quoi ?"Toast.LENGTH_SHORT).show();
            else {
              float pValue = Float.valueOf(p);
              // Si l'utilisateur a indiqué que la taille était en centimètres
              // On vérifie que la Checkbox sélectionnée est la deuxième à l'aide de son identifiant
              if(group.getCheckedRadioButtonId() == R.id.radio2)
                tValue = tValue / 100;
              tValue = (float)Math.pow(tValue2);
              float imc = pValue / tValue;
              result.setText("Votre IMC est " + String.valueOf(imc));
            }
          } else
            result.setText(megaString);
        }
      };
        
      // Listener du bouton de remise à zéro
      private OnClickListener razListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
          poids.getText().clear();
          taille.getText().clear();
          result.setText(defaut);
        }
      };
        
      // Listener du bouton de la megafonction.
      private OnClickListener checkedListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
          // On remet le texte par défaut si c'était le texte de la megafonction qui était écrit
          if(!((CheckBox)v).isChecked() && result.getText().equals(megaString))
            result.setText(defaut);
        }
      };
    }
    Pourquoi on retourne false dans le onKeyListener ? Il se serait passer quoi si j'avais retourné true ?
    Curieux va ! :p En fait l'évènement onKey sera lancé avant que l'écriture soit prise en compte par le système. Ainsi, si vous renvoyez true, Android considérera que l'évènement a été géré, et que vous avez vous-même écrit la lettre qui a été pressée. Si vous renvoyez false, alors le système comprendra que vous n'avez pas écrit la lettre et il le fera de lui-même. Alors vous auriez très bien pu renvoyer true, mais il faudrait écrire nous-même la lettre et c'est du travail en plus pour rien !
    Vous avez vu ce qu'on a fait ? Sans toucher à l'interface graphique, on a pu effectuer toutes les modifications nécessaires au bon fonctionnement de notre application. C'est l'intérêt de définir l'interface dans un fichier XML et le côté interactif en Java : vous pouvez modifier l'un sans toucher l'autre !
    • Il existe un grand nombre de widgets différents. Parmi les plus utilisés, nous avons :
      • TextView destiné à afficher du texte sur l'écran.
      • EditText qui hérite des propriétés de TextView et qui permet à l'utilisateur d'écrire du texte.
      • Button qui hérite des propriétés de TextView et qui permet à l'utilisateur de cliquer sur du texte.
      • CheckBox qui hérite des propriétés de Button et qui permet à l'utilisateur de cocher une case.
      • RadioButton qui hérite des propriétés de Button et qui permet à l'utilisateur de choisir parmi plusieurs choix. De plus, RadioGroup est un layout spécifique aux RadioButton.
    • N'oubliez pas que la documentation est l'unique endroit où vous pourrez trouver toutes les possibilités offertes pour chacun des widgets disponibles.
    • Pour écouter les différents évènements qui pourraient se produire sur vos vues, on utilise des listeners qui enclenchent des méthodes de callback que vous pouvez redéfinir pour gérer leur implémentation.
    • Android permet de lier des listeners à des vues de trois manières différentes :
      • Par héritage en implémentant l'interface au niveau de la classe, auquel cas il faudra réécrire les méthodes de callback directement dans votre classe.
      • Par classe anonyme en donnant directement une implémentation unique à la vue.
      • Par un attribut, si vous voulez réutiliser votre listener sur plusieurs vues.

        Organiser son interface avec des layouts

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

        LinearLayout : placer les éléments sur une ligne

        Comme son nom l'indique, ce layout se charge de mettre les vues sur une même ligne, selon une certaine orientation. L'attribut pour préciser cette orientation est android:orientation. On peut lui donner deux valeurs :
        • vertical pour que les composants soient placés de haut en bas (en colonne) ;
        • horizontal pour que les composants soient placés de gauche à droite (en ligne).
        On va faire quelques expériences pour s'amuser !
        Premier exemple
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent" >
          <Button  
            android:id="@+id/premier"
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content" 
            android:text="Premier bouton" />
            
          <Button  
            android:id="@+id/second"
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content" 
            android:text="Second bouton" />
        </LinearLayout>
        Le rendu de ce code se trouve à la figure suivante.
        Les deux boutons prennent toute la largeur
        Les deux boutons prennent toute la largeur
      • Le LinearLayout est vertical et prend toute la place de son parent (vous savez, l'invisible qui prend toute la place dans l'écran).
      • Le premier bouton prend toute la place dans le parent en largeur et uniquement la taille nécessaire en hauteur (la taille du texte, donc !).
      • Le second bouton fait de même.
      Deuxième exemple
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
          
        <Button  
          android:id="@+id/premier"
          android:layout_width="wrap_content" 
          android:layout_height="fill_parent" 
          android:text="Premier bouton" />
          
        <Button  
          android:id="@+id/second"
          android:layout_width="wrap_content" 
          android:layout_height="fill_parent" 
          android:text="Second bouton" />
      </LinearLayout>
      Le rendu de ce code se trouve à la figure suivante.
      Le premier bouton fait toute la hauteur, on ne voit donc pas le deuxième bouton
      Le premier bouton fait toute la hauteur, on ne voit donc pas le deuxième bouton
    • Le LinearLayout est vertical et prend toute la place de son parent.
    • Le premier bouton prend toute la place de son parent en hauteur et uniquement la taille nécessaire en largeur.
    • Comme le premier bouton prend toute la place, alors le pauvre second bouton se fait écraser. :( C'est pour cela qu'on ne le voit pas.
    Troisième exemple
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" >
      <Button  
        android:id="@+id/premier"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent" 
        android:text="Premier bouton" />
      <Button  
        android:id="@+id/second"
        android:layout_width="wrap_content" 
        android:layout_height="fill_parent" 
        android:text="Second bouton" />
    </LinearLayout>
    Le rendu de ce code se trouve à la figure suivante.
    Les deux boutons prennent uniquement la place nécessaire en hauteur et en largeur
    • Le LinearLayout est vertical et prend toute la place en largeur mais uniquement la taille nécessaire en hauteur : dans ce cas précis, la taille nécessaire sera calculée en fonction de la taille des enfants.
    • Le premier bouton prend toute la place possible dans le parent. Comme le parent prend le moins de place possible, il doit faire de même.
    • Le second bouton fait de même.
    Quatrième exemple
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="horizontal"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" >
      <Button  
        android:id="@+id/premier"
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:text="Premier bouton" />
      <Button  
        android:id="@+id/second"
        android:layout_width="wrap_content" 
        android:layout_height="fill_parent" 
        android:text="Second bouton" />
    </LinearLayout>
    Le rendu de ce code se trouve à la figure suivante.
    Le premier bouton prend uniquement la place nécessaire et le deuxième toute la hauteur
    Le premier bouton prend uniquement la place nécessaire et le deuxième toute la hauteur
    • Le LinearLayout est horizontal et prend toute la place de son parent.
    • Le premier bouton prend uniquement la place nécessaire.
    • Le second bouton prend uniquement la place nécessaire en longueur et s'étend jusqu'aux bords du parent en hauteur.
    Vous remarquerez que l'espace est toujours divisé entre les deux boutons, soit de manière égale, soit un bouton écrase complètement l'autre. Et si on voulait que le bouton de droite prenne deux fois plus de place que celui de gauche par exemple ?
    Pour cela, il faut attribuer un poids au composant. Ce poids peut être défini grâce à l'attribut android:layout_weight. Pour faire en sorte que le bouton de droite prenne deux fois plus de place, on peut lui mettre android:layout_weight="1" et mettre au bouton de gauche android:layout_weight="2". C'est alors le composant qui a la plus faible pondération qui a la priorité.
    Et si, dans l'exemple précédent où un bouton en écrasait un autre, les deux boutons avaient eu un poids identique, par exemple android:layout_weight="1" pour les deux, ils auraient eu la même priorité et auraient pris la même place. Par défaut, ce poids est à 0.
    Dernier attribut particulier pour les widgets de ce layout, android:layout_gravity, qu'il ne faut pas confondre avec android:gravity. android:layout_gravity vous permet de déterminer comment se placera la vue dans le parent, alors que android:gravity vous permet de déterminer comment se placera le contenu de la vue à l'intérieur même de la vue (par exemple, comment se placera le texte dans un TextView ? Au centre, en haut, à gauche ?).
    Vous prendrez bien un petit exemple pour illustrer ces trois concepts ? :)
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="horizontal"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" >
      <Button
        android:id="@+id/bouton1"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:layout_gravity="bottom"
        android:layout_weight="40"
        android:text="Bouton 1" />
      <Button
        android:id="@+id/bouton2"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:layout_gravity="center"
        android:layout_weight="20"
        android:gravity="bottom|right"
        android:text="Bouton 2" />
      <Button
        android:id="@+id/bouton3"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:layout_gravity="top"
        android:layout_weight="40"
        android:text="Bouton 3" />
    </LinearLayout>
    Le rendu de ce code se trouve à la figure suivante.
    Trois boutons placés différemment
    Trois boutons placés différemment
    Comme le bouton 2 a un poids deux fois inférieur aux boutons 1 et 3, alors il prend deux fois plus de place qu'eux. De plus, chaque bouton possède un attribut android:layout_gravity afin de que l'on détermine sa position dans le layout. Le deuxième bouton présente aussi l'attribut android:gravity, qui est un attribut de TextView et non layout, de façon à mettre le texte en bas (bottom) à droite (right).

    Calcul de l'IMC - Partie 3.1

    Énoncé
    Récupérez le code de votre application de calcul de l'IMC et modifiez le layout pour obtenir quelque chose ressemblant à la figure suivante.
    Essayez d'obtenir la même interface
    Essayez d'obtenir la même interface
    Les EditText prennent le plus de place possible, mais comme ils ont un poids plus fort que les TextView, ils n'ont pas la priorité.
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" 
      android:orientation="vertical">
      <LinearLayout 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
      >
        <TextView 
          android:layout_width="wrap_content"
          android:layout_height="wrap_content" 
          android:text="Poids : "
          android:textStyle="bold"
          android:textColor="#FF0000"
          android:gravity="center"
        />
        <EditText 
          android:id="@+id/poids"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content" 
          android:hint="Poids"
          android:inputType="numberDecimal"
          android:layout_weight="1"
        />
      </LinearLayout>
      <LinearLayout 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
      >
        <TextView 
          android:layout_width="wrap_content"
          android:layout_height="wrap_content" 
          android:text="Taille : "
          android:textStyle="bold"
          android:textColor="#FF0000"
          android:gravity="center"
        />
        <EditText 
          android:id="@+id/taille"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content" 
          android:hint="Taille"
          android:inputType="numberDecimal"
          android:layout_weight="1"
        />
      </LinearLayout>
      <RadioGroup
        android:id="@+id/group"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checkedButton="@+id/radio2"
        android:orientation="horizontal"
      >
        <RadioButton 
          android:id="@+id/radio1"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Mètre"
        />
        <RadioButton 
          android:id="@+id/radio2"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Centimètre"
        />
      </RadioGroup>
      <CheckBox 
        android:id="@+id/mega"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Mega fonction !"
      />
      <LinearLayout 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
      >
        <Button 
          android:id="@+id/calcul"
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 
          android:text="Calculer l'IMC"
          android:layout_weight="1"
          android:layout_marginLeft="25dip" 
          android:layout_marginRight="25dip"
        />
        <Button 
          android:id="@+id/raz"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="RAZ"
          android:layout_weight="1" 
          android:layout_marginLeft="25dip" 
          android:layout_marginRight="25dip"
        />
      </LinearLayout>
      <TextView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="Résultat:"
      />
      <TextView 
        android:id="@+id/result"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" 
        android:text="Vous devez cliquer sur le bouton « Calculer l'IMC » pour obtenir un résultat."
      />
    </LinearLayout>

    RelativeLayout : placer les éléments les uns en fonction des autres

    De manière totalement différente, ce layout propose plutôt de placer les composants les uns par rapport aux autres. Il est même possible de les placer par rapport au RelativeLayout parent.
    Si on veut par exemple placer une vue au centre d'un RelativeLayout, on peut passer à cette vue l'attribut android:layout_centerInParent="true". Il est aussi possible d'utiliser android:layout_centerHorizontal="true" pour centrer, mais uniquement sur l'axe horizontal, de même avec android:layout_centerVertical="true" pour centrer sur l'axe vertical.
    Premier exemple
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" >
      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Centré dans le parent"
        android:layout_centerInParent="true" />
      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Centré verticalement"
        android:layout_centerVertical="true" />
      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Centré horizontalement"
        android:layout_centerHorizontal="true" />
    </RelativeLayout>
    Le rendu de ce code se trouve à la figure suivante.
    Deux vues sont empilées
    Deux vues sont empilées
    On observe ici une différence majeure avec le LinearLayout : il est possible d'empiler les vues. Ainsi, le TextView centré verticalement s’entremêle avec celui centré verticalement et horizontalement.
    Il existe d'autres contrôles pour situer une vue par rapport à un RelativeLayout. On peut utiliser :
    • android:layout_alignParentBottom="true" pour aligner le plancher d'une vue au plancher du RelativeLayout ;
    • android:layout_alignParentTop="true" pour coller le plafond d'une vue au plafond du RelativeLayout ;
    • android:layout_alignParentLeft="true" pour coller le bord gauche d'une vue avec le bord gauche du RelativeLayout ;
    • android:layout_alignParentRight="true" pour coller le bord droit d'une vue avec le bord droit du RelativeLayout.
    Deuxième exemple
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" >
      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="En haut !"
        android:layout_alignParentTop="true" />
      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="En bas !"
        android:layout_alignParentBottom="true" />    
      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="A gauche !"
        android:layout_alignParentLeft="true" />
      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="A droite !"
        android:layout_alignParentRight="true" />
      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Ces soirées là !"
        android:layout_centerInParent="true" />
    </RelativeLayout>
    Le rendu de ce code se trouve à la figure suivante.
    En haut à gauche, deux TextView se superposent
    On remarque tout de suite que les TextView censés se situer à gauche et en haut s'entremêlent, mais c'est logique puisque par défaut une vue se place en haut à gauche dans un RelativeLayout. Donc, quand on lui dit « Place-toi à gauche » ou « Place-toi en haut », c'est comme si on ne lui donnait pas d'instructions au final.
    Enfin, il ne faut pas oublier que le principal intérêt de ce layout est de pouvoir placer les éléments les uns par rapport aux autres. Pour cela il existe deux catégories d'attributs :
    • Ceux qui permettent de positionner deux bords opposés de deux vues différentes ensemble. On y trouve android:layout_below (pour aligner le plafond d'une vue sous le plancher d'une autre), android:layout_above (pour aligner le plancher d'une vue sur le plafond d'une autre), android:layout_toRightOf (pour aligner le bord gauche d'une vue au bord droit d'une autre) et android:layout_toLeftOf (pour aligner le bord droit d'une vue au bord gauche d'une autre).
    • Ceux qui permettent de coller deux bords similaires ensemble. On trouve android:layout_alignBottom (pour aligner le plancher de la vue avec le plancher d'une autre), android:layout_alignTop (pour aligner le plafond de la vue avec le plafond d'une autre), android:layout_alignLeft (pour aligner le bord gauche d'une vue avec le bord gauche d'une autre) et android:layout_alignRight (pour aligner le bord droit de la vue avec le bord droit d'une autre).
    Troisième exemple
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" >
      <TextView
        android:id="@+id/premier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="[I] En haut à gauche par défaut" />
      <TextView
        android:id="@+id/deuxieme"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="[II] En dessous de (I)"
        android:layout_below="@id/premier" />    
      <TextView
        android:id="@+id/troisieme"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="[III] En dessous et à droite de (I)"
        android:layout_below="@id/premier"
        android:layout_toRightOf="@id/premier" />
      <TextView
        android:id="@+id/quatrieme"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="[IV] Au dessus de (V), bord gauche aligné avec le bord gauche de (II)"
        android:layout_above="@+id/cinquieme"
        android:layout_alignLeft ="@id/deuxieme" />
      <TextView
        android:id="@+id/cinquieme"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="[V] En bas à gauche"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true" />
    </RelativeLayout>
    Le rendu de ce code se trouve à la figure suivante.
    Les TextView sont bien placés
    Les TextView sont bien placés
    Je vous demande maintenant de regarder l'avant dernier TextView, en particulier son attribut android:layout_above. On ne fait pas référence au dernier TextView comme aux autres, il faut préciser un + ! Eh oui, rappelez-vous, je vous avais dit il y a quelques chapitres déjà que, si nous voulions faire référence à une vue qui n'était définie que plus tard dans le fichier XML, alors il fallait ajouter un + dans l'identifiant, sinon Android pensera qu'il s'agit d'une faute et non d'un identifiant qui sera déclaré après.

    Calcul de l'IMC - Partie 3.2

    Même chose pour un layout différent ! Moi, je vise le même résultat que précédemment.
    Ma solution
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent">
      <TextView 
        android:id="@+id/textPoids"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="Poids : "
        android:textStyle="bold"
        android:textColor="#FF0000"
      />
      <EditText 
        android:id="@+id/poids"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:hint="Poids"
        android:inputType="numberDecimal"
        android:layout_toRightOf="@id/textPoids"
        android:layout_alignParentRight="true"
      />
      <TextView 
        android:id="@+id/textTaille"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="Taille : "
        android:textStyle="bold"
        android:textColor="#FF0000"
        android:gravity="left"
        android:layout_below="@id/poids"
      />
      <EditText 
        android:id="@+id/taille"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:hint="Taille"
        android:inputType="numberDecimal"
        android:layout_below="@id/poids"
        android:layout_toRightOf="@id/textTaille"
        android:layout_alignParentRight="true"
      />
      <RadioGroup
        android:id="@+id/group"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checkedButton="@+id/radio2"
        android:orientation="horizontal"
        android:layout_below="@id/taille"
      >
        <RadioButton 
          android:id="@+id/radio1"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Mètre"
        />
        <RadioButton 
          android:id="@+id/radio2"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Centimètre"
        />
      </RadioGroup>
      <CheckBox 
        android:id="@+id/mega"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Mega fonction !"
        android:layout_below="@id/group"
      />
      <Button 
        android:id="@+id/calcul"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Calculer l'IMC"
        android:layout_below="@id/mega"
        android:layout_marginLeft="25dip"
      />
      <Button 
        android:id="@+id/raz"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="RAZ"
        android:layout_below="@id/mega"
        android:layout_alignRight="@id/taille"
        android:layout_marginRight="25dip"
      />
      <TextView 
        android:id="@+id/resultPre"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="Résultat:"
        android:layout_below="@id/calcul"
      />
      <TextView 
        android:id="@+id/result"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" 
        android:text="Vous devez cliquer sur le bouton « Calculer l'IMC » pour obtenir un résultat."
        android:layout_below="@id/resultPre"
      />
    </RelativeLayout>
    Le problème de ce layout, c'est qu'une petite modification dans l'interface graphique peut provoquer de grosses modifications dans tout le fichier XML, il faut donc savoir par avance très précisément ce qu'on veut faire.

    TableLayout : placer les éléments comme dans un tableau

    Dernier layout de base, il permet d'organiser les éléments en tableau, comme en HTML, mais sans les bordures. Voici un exemple d'utilisation de ce layout :
    <?xml version="1.0" encoding="utf-8"?>
    <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:stretchColumns="1">
      <TextView 
        android:text="Les items précédés d'un V ouvrent un sous-menu"
      />
      <View
        android:layout_height="2dip"
        android:background="#FF909090"
      />
      <TableRow>
        <TextView 
          android:text="N'ouvre pas un sous-menu"
          android:layout_column="1"
          android:padding="3dip" 
        />
        <TextView 
          android:text="Non !"
          android:gravity="right"
          android:padding="3dip" 
        />
      </TableRow>
      <TableRow>
        <TextView 
          android:text="V"
        />
        <TextView 
          android:text="Ouvre un sous-menu"
          android:layout_column="1"
          android:padding="3dip" 
        />
        <TextView 
          android:text="Là si !"
          android:gravity="right"
          android:padding="3dip" 
        />
      </TableRow>
      <View
        android:layout_height="2dip"
        android:background="#FF909090" 
      />
      <TableRow>
        <TextView 
          android:text="V" 
        />
        <TextView 
          android:text="Ouvre un sous-menu"
          android:padding="3dip"
        />
      </TableRow>
      <View
        android:layout_height="2dip"
        android:background="#FF909090" 
      />
      <TableRow>
        <TextView 
          android:layout_column="1"
          android:layout_span="2"
          android:text="Cet item s'étend sur deux colonnes, cool hein ?"
          android:padding="3dip" 
        />
      </TableRow>
    </TableLayout>
    Ce qui donne la figure suivante.
    Le contenu est organisé en tableau
    Le contenu est organisé en tableau
    On observe tout d'abord qu'il est possible de mettre des vues directement dans le tableau, auquel cas elles prendront toute la place possible en longueur. En fait, elles s'étendront sur toutes les colonnes du tableau. Cependant, si on veut un contrôle plus complet ou avoir plusieurs éléments sur une même ligne, alors il faut passer par un objet <TableRow>.
    <TextView 
      android:text="Les items précédés d'un V ouvrent un sous-menu" />
    Cet élément s'étend sur toute la ligne puisqu'il ne se trouve pas dans un <TableRow>
    <View
      android:layout_height="2dip"
      android:background="#FF909090" />
    Moyen efficace pour dessiner un séparateur — n'essayez pas de le faire en dehors d'un <TableLayout> ou votre application plantera.
    Une ligne est composée de cellules. Chaque cellule peut contenir une vue, ou être vide. La taille du tableau en colonnes est celle de la ligne qui contient le plus de cellules. Dans notre exemple, nous avons trois colonnes pour tout le tableau, puisque la ligne avec le plus cellules est celle qui contient « V » et se termine par « Là si ! ».
    <TableRow>
      <TextView 
        android:text="V" 
      />
      <TextView 
        android:text="Ouvre un sous-menu"
        android:layout_column="1"
        android:padding="3dip" 
      />
      <TextView 
        android:text="Là si !"
        android:gravity="right"
        android:padding="3dip" 
      />
    </TableRow>
    Cette ligne a trois éléments, c'est la plus longue du tableau, ce dernier est donc constitué de trois colonnes.
    Il est possible de choisir dans quelle colonne se situe un item avec l'attribut android:layout_column. Attention, l'index des colonnes commence à 0. Dans notre exemple, le dernier item se place directement à la deuxième colonne grâce à android:layout_column="1".
    <TableRow>
      <TextView 
        android:text="N'ouvre pas un sous-menu"
        android:layout_column="1"
        android:padding="3dip" 
      />
      <TextView 
        android:text="Non !"
        android:gravity="right"
        android:padding="3dip" 
      />
    </TableRow>
    On veut laisser vide l'espace pour la première colonne, on place alors les deux TextView dans les colonnes 1 et 2.
    La taille d'une cellule dépend de la cellule la plus large sur une même colonne. Dans notre exemple, la seconde colonne fait la largeur de la cellule qui contient le texte « N'ouvre pas un sous-menu », puisqu'il se trouve dans la deuxième colonne et qu'il n'y a pas d'autres éléments dans cette colonne qui soit plus grand.
    Enfin, il est possible d'étendre un item sur plusieurs colonnes à l'aide de l'attribut android:layout_span. Dans notre exemple, le dernier item s'étend de la deuxième colonne à la troisième. Il est possible de faire de même sur les lignes avec l'attribut android:layout_column.
    <TableRow>
      <TextView 
        android:layout_column="1"
        android:layout_span="2"
        android:text="Cet item s'étend sur deux colonnes, cool hein ?"
        android:padding="3dip" 
      />
    </TableRow>
    Ce TextView débute à la deuxième colonne et s'étend sur deux colonnes, donc jusqu'à la troisième.
    Sur le nœud TableLayout, on peut jouer avec trois attributs (attention, les rangs débutent à 0) :
    • android:stretchColumns pour que la longueur de tous les éléments de cette colonne passe en fill_parent, donc pour prendre le plus de place possible. Il faut préciser le rang de la colonne à cibler, ou plusieurs rangs séparés par des virgules.
    • android:shrinkColumns pour que la longueur de tous les éléments de cette colonne passe en wrap_content, donc pour prendre le moins de place possible. Il faut préciser le rang de la colonne à cibler, ou plusieurs rangs séparés par des virgules.
    • android:collapseColumns pour faire purement et simplement disparaître des colonnes du tableau. Il faut préciser le rang de la colonne à cibler, ou plusieurs rangs séparés par des virgules.

    Calcul de l'IMC - Partie 3.3

    Énoncé
    Réitérons l'expérience, essayez encore une fois d'obtenir le même rendu, mais cette fois avec un TableLayout. L'exercice est intéressant puisqu'on n'est pas vraiment en présence d'un tableau, il va donc falloir réfléchir beaucoup et exploiter au maximum vos connaissances pour obtenir un rendu acceptable.
    Ma solution
    <?xml version="1.0" encoding="utf-8"?>
    <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:stretchColumns="1">
      <TableRow>
        <TextView 
          android:text="Poids : "
          android:textStyle="bold"
          android:textColor="#FF0000"
          android:gravity="center"
        />
        <EditText 
          android:id="@+id/poids"
          android:hint="Poids"
          android:inputType="numberDecimal"
          android:layout_span="2"
        />
      </TableRow>
      <TableRow>
        <TextView 
          android:layout_width="fill_parent"
          android:layout_height="wrap_content" 
          android:text="Taille : "
          android:textStyle="bold"
          android:textColor="#FF0000"
          android:gravity="center"
        />
        <EditText 
          android:id="@+id/taille"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content" 
          android:hint="Taille"
          android:inputType="numberDecimal"
          android:layout_span="2"
        />
      </TableRow>
      <RadioGroup
        android:id="@+id/group"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checkedButton="@+id/radio2"
        android:orientation="horizontal">
        <RadioButton 
          android:id="@+id/radio1"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Mètre"
        />
        <RadioButton 
          android:id="@+id/radio2"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Centimètre"
        />
      </RadioGroup>
      <CheckBox 
        android:id="@+id/mega"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Mega fonction !"
      />
      <TableRow>
        <Button 
          android:id="@+id/calcul"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Calculer l'IMC"
        />
        <Button 
          android:id="@+id/raz"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="RAZ"
          android:layout_column="2"
        />
      </TableRow>
      <TextView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="Résultat:"
      />
      <TextView 
        android:id="@+id/result"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" 
        android:text="Vous devez cliquer sur le bouton « Calculer l'IMC » pour obtenir un résultat."
      />
    </TableLayout>

    FrameLayout : un layout un peu spécial

    Ce layout est plutôt utilisé pour afficher une unique vue. Il peut sembler inutile comme ça, mais ne l'est pas du tout ! Il n'est destiné à afficher qu'un élément, mais il est possible d'en mettre plusieurs dedans puisqu'il s'agit d'un ViewGroup. Si par exemple vous souhaitez faire un album photo, il vous suffit de mettre plusieurs éléments dans le FrameLayout et de ne laisser qu'une seule photo visible, en laissant les autres invisibles grâce à l'attribut android:visibility (cet attribut est disponible pour toutes les vues). Pareil pour un lecteur de PDF, il suffit d'empiler toutes les pages dans le FrameLayout et de n'afficher que la page actuelle, celle du dessus de la pile, à l'utilisateur. Cet attribut peut prendre trois valeurs :
    • visible (View.VISIBLE), la valeur par défaut.
    • invisible (View.INVISIBLE) n'affiche rien, mais est pris en compte pour l'affichage du layout niveau spatial (on lui réserve de la place).
    • gone (View.GONE) n'affiche rien et ne prend pas de place, un peu comme s'il n'était pas là.
    L'équivalent Java de cet attribut est public void setVisibility (int) avec comme paramètre une des valeurs entre parenthèses dans la liste ci-dessus. Quand il y a plusieurs éléments dans un FrameLayout, celui-ci les empile les uns au-dessus des autres, le premier élément du XML se trouvant en dernière position et le dernier ajouté tout au-dessus.

    ScrollView : faire défiler le contenu d'une vue

    Ne vous laissez pas bernez par son nom, cette vue est bel et bien un layout. Elle est par ailleurs un peu particulière puisqu'elle fait juste en sorte d'ajouter une barre de défilement verticale à un autre layout. En effet, si le contenu de votre layout dépasse la taille de l'écran, une partie du contenu sera invisible à l'utilisateur. De façon à rendre ce contenu visible, on peut préciser que la vue est englobée dans une ScrollView, et une barre de défilement s'ajoutera automatiquement.
    Ce layout hérite de FrameLayout, par conséquent il vaut mieux envisager de ne mettre qu'une seule vue dedans.
    Il s'utilise en général avec LinearLayout, mais peut être utilisé avec tous les layouts… ou bien des widgets ! Par exemple :
    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">
      <LinearLayout>
        <!-- contenu du layout -->
      </LinearLayout>
    </ScrollView>
    • LinearLayout permet d'afficher plusieurs vues sur une même ligne de manière horizontale ou verticale. Il est possible d'attribuer un poids aux vues pour effectuer des placements précis.
    • RelativeLayout permet d'afficher des vues les unes en fonction des autres.
    • TableLayout permet d'organiser les éléments en tableau.
    • FrameLayout permet d'afficher une vue à l'écran ou d'en superposer plusieurs les unes au-dessus des autres.
    • ScrollView permet de rendre « scrollable » la vue qu'elle contient. Attention de ne lui donner qu'un fils et de ne pas fournir des vues déjà « scrollable » sinon il y aura des conflits.






Aucun commentaire:

Enregistrer un commentaire