• 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 13/01/2020

Interagissez avec votre application

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Nous voulons réaliser des applications communicantes exploitant les informations de capteurs pour réaliser des actions complexes (Tout un programme ! :D ). Mais avant d'en arriver là, nous devons savoir faire une interface graphique. Même si cela n'est pas vraiment dans nos objectifs principaux, il n'existe pas de mode texte en Android. Il n'est donc pas possible de faire des applications sans savoir créer des boutons ou des champs de saisie. Nous allons voir cela en 2 parties : d'abord la réalisation du visuel, ensuite l'association d'actions aux événements.

Construisez une interface graphique : les grands principes

Le visuel d'une application est constitué de widgets organisés par des conteneurs. On appellera Widget, un petit objet visuel simple, par opposition aux conteneurs, qui sont des objets structurants. Les Widgets les plus courants sont (cf. illustration suivante) les boutons (Button), les potentiomètres (SeekBar), les champs texte (EditText), les étiquettes (TextView) et les cases à cocher (CheckBox). Tout objet est défini par une taille et un parent. On appelle parent l'objet dans lequel est placé le widget. Le parent de l'objet principal est l'écran.

Principaux Widgets Android
Principaux Widgets Android

Il est important d'utiliser des conteneurs pour structurer le visuel d'une application et de ne pas chercher à placer tous les objets par des coordonnées (x,y), car on ne maitrise pas la dimension de l'écran qui sera utilisé pour exécuter notre application.

Exemples de conteneurs
Exemples de conteneurs

Parmi les conteneurs, on trouve notamment les Layouts, aussi appelés gabarits. Leur rôle est d'être invisible mais de permettre un placement automatique des objets. Par exemple, un LinearLayouthorizontal a pour objectif de remplir une ligne. Si on y place 3 boutons, alors ils seront affichés côte à côte et étirés (ou écrasés) de sorte à occuper toute la ligne. Cependant, le premier gabarit auquel on est confronté est le ConstraintLayout. C'est celui généré par défaut dans le Layout d'un nouveau programme. Il permet de placer des objets les uns par rapport aux autres et par rapport aux bords. Ainsi, construire le visuel d'une interface graphique consiste à choisir des widgets et à les organiser avec des gabarits, qui seront placés les uns dans les autres.

À titre d'exemple, la figure suivante montre un visuel constitué d'une imbrication de gabarits. Le conteneur principal est un LinearLayout  vertical, où chaque ligne contient un LinearLayouthorizontal formant les lignes. Chacune de ces lignes est ensuite constituée de différents Widgets.

Exemple de cascade d'objets constituant un visuel
Exemple de cascade d'objets constituant un visuel

Propriétés importantes des objets graphiques

Les trois propriétés suivantes sont les plus courantes dans un objet graphique : sa taille, son identifiant et le texte affiché.

Définition de la dimension d'un objet graphique

Les objets graphiques sont définis par différentes propriétés. Parmi elles, deux sont obligatoires : la largeur (android:layout_width) et la hauteur de l'objet (android:layout_height). Ces dimensions peuvent être exprimées en absolu ou en relatif  :

  • wrap_content : cette consigne dit que l'objet doit être aussi petit que le permet son contenu, sans que ce dernier ne soit altéré ;

  • match_parent : cette consigne dit que l'objet doit être aussi grand que le permet l'objet le contenant (son parent) ;

  • Valeur numérique : taille (en pixels par exemple)

Il est souvent préférable de favoriser les deux premières dimensions, qui sont relatives, et qui favoriseront l'adaptation de votre interface à l'écran utilisateur.

Voici un l'exemple de définition d'un potentiomètre de hauteur « normale », mais aussi large que possible :

<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Identification des objets graphiques

La propriété id, quand à elle, permet de donner un identifiant à un objet. Cela est utile principalement dans deux cas de figure : le placement relatif d'objets et le lien entre Java et XML. Concrètement, si on utilise unConstraintLayout  ou un  RelativeLayout  pour organiser des objets les uns par rapport aux autres, alors il est nécessaire que les objets soient nommés. Ainsi, la propriété app:layout_constraintTop_toBottomOf="@id/clearBtn"  utilisée dans un objet placé dans un ConstraintLayout  permettra de demander que cet objet soit verticalement sous l'objet nommé clearBtn.

Gestion du texte affiché sur un objet graphique

Un grand nombre d'objets graphiques contiennent notamment un texte. Ce texte peut être fourni en paramètre XML via le paramètre  android:text  soit directement, soit indirectement. La recommandation est de placer toutes ces chaines de caractères constantes dans un fichier  string.xml  et d'y faire toujours référence, ce qui permet ensuite très facilement de faire une application multilingue.

L'exemple suivant montre 2 boutons, chacun identifié par un nom (unnom  et unautrenom), et tous deux aussi larges que possible. Le texte du premier est directement présent, tandis que celui du second est une indirection vers la constante textuelle untexte, définie dans une ressource string.

<Button
android:text="Texte apparaissant sur le bouton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/unnom" />
<Button
android:text="@string/untexte"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/unautrenom" />

Gabarit le plus courant :ConstraintLayout

Ce gabarit est le plus courant, car c'est celui généré par défaut dans un projet vierge. Il est intéressant, car il permet de réduire la surcharge d'imbrication de gabarits nécessaires à la construction d'un visuel, mais il nécessite que l'on soit organisé >_<, car si toute l'interface est construite autour d'un objet que l'on décide ensuite d'enlever, alors elle peut fortement s'écrouler. Prenons donc juste 2 minutes pour comprendre son fonctionnement.

Dans l'éditeur Wysiwyg, lorsque vous placez un objet dans un gabarit de type ConstraintLayout, l'objet peut être placé librement. Mais cela générera une erreur tant que l'objet ne sera pas lié horizontalement et verticalement à quelque chose. Pour créer le lien, il faut utiliser les cercles placés sur le bord de l'objet et les étirer jusqu'à un bord du parent ou d'un autre objet. Une fois le rattachement fait, en déplaçant l'objet, on modifie la taille de la marge (en pixel).

Il est également possible de centrer un objet entre 2 points d'accroche. Il suffit d'aimanter les deux côtés sur les 2 points. Par déplacement de l'objet ainsi accroché, on modifie son placement, proportionnellement à l'écart des deux objets. La propriétéapp:layout_constraintVertical_bias permet de configurer le placement vertical d'un objet aimanté en haut et en bas. Cette propriété s'exprime en pourcentage en notation décimale (valeurs dans l'intervalle [0..1]). La valeur 0.5 correspondra alors à une position centrée entre les deux points d'accroche.

Notion de style

Si certains éléments de mise en forme sont récurrents (couleur de fond, largeur d'un objet, …), alors il est possible de définir des styles de manière assez simple. Il suffit de compléter le fichier styles.xml  en ajoutant une nouvelle balise style en lui attribuant un nom. Ensuite, pour chaque propriété voulue, il faut créer une entrée item, où dont le nom est le nom de la propriété voulu et la valeur est la valeur de la propriété. Par exemple :

<style name="style_petit_objet">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
</style>

Il est ensuite possible d'utiliser un style via la propriété style :

<Button style="@style/style_petit_objet" android:text="Texte du bouton" />

Maintenant que vous avez compris les bases de la création du visuel d'une activité, intéressons-nous à faire le lien entre les parties Java et XML d'un programme.

Lien entre le visuel et le reste du programme

Si le visuel est défini dans un fichier XML, toute la partie interaction, quant à elle, est définie dans des classes java. Nous allons donc avoir deux problèmes à aborder :

  • Comment faire le lien entre Java et XML ?

  • Comment réagir à un événement ?

Comment faire le lien entre Java et XML ?

Lorsque vous construisez une interface graphique dans un fichier XML, c'est ce fichier qui va être utilisé comme recette pour l'instanciation des objets en mémoire. Si on veut, par exemple, modifier le texte affiché sur un bouton, il ne faudrait pas créer un nouveau bouton. Nous allons donc utiliser un identifiant XML pour récupérer une référence vers le bouton à modifier.

Les identifiants XML sont des entiers (un moyen comme un autre d'identifier  ;) ). Mais comme ce ne sont pas des références vers les objets, vous devrez utiliser la méthode findViewById  pour récupérer la vue associée à un identifiant. Cette méthode est héritée d'Activity.

Ainsi, la simple commande suivante permettra de récupérer une référence vers un bouton nommémybtndans le XML instancié :

View btn = findViewById(R.id.mybtn);

Bien sûr, si vous voulez exploiter ses caractéristiques liées à son état de bouton, alors il faut penser à caster  le résultat obtenu :

Button btn = (Button)findViewById(R.id.mybtn);

Quand j'ai fait mumuse avec l'éditeur Wysiwyg, j'ai placé de nouveaux objets que je ne connaissais pas. Comment puis-je savoir quel type utiliser dans mon programme Java ?

N'oubliez pas que le nom des balises XML est le nom des types Java. L'information est donc directement accessible en XML :).

Pour vérifier que l'on a bien tout compris, le plus simple est de faire une action depuis Java, visible graphiquement, comme modifier le texte présent sur le bouton. Ce qui donnerait :

Button btn = (Button)findViewById(R.id.mybtn);
btn.setText("Texte modifié depuis Java");

Après avoir exécuter ce programme, vous ne devriez voir que "Texte modifié depuis Java" et jamais le texte d'origine, défini dans le XML.

Maintenant que l'on sait interagir entre Java et l'interface graphique, il ne reste plus qu'à intégrer la gestion des événements :)

Comment réagir à un événement ?

Rappel du principe général

Vous savez normalement déjà faire de la programmation événementielle en Java (sisi ! :pirate: ). Pour rappel, le principe général est qu'il ne faut pas faire une boucle active qui passe son temps à aller voir si un bouton serait dans un état "pressé". On veut plutôt donner un morceau de code à un bouton (ou tout autre objet graphique) en lui disant "Tiens, je te file cette fonction. Garde-la avec toi. Le jour ou un événement particulier survient, tu l'exécuteras.". Cependant, on ne sait pas "donner" une méthode en paramètre sans l'exécuter (ce n'est plus vrai en Java 8, mais le principe est resté). On ne sait que l'exécuter, puis transmettre le résultat. Mais ce n'est pas ce que l'on voudrait. La solution Java (et donc Android) consiste à encapsuler cette méthode dans une classe et à transmettre une instance de la-dite classe. Pour savoir quelle méthode de l'objet doit être exécutée, la classe doit hériter d'un cahier des charges connu : une interface.

Exemple de base : le clic sur un bouton

Ainsi, si je vous dis que l'événement "clic" correspond à l'interface OnClickListener, alors on en déduit les grandes lignes :

btn.setOnClickListener(/*ici, mettre un OnClickListener*/);

La syntaxe que je recommande consiste à faire une classe anonyme appelant une méthode spécialisée. Heureux hasard, l'IDE est très puissant et nous aide grandement dans cette démarche ! :D Pour vous en rendre compte, maintenant que vous avez écrit l'exemple précédent, commencez à écrirenew OnClickListener  en paramètre. Avec les réglages par défaut, il suffit de commencer "new O" pour avoir le menu d'auto-complétion qui s'affiche.

Proposition de complétion de l'écouteur
Proposition de complétion de l'écouteur

En validant le premier choix, il vous est proposé une classe anonyme, héritant de OnClickListener  et avec la méthode onClick  déjà redéfinie. Il n'y a plus qu'à écrire le code que vous voulez exécuter.

Résultat de l'auto-complétion
Résultat de l'auto-complétion

Pour résumer, voici un code complet qui permet de récupérer une référence vers un bouton, y modifier le texte affiché et faire en sorte que lorsque l'on clique dessus, alors le texte soit à nouveau modifié :

public class MainActivity extends AppCompatActivity {
private Button btn;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button)findViewById(R.id.clearbtn);
btn.setText("Clear");
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Appel vers la méthode spécialisée
effaceur((Button)v);
}
});
}
// Tout objet graphique affichant du texte hérite de TextView.
// C'est également le cas pour Button.
public void effaceur(TextView tv) {
tv.setText("Vous venez de vous faire effacer.");
}
}

Notez que l'événement  onClick  est particulier. C'est le seul où l'association entre la méthode et le XML peut être fait depuis le XML (sans classe anonyme). Pour se faire, il est nécessaire d'écrire une méthode de type void et acceptant un unique paramètre de type View. Ensuite, depuis l'éditeur de Layout, vous pouvez préciser le nom de cette méthode dans la propriété onClick  d'un objet.

Exemple de méthode :

public void effaceur(View tv) {
if(tv instanceof TextView) {
((TextView)tv).setText("Vous venez de vous faire effacer.");
}
}

Exemple d'association de la méthode à l'événement onClick  :

Association d'une méthode à onClick via le design
Association d'une méthode à onClick via le design
D'autres types d'événements courants

Le principe sera similaire pour les différents types d'événements que l'on voudrait capturer. Simplement, il pourra être nécessaire  de redéfinir plusieurs méthodes dans un même écouteur pour réagir à différentes actions liées à un même type d'événement :

  • Pour réagir à une touche ou un relâchement :
    (...).setOnTouchListener(new OnTouchListener() {...});
    On pourra redéfinir la méthode onTouch, dont le paramètre MotionEvent  contient notamment la description de l'action survenue.

  • Pour réagir à un déplacement :
    (...).setOnDragListener(new OnDragListener() {...});
    On pourra redéfinir la méthode onDrag, dont le paramètre DragEvent  contient notamment la description de l'action.

  • Pour réagir à un clic long :
    (...).setOnLongClickListener(new OnLongClickListener() {...});
    On pourra redéfinir la méthodeonLongClick.

  • ...

Exemple de certificat de réussite
Exemple de certificat de réussite