Ressources ‐ Android - vbridonneau/CoursSysteme GitHub Wiki
C'est l'endroit où est stocké le fichier AndroidManifest.xml
.
Ce fichier contient plusieurs informations clés d'une application comme les permissions,
les icônes et les différentes activités contenues dans l'application.
Ce fichier est au format XML.
Dans le cas d'une activité vide, il contient le code suivant :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Appel"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
L'activité principale est donnée dans la balise <activity>
qui contient le nom de l'activité : MainActivity.
On peut également trouver d'autres informations comme l'icône représentant l'application sur le téléphone, donnée par
android:icon
, ou encore le nom de l'application donné par android:label
.
Les ressources dans Android sont stockées dans le dossier res
et contiennent plusieurs éléments importants pour l'application, notamment :
-
Drawable (
res/drawable/
) : Contient les images et formes graphiques utilisées dans l'application. -
Layout (
res/layout/
) : Contient les fichiers XML qui définissent l'apparence des interfaces utilisateur. -
Values (
res/values/
) : Contient des fichiers XML pour stocker les chaînes de caractères (strings.xml
), les couleurs (colors.xml
), les dimensions (dimens.xml
) et les styles (styles.xml
). -
Mipmap (
res/mipmap/
) : Contient les icônes de l'application pour différentes résolutions d'écran.
Exemple d'un fichier res/values/strings.xml
:
<resources>
<string name="app_name">MonApplication</string>
<string name="hello_world">Bonjour le monde !</string>
</resources>
Le code de l'application Android est principalement écrit en Java ou Kotlin.
La classe principale de l'application est généralement une activité qui hérite de AppCompatActivity
et nommée MainActivity.
Exemple d'une activité de base en java :
package com.example.appel;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}
Dans cet exemple :
- La classe
MainActivity
hérite deAppCompatActivity
. -
onCreate
est une méthode appelée au lancement de l'application. Elle est essentielle au cycle de vie de l'activité. -
setContentView(R.layout.activity_main)
définit l'interface utilisateur en utilisant le fichier XMLactivity_main.xml
situé dansres/layout/
.
Le fichier res/layout/activity_main.xml
définit l'interface utilisateur de l'application.
Un des gestionnaires de mise en page les plus couramment utilisés est ConstraintLayout, qui permet d'organiser les éléments avec des contraintes flexibles.
Exemple de activity_main.xml
utilisant ConstraintLayout
:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Bonjour le monde !"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Dans cet exemple :
- Le
ConstraintLayout
est utilisé comme conteneur principal. - Un
TextView
est ajouté au centre de l'écran grâce aux contraintes. Il affiche le texte "Bonjour le monde !". Ce qui permet de centrer le texte dans l'écran est donné par les attributsapp:layout_constraintBottom_toBottomOf="parent"
,app:layout_constraintLeft_toLeftOf="parent"
,app:layout_constraintRight_toRightOf="parent"
etapp:layout_constraintTop_toTopOf="parent"
.
Le cycle de vie d'une activité est important pour comprendre comment les activités interagissent entre elles et comment elles réagissent aux événements.
Une activité passe par plusieurs états tout au long de son cycle de vie.
Les états principaux sont création, démarrage, reprise, pause, arrêt et destruction.
Chaque état est associé à une méthode spécifique qui est appelée à ce moment-là.
Ces méthodes sont onCreate
, onStart
, onResume
, onPause
, onStop
et onDestroy
respectivement.
La figure suivante illustre le cycle de vie d'une activité :

Dans les sections qui suivent, nous allons explorer quelques concepts de base pour créer nos premières applications Android. Parmis eux, nous allons voir comment créer une nouvelle activité, comment passer des données entre activités, comment gérer les événements de clic sur un bouton, comment obtenir des données de l'utilisation des capteurs, comment gérer les permissions, etc.
Dans cette première application, nous allons créer une activité simple qui affiche une date à l'écran.
L'activité contiendra un CalendarView
(widget Android permettant de gérer des dates) et un TextView
qui affichera la date choisie par l'utilisateur.
Notre première application consiste en la création d'une seule activité : MainActivity
.
Cette activité est associée à un fichier XML qui définit l'interface utilisateur.
Nous allons étudier les deux fichiers ainsi que leurs connexions.
Le code de l'activité principal est le suivant :
package com.example.calendrieractivite;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
CalendarView cv;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
cv = findViewById(R.id.calendarView);
tv = findViewById(R.id.tv);
/* Gestion du calendrier */
cv.setOnDateChangeListener((v, a, m, j) -> {
tv.setText(a + "/" + (m + 1) + "/" + j);
});
}
}
Le fichier XML associé à cette activité est le suivant :
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<CalendarView
android:id="@+id/calendarView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Date"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Dans l'activité MainActivity
, nous avons plusieurs éléments importants.
Dans le code Java, nous avons un moyen de récupérer les éléments de l'interface utilisateur définis dans le fichier XML.
Cela se fait en utilisant la méthode findViewById
qui prend en paramètre l'identifiant de l'élément à récupérer.
Par exemple, pour récupérer le CalendarView
et le TextView
, nous utilisons les lignes suivantes :
cv = findViewById(R.id.calendarView);
tv = findViewById(R.id.tv);
La gestion de l'événement de changement de date est faite avec la méthode setOnDateChangeListener
qui prend un écouteur en paramètre.
Cet écouteur réagit lorsque l'utilisateur sélectionne une date dans le CalendarView
.
Dans notre cas, nous mettons à jour le TextView
avec la date sélectionnée.
cv.setOnDateChangeListener((v, a, m, j) -> {
tv.setText(a + "/" + (m + 1) + "/" + j);
});
Cette ligne de code met à jour le TextView
avec la date sélectionnée au format aaaa/mm/jj
.
Elle utilise une lambda expression pour définir le comportement à adopter lorsqu'une date est sélectionnée.
On peut s'en passer en utilisant une classe anonyme qui implémente l'interface CalendarView.OnDateChangeListener
.:
cv.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
@Override
public void onSelectedDayChange(@NonNull CalendarView view, int year, int month, int dayOfMonth) {
tv.setText(year + "/" + (month + 1) + "/" + dayOfMonth);
}
});
Dans le fichier XML, nous utilisons un ConstraintLayout
pour organiser les éléments de l'interface utilisateur.
Pour centrer le CalendarView
dans l'écran, nous utilisons les contraintes suivantes :
<CalendarView
android:id="@+id/calendarView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Si nous voulons placer le CalendarView
sous le TextView
, on peut changer la contrainte app:layout_constraintTop_toTopOf="parent"
en app:layout_constraintTop_toBottomOf="@id/tv"
:
<CalendarView
...
app:layout_constraintTop_toBottomOf="@id/tv" /> <!-- Placer en dessous du TextView identifié par @id/tv -->
Dans cette première application, nous allons créer une activité qui permet d'obtenir un résultat sous forme de texte, en l'occurrence une date.
Notre première application consiste en la création de deux activités : MainActivity
et DateActivity
.
L'activité MainActivity
déclenche l'affichage de l'activité DateActivity
lorsqu'un bouton est cliqué.
Une fois que l'activité DateActivity
est affichée, elle affiche un calendrier et retourne la date sélectionnée à l'activité MainActivity
.
Nous avons donc deux activités :
- MainActivity : L'activité principale qui déclenche l'affichage de la seconde activité.
- DateActivity : L'activité qui retourne une date sous forme de texte.
L'activité principale MainActivity
contient un bouton qui, lorsqu'il est cliqué, déclenche l'affichage de l'activité DateActivity
.
Une fois que l'activité DateActivity
retourne une date, elle est affichée dans un TextView
de l'activité MainActivity
.
package com.example.calendrieractivite;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
Button b;
TextView tv;
ActivityResultLauncher<Intent> launcher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
o -> {
if (o.getResultCode() == RESULT_OK) {
Intent data = o.getData();
if (data != null) {
tv.setText(data.getStringExtra("donnee"));
}
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
b = findViewById(R.id.button);
tv = findViewById(R.id.tv);
/* Gestion d'un bouton */
b.setOnClickListener(v -> {
Intent intent = new Intent(
v.getContext(),
Calendrier.class
);
launcher.launch(intent);
});
}
}
Plusieurs éléments sont à noter dans cette activité.
Premièrement l'attribut laucher
permet de gérer le résultat retourné par une activité.
Il permet de définir le comportement à adopter lorsqu'une activité retourne un résultat.
Pour définir ce comportement, on utilise une expression lambda qui vérifie si le résultat retourné est correct.
Si c'est le cas, on récupère la date retournée par l'activité DateActivity
et on l'affiche dans un TextView
.
ActivityResultLauncher<Intent> launcher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
o -> { /* Lambda expression */
if (o.getResultCode() == RESULT_OK) {
Intent data = o.getData(); /* Récupérer les données retournées */
if (data != null) {
tv.setText(data.getStringExtra("donnee"));
}
}
});
Dans ce code, l'objet Intent
est utilisé pour définir l'activité à lancer. C'est le type de données passé en entré à l'activité DateActivity
.
La méthode registerForActivityResult
permet de définir le comportement à adopter lorsqu'une activité retourne un résultat.
Ce comportement est implémenté sous forme d'une expression lambda qui vérifie si le résultat retourné est correct.
Cette lambda est donnée par o -> { ... }
.
Le type de o
est ActivityResult
, qui contient le résultat retourné par l'activité.
On vérifie si le résultat est correct en utilisant o.getResultCode() == RESULT_OK
.
Si le résultat est correct, on récupère la date retournée par l'activité DateActivity
en utilisant data.getStringExtra("donnee")
.
D'une manière générale, le résultat retourné par une activité est stocké dans un objet Intent
qui peut contenir plusieurs types de données.
Les données résultats peuvent être récupérées à l'aide de méthodes comme getStringExtra
, getIntExtra
, etc. faisant pensées à un tableau associatif.
La date est ensuite affichée dans un TextView
en utilisant tv.setText(data.getStringExtra("donnee"))
.
La seconde chose à noter est la gestion du clic sur un bouton.
Dans notre cas, nous utilisons un Button
qui, lorsqu'il est cliqué, déclenche l'affichage de l'activité DateActivity
.
Pour cela, nous utilisons une expression lambda qui crée un objet Intent
pour lancer l'activité DateActivity
et utilise le launcher
pour lancer l'activité.
b.setOnClickListener(v -> { /* v de type View */
Intent intent = new Intent(
v.getContext(),
Calendrier.class
);
launcher.launch(intent);
});
De la même manière, on peut utiliser une classe anonyme qui implémente l'interface View.OnClickListener
:
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(
v.getContext(),
Calendrier.class
);
launcher.launch(intent);
}
});
Dans ce code, on crée un objet Intent
pour lancer l'activité DateActivity
et on utilise le launcher
pour lancer l'activité.
Les paramètres de l'objet Intent
sont le contexte de la vue v
et la classe de l'activité DateActivity
.
Le contexte est nécessaire pour lancer une activité et est obtenu à partir de la vue v
en utilisant v.getContext()
.
L'activité DateActivity
est lancée en utilisant launcher.launch(intent)
.
Pour cette activité, il n'est nécessaire que de créer un calendrier et de retourner la date sélectionnée à l'activité MainActivity
.
Le code de l'activité DateActivity
est le suivant :
package com.example.calendrieractivite;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.CalendarView;
import android.widget.ImageButton;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class Calendrier extends AppCompatActivity {
CalendarView cv;
ImageButton b;
String date;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_calendrier);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
cv = findViewById(R.id.calendarView);
b = findViewById(R.id.retour);
cv.setOnDateChangeListener((v, a, m, j) -> {
date = a + "/" + (m + 1) + "/" + j;
});
b.setOnClickListener(v -> {
Intent intent = getIntent();
intent.putExtra("donnee", date);
setResult(RESULT_OK, intent);
finish();
});
}
}
Dans cette activité, nous avons la gestion du bouton qui soit important.
Lorsque le bouton est cliqué, la date sélectionnée est retournée à l'activité MainActivity
.
Pour cela, nous utilisons un objet Intent
pour stocker la date sélectionnée et la retourner à l'activité MainActivity
.
b.setOnClickListener(v -> {
Intent intent = getIntent();
intent.putExtra("donnee", date);
setResult(RESULT_OK, intent);
finish();
});
Dans ce code, on récupère l'objet Intent
associé à l'activité DateActivity
en utilisant getIntent()
.
On stocke la date sélectionnée dans l'objet Intent
en utilisant intent.putExtra("donnee", date)
.
La date est stockée avec une clé "donnee" pour pouvoir la récupérer dans l'activité MainActivity
.
On définit le résultat à retourner à l'activité MainActivity
en utilisant setResult(RESULT_OK, intent)
.
Enfin, on termine l'activité DateActivity
et on retourne le résultat à l'activité MainActivity
en utilisant finish()
.
Le résultat retourné est stocké dans l'objet Intent
et peut être récupéré dans l'activité MainActivity
en utilisant data.getStringExtra("donnee")
.
Dans cette application nous allons voir comment demander des permissions à l'utilisateur pour accéder à des fonctionnalités du téléphone.
Pour cela, nous allons créer une application qui demande la permission d'effectuer un appel téléphonique.
L'application contient deux boutons : un pour demander la permission et un pour effectuer un appel téléphonique.
Avant de passer au code de l'activité il est important de déclarer la permission dans le fichier AndroidManifest.xml
.
Cela se fait en rajoutant les lignes suivantes dans le fichier :
<uses-feature android:name="android.hardware.telephony" android:required="yes" />
<uses-permission android:name="android.permission.CALL_PHONE" />
La première ligne déclare que l'application nécessite la fonctionnalité de téléphonie. Cela permet à Google Play de filtrer les appareils qui ne disposent pas de cette fonctionnalité et de ne pas proposer l'application à ces appareils. La seconde ligne déclare que l'application nécessite la permission d'effectuer un appel téléphonique.
Le code de l'activité MainActivity
est le suivant :
package com.example.appelcorrection;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
Button demandePermission;
Button appel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_appel);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
demandePermission = findViewById(R.id.demandePermission);
appel = findViewById(R.id.appel);
demandePermission.setOnClickListener(v -> {
if(ContextCompat.checkSelfPermission(this,android.Manifest.permission.CALL_PHONE) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
(Activity) this,
new String[]{android.Manifest.permission.CALL_PHONE},
0);
}
});
appel.setOnClickListener(v -> {
if(ContextCompat.checkSelfPermission(this,android.Manifest.permission.CALL_PHONE) ==
PackageManager.PERMISSION_GRANTED) {
startActivity(new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + "0612345678")));
}
});
}
}
Dans ce code ce qui est important est la gestion des permissions.
Pour demander une permission, on utilise la méthode ActivityCompat.requestPermissions
qui prend en paramètre l'activité, un tableau de permissions et un code de requête.
Vu que la méthode requestPermissions
prend en paramètre un tableau de permissions, on peut demander plusieurs permissions en même temps.
Dans notre cas, on demande la permission d'effectuer un appel téléphonique en utilisant android.Manifest.permission.CALL_PHONE
.
if(ContextCompat.checkSelfPermission(this,android.Manifest.permission.CALL_PHONE) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
(Activity) this,
new String[]{android.Manifest.permission.CALL_PHONE},
0);
}
Si la permission est accordée, on peut effectuer un appel téléphonique en utilisant startActivity()
.
Dans cet exemple, on utilise l'intent Intent.ACTION_CALL
pour effectuer un appel téléphonique.
L'URI Uri.parse("tel:" + "0612345678")
permet de spécifier le numéro de téléphone à appeler.
startActivity(new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + "0612345678")));
Dans cette application, nous allons voir comment gérer les événements de toucher sur un écran tactile.
Pour cela, nous allons créer une application qui affiche les coordonnées du point touché sur l'écran.
L'application contient un TextView
qui affiche les coordonnées du point touché.
Le code de l'activité MainActivity
est le suivant :
package com.example.motionandtoat;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: // L'utilisateur a appuyé sur l'écran
break;
case MotionEvent.ACTION_MOVE: // L'utilisateur a déplacé son doigt sur l'écran
int x = (int) event.getX();
int y = (int) event.getY();
/* ... */
break;
case MotionEvent.ACTION_UP: // L'utilisateur a relâché l'écran
break;
}
return true;
}
}
Dans ce code, on utilise la méthode onTouchEvent
pour gérer les événements de toucher sur l'écran tactile.
Cette méthode est appelée chaque fois qu'un événement de toucher est détecté.
Elle prend en paramètre un objet MotionEvent
qui contient des informations sur l'événement de toucher.
Dans notre cas, on récupère l'action de l'événement en utilisant event.getAction()
.
L'action de l'événement peut être ACTION_DOWN
si l'utilisateur appuie sur l'écran ou ACTION_UP
si l'utilisateur relâche l'écran.
Si l'utilisateur déplace son doigt sur l'écran, l'action de l'événement est ACTION_MOVE
.
On peut récupérer les coordonnées du point touché en utilisant event.getX()
et event.getY()
.
On peut afficher les coordonnées du point touché en utilisant un Toast
par exemple (voir plus bas pour plus de détails).
case MotionEvent.ACTION_MOVE: // L'utilisateur a déplacé son doigt sur l'écran
int x = (int) event.getX();
int y = (int) event.getY();
Toast.makeText(this, "Coordonnées : " + x + ", " + y, Toast.LENGTH_SHORT).show();
break;
Dans cette application, nous allons voir comment obtenir des données de l'utilisation des capteurs d'un téléphone.
Pour cela, nous allons créer une application qui affiche les valeurs des capteurs de l'accéléromètre.
L'application contient un TextView
qui affiche les valeurs des capteurs de l'accéléromètre.
Le code de l'activité MainActivity
est le suivant :
package com.example.capteurs;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity implements SensorEventListener {
TextView tv;
private SensorManager sensorManager;
private Sensor accelerometre;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
accelerometre = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
tv = findViewById(R.id.tv);
}
@Override
protected void onResume() {
/* On implemente cette méthode pour */
super.onResume();
sensorManager.registerListener(this, accelerometre, SensorManager.SENSOR_DELAY_GAME);
}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(this);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// Récupération des valeurs de l'accéléromètre
float x = event.values[0];
float y = event.values[1];
tv.setText("X : " + x + "Y : " + y);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
// Doit être implémenté
}
}
La gestion des capteurs est faite en utilisant les méthodes en utilisant les classes SensorManager et Sensor.
La méthode getSystemService
permet de récupérer le service des capteurs en utilisant Context.SENSOR_SERVICE
.
La méthode getDefaultSensor
permet de récupérer le capteur de l'accéléromètre en utilisant Sensor.TYPE_ACCELEROMETER
.
La méthode registerListener
permet d'enregistrer un écouteur pour les valeurs des capteurs.
Les fait d'enregistrer et de désenregistrer un écouteur pour les valeurs des capteurs sont faits dans les méthodes onResume
et onPause
.
Cela permet de ne pas consommer de ressources inutilement lorsque l'activité n'est pas visible.
Pour pouvoir récupérer les valeurs des capteurs, il faut implémenter les méthodes onSensorChanged
et onAccuracyChanged
de l'inteface SensorEventListener
.
La méthode onSensorChanged
est appelée chaque fois que les valeurs des capteurs changent.
Dans notre cas, on récupère les valeurs de l'accéléromètre en utilisant event.values[0]
et event.values[1]
.
Dans cette application, nous allons voir comment jouer un son dans une application Android. Pour cela, nous allons créer une application qui joue un son dès que l'utilisateur appuie sur un bouton.
Le code de l'activité MainActivity
est le suivant :
package com.example.myapplication;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.widget.Button;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.HashMap;
public class MainActivity extends AppCompatActivity {
private SoundPool pool;
private AudioManager manager;
private final int MAX_STREAMS = 2;
private HashMap<String, Integer> map;
private Button b1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
manager = (AudioManager) getSystemService(AUDIO_SERVICE);
/* Initialisation */
map = new HashMap<>();
/* Attrributs audios */
AudioAttributes attributes = new AudioAttributes.Builder().
setUsage(AudioAttributes.USAGE_GAME).build();
/* Pool de sons */
pool = new SoundPool.Builder()
.setMaxStreams(MAX_STREAMS)
.setAudioAttributes(attributes).build();
chargerSon();
b1 = findViewById(R.id.button);
b1.setOnClickListener(v -> {
int id = map.get("son1");
pool.play(id, 1, 1, 0, 0, 1);
});
}
private void chargerSon() {
int id = pool.load(getApplicationContext(), R.raw.son1, 1);
map.put("son1", id);
}
}
Plusieurs choses sont à noter dans ce code.
Premièrement, l'usage des services audio.
Dans notre cas, on utilise le service audio pour jouer un son.
Pour cela, on utilise la méthode getSystemService
pour récupérer le service audio en utilisant AUDIO_SERVICE
.
manager = (AudioManager) getSystemService(AUDIO_SERVICE);
Le service audio permet de gérer les flux audio de l'application.
On peut l'utiliser pour gérer le volume, les attributs audio, etc.
Les attributs audios sont gérés par la classe AudioAttributes
qui permet de définir les attributs d'un flux audio.
Dans notre cas, on définit les attributs d'un flux audio en utilisant la classe AudioAttributes.Builder
.
Cette classe permet de construire en plusieurs étapes les attributs d'un flux audio.
Quand on définit les attributs d'un flux audio, on se pause trois questions : pourquoi, quoi et comment:
-
Pourquoi : On définit l'usage du flux audio, c'est-à-dire pourquoi on utilise ce flux audio.
Est-ce pour un jeu vidéo, pour de la musique, pour une notification, etc.
Pour chaque usage, une constante est définie dans la classe
AudioAttributes
et qui permet de définir l'usage du flux audio. Dans notre cas, on utiliseUSAGE_GAME
pour définir l'usage du flux audio. -
Quoi : On définit le type de contenue du flux, quand celui-ci est connue et spécifique.
Par exemple, si on joue de la musique, on peut définir le type de contenu du flux audio en utilisant
CONTENT_TYPE_MUSIC
. -
Comment : On définit la façon dont le flux audio est utilisé.
Par exemple, si on utilise un flux audio pour une notification, on peut définir la façon dont le flux audio est utilisé en utilisant
USAGE_NOTIFICATION
.
Deuxièmement, l'usage de la classe SoundPool
qui permet de jouer des sons dans une application Android.
La classe SoundPool
permet de charger des sons et de les jouer.
Elle est utilisée pour jouer des sons courts et répétitifs, comme des effets sonores dans un jeu vidéo.
La classe SoundPool
est initialisée en utilisant la méthode new SoundPool.Builder()
.
On peut définir le nombre maximum de flux audio en utilisant la méthode setMaxStreams
.
On peut définir les attributs audio en utilisant la méthode setAudioAttributes
.
Dans notre cas, on définit le nombre maximum de flux audio à 2 et on définit les attributs audio en utilisant attributes
.
pool = new SoundPool.Builder()
.setMaxStreams(MAX_STREAMS)
.setAudioAttributes(attributes).build();
On peut charger un son en utilisant la méthode load
de la classe SoundPool
.
Cette méthode prend en paramètre le contexte de l'application, le son à charger et la priorité du son.
Le son est chargé et son identifiant est retourné puis stocké dans une HashMap
.
int id = pool.load(getApplicationContext(), R.raw.son1, 1);
map.put("son1", id);
Le son est stocké dans le dossier res/raw
de l'application.
Enfin, on peut jouer un son en utilisant la méthode play
de la classe SoundPool
.
Cette méthode prend en paramètre l'identifiant du son à jouer, le volume gauche et droit, la priorité du son, le nombre de répétitions et la vitesse de lecture.
Dans notre cas, on joue le son en utilisant pool.play(id, 1, 1, 0, 0, 1)
.
Le premier paramètre est l'identifiant du son à jouer, le second et le troisième paramètres sont le volume gauche et droit, le quatrième paramètre est la priorité du son, le cinquième paramètre est le nombre de répétitions et le sixième paramètre est la vitesse de lecture.
Dans cette application, nous allons voir comment afficher des messages temporaires à l'utilisateur en utilisant des toasts. Un toast est un message qui apparaît à l'écran pendant un court instant et disparaît automatiquement. Pour cela, nous allons créer une application qui affiche un toast dès que l'application est lancée.
Le code de l'activité MainActivity
est le suivant :
package com.example.motionandtoat;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
Toast.makeText(this, "Notification toast", Toast.LENGTH_SHORT).show(); // Afficher un toast
}
}
Dans ce code, on utilise la méthode Toast.makeText
pour créer un toast.
Cette méthode prend en paramètre le contexte de l'application, le message à afficher et la durée d'affichage du toast.
Le contexte de l'application est obtenu en utilisant this
.
Le message à afficher est donné par "Notification toast"
.
La durée d'affichage du toast est donnée par Toast.LENGTH_SHORT
.
Elle correspond à une durée de 2 secondes.
Elle peut être remplacée par Toast.LENGTH_LONG
pour une durée de 3,5 secondes.
Enfin, on affiche le toast en utilisant show()
.
Dans cette application, nous allons voir comment afficher une image dans une application Android pour quelle suive le mouvement de l'utilisateur.
Pour cela, nous allons créer une application qui affiche une image à l'écran.
L'image est stockée dans le dossier res/drawable
de l'application.
Pour ce faire, nous allons utiliser une image de croix disponible dans le dossier ressource du dépôt git de ce cours.
L'image doit être stockée dans le dossier AndroidStudioProjects/[Nom de votre projet]/app/src/main/res/drawable/ de l'application.
Le code de l'activité MainActivity
est le suivant :
package com.example.imagedeplacement;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
ImageView iw;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
iw = findViewById(R.id.imageView);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE: // L'utilisateur a déplacé son doigt sur l'écran
int x = (int) event.getX();
int y = (int) event.getY();
iw.setX(x); // Déplacer l'image horizontalement
iw.setY(y); // Déplacer l'image verticalement
break;
default:
break;
}
return true;
}
}
La gestion de la position de l'image est faite dans la méthode onTouchEvent
.
Pour récupérer les coordonnées de, l'image, on utilise iw.getX()
et iw.getY()
.
Pour déplacer l'image, on utilise iw.setX(x)
et iw.setY(y)
.
Ce qui importe le plus pour cette application est la gestion de l'image dans le fichier XML.
Le code XML du fichier activity_main.xml
est le suivant :
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="50dp"
android:layout_height="50dp"
app:srcCompat="@drawable/croix"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Dans ce code, on utilise un ImageView
pour afficher l'image.
L'image est donnée par l'attribut app:srcCompat="@drawable/croix"
.
L'image croix
est stockée dans le dossier res/drawable
de l'application.
On peut remarquer que le nom de l'image est donné sans l'extension .png
et qu'elle est identifié avec le prefixe @drawable/
indiquant qu'elle est stockée dans le dossier res/drawable
.
L'attribut android:scaleType="fitXY"
permet de redimensionner l'image pour qu'elle remplisse l'espace disponible.
La taille de l'image est donnée par les attributs android:layout_width="50dp"
et android:layout_height="50dp"
.
Ces attributs définissent la largeur et la hauteur de l'image en pixels.
On se propose de créer un jeu simple qui consiste ressemble au jeu du Simon.
Le but du jeu est de reproduire une séquence de couleurs qui s'affiche à l'écran.
Le jeu est composé de quatre couleurs : rouge, vert, bleu et jaune.
Le joueur doit reproduire la séquence de couleurs en appuyant sur les boutons de couleur.
On pourra utiliser la classe suivante qui étend SurfaceView
pour créer le jeu.
package com.example.simon;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.media.SoundPool;
import android.os.Handler;
import android.util.Log;
import android.view.SurfaceView;
public class GameView extends SurfaceView {
private Paint paint;
protected int selectedColor;
protected int colors[] = {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW};
private Handler handler;
protected SoundPool sp;
public GameView(Context context) {
super(context);
this.selectedColor= Color.BLACK;
paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
setWillNotDraw(false);
handler = new Handler();
// TODO : Initialiser le SoundPool ici si nécessaire
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawSimonButtons(canvas);
}
private void drawSimonButtons(Canvas canvas) {
int width = getWidth();
int height = getHeight();
for (int i = 0; i < 4; i++) {
paint.setColor(colors[i]);
if (this.selectedColor == colors[i])
paint.setColor(assombrirCouleur(colors[i]));
else
paint.setColor(colors[i]);
canvas.drawRect(i % 2 * width / 2, i / 2 * height / 2, (i % 2 + 1) * width / 2, (i / 2 + 1) * height / 2, paint);
}
}
//get selectedColor
public int getSelectedColor() {
return selectedColor;
}
//set selectedColor
public void setSelectedColor(int selectedColor) {
this.selectedColor = selectedColor;
// invalidate(); // Redessiner la vue après avoir changé la couleur
}
private int assombrirCouleur(int color) {
float ratio = 1.0f - 0.5f; // Ratio pour assombrir la couleur
int a = (color >> 24) & 0xFF; // Extraire le canal alpha
int r = (int) (((color >> 16) & 0xFF) * ratio); // Modifier le canal rouge
int g = (int) (((color >> 8) & 0xFF) * ratio); // Modifier le canal vert
int b = (int) ((color & 0xFF) * ratio); // Modifier le canal bleu
return (a << 24) | (r << 16) | (g << 8) | b; // Combiner les canaux pour obtenir la couleur assombrie
}
public void changeColorTemporarily(int color) {
setSelectedColor(color);
// Jouer le son correspondant
int indexSon=1;
if (color == Color.RED)
indexSon = 1;
else if (color == Color.GREEN)
indexSon = 2;
else if (color == Color.BLUE)
indexSon = 3;
else if (color == Color.YELLOW)
indexSon = 4;
// TODO: Jouer le son correspondant
invalidate();
handler.postDelayed(new Runnable() {
@Override
public void run() {
setSelectedColor(0);
invalidate();
}
}, 300); // 1 seconde
}
}
Dans cette classe, on utilise la méthode onDraw
pour dessiner les boutons de couleur.
Cette méthode est appelée chaque fois que la vue doit être redessinée.
On utilise la méthode drawSimonButtons
pour dessiner les boutons de couleur.
Cette méthode utilise la classe Paint
pour dessiner les boutons de couleur.
On utilise la méthode drawRect
pour dessiner un rectangle de couleur.
On utilise la méthode setColor
pour définir la couleur du bouton.
Pour simuler le jeu, on utilise la méthode changeColorTemporarily
pour changer temporairement la couleur du bouton.
Cette méthode utilise la méthode setSelectedColor
pour changer la couleur du bouton.
On utilise la méthode postDelayed
pour changer la couleur du bouton après un certain temps.
On utilise la méthode invalidate
pour redessiner la vue après avoir changé la couleur du bouton.
On utilise la méthode assombrirCouleur
pour assombrir la couleur du bouton.