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 !
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 :
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.
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 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.
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 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.
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.
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).
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 :
É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.
Même si l’architecture de l’application semble un peu compliquée, la structure proposée présente plusieurs avantages :
L’icône de l’application est disponible sous différents formats pour s’adapter aux différentes résolutions d’appareils :
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 :
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 :
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);
}
}
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
.
Traduire en français les deux chaînes app_name
et action_settings
.
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 "©"
.
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 ;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
.
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 :
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 …
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.
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()
.
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();
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.
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.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é.
commande.xml
, de type layout avec comme élément racine un LinearLayout
.Exemple de résultat attendu :
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);
}
}
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.
onClick
du bouton « Connecter » afin d’afficher le message temporaire Toast
: « Connexion vers String requete = "Connexion vers " + edtAdresseIP.getText().toString() + ":" + edtPort.getText().toString() + " ...";
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);
}
Le squelette utilisé au départ a ajouté un bouton flottant (FloatingActionButton
) sous la forme d’une icône ‘courriel’.
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"/>
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();
}
});*/
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.