Activité n°1 : IHM

Objectif

L’objectif est d’écrire sa première application Android en utilisant les mécanismes de base.

Remarque : cette activité est fortement inspirée de l’Épisode 4 : Premiers pas de programmation d’Alain Menu !

Pré-requis

Pour développer des applications Android, il faut au moins disposer du Android SDK et du kit de développement Java. Pour faciliter le développement, il est conseillé d’untiliser un EDI/IDE (Integrated Development Environment) comme Android Studio.

Android Studio est un environnement de développement pour développer des applications Android :

  1. Installer Android Studio

Android Studio permet principalement d’éditer les fichiers Java et les fichiers de configuration d’une application Android.

Il propose entre autres des outils pour gérer le développement d’applications multilingues et permet de visualiser la mise en page des écrans sur des écrans de résolutions variées simultanément.

  1. Créer votre première application

Pour rappel, les principales commandes sont :

  • Lancer Android Studio : studio.sh

  • Lancer Android SDK Manager : android

  • Tester la connexion avec un périphérique Android : adb devices

Les composants d’un projet Android

Les applications Android sont constituées de composants à couplage, liés par un manifeste. Un projet Android contient essentiellement des fichiers sources .java et des fichiers de configuration XML.

AndroidManifest.xml

Chaque projet android doit comporter un fichier XML nommé AndroidManifest.xml (localisé dans app -> manifests) stocké à la racine de la hiérarchie du projet.

Le manifeste décrit les composants, leurs interactions et les métadonnées de l’application, dont notamment ses exigences en matière de plateforme et de matériel.

Exemple :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.tv.myapplication">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

Gradle

Gradle est un outil de construction automatique de projet qui a été intégré à Android Studio sous forme de plugin. C’est un outil comparable à ant ou make.

Les composants Android

Il existe 7 types de composants :

  • Activities : C’est la couche présentation de l’application, chaque écran est une extension de la classe Activity et utilise des Views pour former une interface utilisateur.

  • Services : Ces composants tournent en arrière-plan pour déclencher des Notifications et mettre à jour les sources de données et les Activities visibles.

  • Content Providers : Ces composants gèrent les sources de données partageables ; ils peuvent être configurés pour en autoriser l’accès à d’autres applications et pour utiliser celles qu’elles exposent.

  • Intents : Ce sont des composants de communication entre applications qui permettent de diffuser des messages au sein du système ou à destination d’une Activity ou d’un Service cible.

  • Broadcast Receivers : Ce sont des consommateurs de messages diffusés par les Intents. Ils sont à l’écoute des intentions envoyées par les autres composants applicatifs et peuvent démarrer automatiquement l’application pour répondre à un message entrant.

  • Widgets : Ce sont des variantes des Broadcast Receivers permettant de créer des composants interactifs et dynamiques incorporables dans l’écran d’accueil.

  • Notifications : Ces composants permettent d’envoyer un signal aux utilisateurs sans dérober le focus ni interrompre les Activities en cours.

IHM

L’IHM est aussi un composant essentiel. Une activité utilise une IHM. Les IHM sont stockées dans res -> layout sous forme de fichiers XML. Lorsqu’on double-clique sur ces fichiers, on obtient une vue avec l’éditeur graphique en mode design qui fournit une palette d’objets.

Les IHM sont construites avec un layout, un objet graphique qui sert de mise en page pour les autres composants graphiques (boutons, …).

Le layout RelativeLayout autorise les positionnements relatifs entre objets graphiques :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.example.tv.myapplication.MainActivity"
    tools:showIn="@layout/activity_main">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World !"/>
</RelativeLayout>

Certaines valeurs sont référencées dans des fichiers XML localisés dans res -> values (strings.xml par exemple).

Fabrication

La phase de build et déploiement via Studio est très simple puisqu’elle consiste simplement à cliquer sur le bouton ‘Run’.

En fait le processus est plus complexe :

Le code source

Éditer le source principal de l’application, localisé dans l’arborescence du projet sous app -> java -> com.example.tv.myapplication > MainActivity.java :

package com.example.tv.myapplication;

import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity
{

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        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();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings)
        {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Le générateur de code a seulement produit le squelette d’une activité qu’il conviendra de compléter. En principe, les Activités héritent directement de Activity (la classe de base du composant visuel interactif de l’application). En fait notre classe principale MainActivity étend la classe AppCompatActivity pour assurer un support de compatibilité entre les différentes API.

Le générateur de code a proposé l’utilisation d’un menu via les méthodes onCreateOptionsMenu() et onOptionsItemSelected(). On peut les conserver si on souhaite ajouter des menus par la suite.

Le point d’entrée d’une application Android est la méthode onCreate() (et non main() pour un programme Java standard). La méthode onCreate() est la première méthode exécutée de votre application et se charge entre autres de l’IHM via setContentView() et le layout activity_main.

Les ressources du projet sont stockées dans le dossier res avec notamment les sous-dossiers drawable, layout et values. Elles sont référencées dans le code par la construction R.<resourcefolder>.<resourcename>.

Les applications Android ont la particularité de redémarrer lorsque l’équipement est pivoté de paysage à portait et vice-versa : le système détruit et redémarre l’application. Ce comportement par défaut peut être modifié par l’application elle-même. Afin de retrouver l’état précédent, un objet de type Bundle est transmis à la méthode onCreate(). L’objet Bundle contient les états internes et toutes les variables de l’application nécessaires à son redémarrage :

public void onSaveInstanceState(Bundle savedInstanceState)
{
    // sauvegarde des états internes
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
    
    // appel de la classe mère
    super.onSaveInstanceState(savedInstanceState);
}

Remarque : on peut tester avec l’émulateur et les touches Ctrl + Gauche ou Ctrl + Droit sous GNU/Linux.

Bilan

Même si l’architecture de l’application semble un peu compliquée, la structure proposée présente plusieurs avantages :

  • la logique de l’application et sa présentation sont parfaitement découplées,
  • le placement des éléments d’interface est beaucoup plus facile à traiter avec une arborescence XML que par de code,
  • tous les textes sont centralisés, ce qui facilite l’internationalisation de l’application.

Activité n°1

Icône personnalisée

  1. Modifier l’icône par défaut de l’application.

L’icône de l’application est disponible sous différents formats pour s’adapter aux différentes résolutions d’appareils :

  • ldpi : 36 x 36 pixels (obsolète)
  • mdpi : 48 x 48 pixels
  • hdpi : 72 x 72 pixels
  • xhdpi : 96 x 96 pixels
  • xxhdpi : 144 x 144 pixels

Dans la vue du Projet, faire un clic droit sur app puis choisir New -> Image Asset :

Cliquer Image pour Asset Type puis sur Path et déposer la nouvelle icône :

Ajout de ressources

Pour le moment, l’application se contente toujours d’affcher le traditionnel texte de bienvenue.

L’affichage d’une chaîne de caractères dans l’application peut être géré de deux façons :

  • directement dans le code source Java en utilisant un objet de type TextView :
package com.example.tv.myapplication;

import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity
{

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        
        TextView textView = new TextView(this);
        textView.setText("Bienvenue le monde !");
        setContentView(textView);
    }
}
  • par l’intermédiaire de ressources sous forme XML : ici dans content_main.xml

Il est aussi possible d’ajouter des chaînes de caractères dans le fichier strings.xml localisé dans app -> res -> values.

Éditer le fichier strings.xml.

  1. Traduire en français les deux chaînes app_name et action_settings.

  2. Ajouter deux chaînes nommées copyright et commentaires. La première doit contenir la signature du développeur (du style « (c)2016 by Me »), et la deuxième un commentaire volontairement plus long tel que : « Ma première application pour Android développée avec Android Studio ! ».

Remarque : La séquence “(c)” peut être inscrite dans la chaine sous sa forme HTML "&#169;".

  1. Ajouter un objet de type ScrollView puis lui appliquer un layout RelativeLayout. Ajouter deux objets de type TextView et éditer leur id.

Les contraintes de placement d’un objet sont ensuite fixées relativement à un autre objet spécifié par son id. Par exemple, l’objet textViewCommentaires est contraint par l’attribut android:layout_below="@+id/textViewCopyright" à être placé directement sous l’objet d’id textViewCopyright.

Les attributs layout_width et layout_height permettent de spécifer l’adaptation d’un élément à son contenu :

  • match_parent demande à l’élément de remplir totalement son parent ;
  • wrap_content demande à l’élément de s’adapter à son contenu ;
  • Une valeur numérique en points (dp) permet de forcer une dimension absolue.
  1. Forcer la hauteur du textViewCommentaires à exactement 50 dp grâce à son attribut android:height. Centrer les textes dans leur conteneur au moyen de l’attribut android:gravity.

Exemple de résultat attendu :

Copier une image PNG dans le dossier $HOME/AndroidStudioProjects/MyApplication/app/src/main/res/drawable.

  1. Insérer un objet de type ImageView et placer là au-dessus de textViewCopyright. Éditer l’attribut src ... avec la valeur @drawable/<votreimage> qui correspond à votre image.

Exemple de résultat attendu :

Interception des actions utilisateur

Les éléments actuels de l’unique page de l’application sont rangés suivant une stratégie défnie par un RelativeLayout racine. Android offre plusieurs possibilités de stratégie de placement des composants, parmi lesquels :

  • RelativeLayout qui laisse les éléments enfants se positionner en relatif par rapport au parent ;
  • AbsoluteLayout qui permet aux éléments enfants de se placer de façon exacte par rapport au parent ;
  • FrameLayout qui oblige l’ensemble des enfants à se placer au coin supérieur gauche ;
  • LinearLayout qui permet d’aligner les enfants dans une direction unique, horizontale ou verticale…

Ces éléments peuvent être imbriqués entre eux : un LinearLayout vertical peut par exemple contenir comme élément un LinearLayout horizontal …

  1. Modifer content_main.xml de manière à ajouter en bas de la page d’application une ligne contenant trois boutons (de classe Button) dans un LinearLayout horizontal. Fixer l’attribut layout_width du dernier bouton de manière à ce qu’il complète horizontalement le remplissage de son parent (fill_parent).

Exemple de résultat attendu :

On va modifer le code de l’application de manière à ce que l’appui sur le bouton « Hide » efface le texte du commentaire, et que l’appui sur le bouton « Show » le restitue.

  1. Modifer MainActivity.java comme dans l’exemple ci-dessous qui montre la récupération des id des objets, puis la mise en place d’un gestionnaire d’écoute pour l’évènement onClick du bouton « Hide ».

Code source à placer à l’intérieur de la méthode onCreate() à la suite de l’appel à setContentView() :

final TextView commentaires = (TextView)findViewById(R.id.textViewCommentaires);
Button btnHide = (Button)findViewById(R.id.buttonHide);

btnHide.setOnClickListener(
        new View.OnClickListener()
        {
            public void onClick(View v)
            {
                commentaires.setText("");
            }
        }
);

Remarque : La méthode setOnClickListener() installe un gestionnaire d’écoute pour l’objet btnHide. Cette méthode reçoit en argument une interface de callback OnClickListener() de classe View, implémentée par sa méthode onClick().

  1. Coder le gestionnaire d’écoute pour l’évènement onClick du bouton « Show ». Celui-ci doit restituer le texte accessible à partir de R.string.commentaires.

Le troisième bouton « Test … » va pour l’instant nous servir à afficher une fenêtre temporaire avec un message. Pour cela, nous utilisons la classe Toast qui fournit un moyen très simple pour signaler à l’utilisateur de manière discrète, rapide et efficace, un événement non bloquant, sous la forme d’un affichage discret et non modal.

La méthode makeText() crée le composant de classe Toast qui est ensuite affiché par show(). Elle nécessite trois arguments : le contexte de l’application, le texte à afficher et une durée à choisir entre LENGTH_SHORT et LENGTH_LONG.

Toast toast = Toast.makeText(getApplicationContext(), "Test !", Toast.LENGTH_SHORT);
toast.show();
  1. Coder le gestionnaire d’écoute pour l’évènement onClick du bouton « Test … » afin d’afficher un message temporaire.

Il est possible de détecter un clic effectué sur l’image pour, par exemple, accéder à un site Web au travers du navigateur par défaut de l’appareil.

Jusqu’à maintenant, les interceptions ont été codées une à une dans la méthode onCreate() de l’activité en invoquant setOnClickListener() pour chaque objet concerné.

Une autre solution consiste à spécifier que la classe d’activité implémente l’interface View.OnClickListener. L’activité est ensuite enregistrée auprès des éléments concernés afin qu’elle reçoive les événements lorsque l’utilisateur clique sur ceux-ci. Les événements seront alors traités globalement dans la méthode onClick() de la classe.

On modifie la classe MainActivity de la manière suivante en y intégrant aussi un attribut privé pour gérer l’image :

public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
    private ImageView imageLogo;
    
    ...
    
    @Override
    public void onClick(View view)
    {
        if(view == imageLogo)
        {
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.lasalle84.net/"));
            startActivity(intent);
        }
    }
}

Un Intent est un objet permettant la transmission entre composants de messages contenant de l’information : demande de démarrage d’une autre activité, action à effectuer, … Un clic sur l’image entraine la création d’un Intent de type action (premier argument). Ce type d’action est défini par le framework et consiste ici à « voir » l’URI (Uniform Resource Identifer) spécifiée par le deuxième argument. Le lancement effectif de l’intention est réalisé par startActivity(). L’action ne défnissant pas une application en particulier, on parle de démarrage d’une activité implicite. Android va alors rechercher une application susceptible de répondre à la demande : ici le navigateur Web par défaut utilisé par l’appareil.

  1. Modifer MainActivity.java de manière à récupérer l’élément image dans l’attribut privé imageLogo de classe ImageView, puis appeler la méthode setOnClickListener(this) pour que l’activité intercepte le clic.

Lancement d’une activité

En pratique, une application est souvent constituée de plusieurs pages parmi lesquelles l’utilisateur peut naviguer selon son gré.

On va utiliser le bouton « Test … » pour provoquer l’affichage d’une nouvelle page. Sous Android, une nouvelle page est … une nouvelle activité !

On va donc devoir ajouter au projet une nouvelle activité.

  1. Créer un nouveau fichier ressource nommé commande.xml, de type layout avec comme élément racine un LinearLayout.

Exemple de résultat attendu :

  1. Créer une nouvelle activité Empty nommée MyActivity (le fichier ressource layout aurait pu être créé automatiquement).

Puis éditer le fichier et ajouter le code nécessaire pour associer la vue commande à l’activité.

public class MyActivity extends AppCompatActivity
{

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.commande);
    }
}
  1. Coder maintenant le gestionnaire d’écoute pour l’évènement onClick du bouton « Test … » afin d’afficher la nouvelle page.

Pour afficher la vue associée à l’activité créée, on utilise un objet de type Intent :

Intent intent = new Intent(MainActivity.this, MyActivity.class);
startActivity(intent);

Remarque : La touche retour de l’appareil permet de revenir sur la page d’accueil.

  1. Coder ensuite le gestionnaire d’écoute pour l’évènement onClick du bouton « Connecter » afin d’afficher le message temporaire Toast : « Connexion vers : … ». On récupérera le contenu des zones de saisie de la manière suivante :
String requete = "Connexion vers " + edtAdresseIP.getText().toString() + ":" + edtPort.getText().toString() + " ...";

Le menu de l’application

Le squelette utilisé au début à créer un fichier menu.xml dans app -> res -> menu.

On va ajouter une entrée ‘Quitter’ en lui indiquant un id :

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      tools:context="com.example.tv.myapplication2.MainActivity">
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:title="@string/action_settings"
        app:showAsAction="never"/>
    <item android:id="@+id/quitter"
          android:title="Quitter" />
</menu>

Lorsqu’on appuie sur la touche Menu du smartphone, la méthode onCreateOptionsMenu() sera appelée :

public boolean onCreateOptionsMenu(Menu menu)
{
    // Création d'un MenuInflater qui va permettre d'instancier un Menu XML en un objet Menu
    //MenuInflater inflater = getMenuInflater();
    
    // Instanciation du menu XML spécifié en un objet Menu
    //inflater.inflate(R.menu.menu_main, menu);
    
    // En une seule ligne
    getMenuInflater().inflate(R.menu.menu_main, menu);
    
    return true;
}

Si on clique alors sur une entrée de menu, on déclenchera la méthode onOptionsItemSelected() :

public boolean onOptionsItemSelected(MenuItem item)
{
    // Récupère l'id de l'item sélectionné
    int id = item.getItemId();

    // En fonction de l'item séléctionné, on déclenche une action ...
    switch (id)
    {
        case R.id.action_settings:
            Toast.makeText(getApplicationContext(), "TODO", Toast.LENGTH_SHORT).show();
            return true;
        case R.id.quitter:
            // Pour fermer l'application, il suffit de faire finish()
            finish();
            return true;
    }

    return super.onOptionsItemSelected(item);
}
  1. Ajouter une entrée de menu ‘Test’ qui permet de lancer l’activité créée précédemment.

Divers

Le squelette utilisé au départ a ajouté un bouton flottant (FloatingActionButton) sous la forme d’une icône ‘courriel’.

  1. Retirer le de l’application. Pour cela, il faut :
  • supprimmer les lignes suivantes dans activity_main.xml :
<android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email"/>
  • retirer ensuite les lignes de code ci-dessous dans la méthode onCreate() du fichier MainActivity.java :
/*FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener()
{
    @Override
    public void onClick(View view)
    {
        Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show();
    }
});*/
  1. Pour finir, on va retirer la barre de titre dans cette nouvelle page.

Pour cela, éditer tout d’abord le fichier styles.xml dans app -> res -> values et ajouter :

<style name="AppTheme.NoTitleBar">
        <item name="windowNoTitle">true</item>
</style>

Puis éditer le fichier AndroidManifest.xml :

<activity android:name=".MyActivity" android:theme="@style/AppTheme.NoTitleBar"></activity>

Reconstruire et tester.

Remarque : pour une activité de type Activity -> ici.

Documentation

Retour