Les applications Android sont constituées de composants liés par un manifeste. Un projet Android contient essentiellement des fichiers sources .java
et des fichiers de définition XML .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.
Sauf exception, une application Android comporte au moins une classe héritant de Activity
(ou AppCompatActivity
pour des raisons de compatibilité) et peut évidemment en avoir plusieurs.
Chaque activité doit impérativement être déclarée dans le fichier AndroidManifest.xml
dans une balise <activity>
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tv.myapplicationlayout">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Remarque : Comme une seule activité peut être lancée au démarrage d’une application, il faut l’indiquer avec la balise <intent-filter>
. Celle-ci contient deux balises <action>
(MAIN) et <category>
(LAUNCHER). On la nomme activité principale de l’application.
Tous les éléments d’une GUI sont des objets View
et ViewGroup
:
Les objets View
sont généralement appelés des widgets (comme Button
ou TextView
par exemple). Ils représentent quelque chose que l’utilisateur peut voir.
Les objets ViewGroup
sont généralement appelés layout (comme LinearLayout
ou ConstraintLayout
par exemple). Ce sont des conteneurs invisibles qui définissent la structure de mise en page pour des objets View
et d’autres objets ViewGroup
.
Une GUI sera donc une hierarchie d’objets View
et ViewGroup
imbriqués.
La GUI d’une application Android est décrite dans un fichier XML.
Exemple d’un fichier activity_main.xml
lorsque l’on crée une application Android basée sur le modèle Empty Actvity :
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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/texteMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Android Studio fournit un éditeur intégré de GUI :
Remarque : en mode Design, Android Studio fournit deux vues, preview et blueprint, cette dernière étant la plus pratique pour assurer le positionnement des widgets.
Une application Android met en oeuvre une séparation entre la présentation (la GUI) et son comportement (le code qui la contrôle).
Chaque fichier de disposition XML est compilé dans une ressource View
.
Il faut charger cette ressource à partir du code de l’activité (dans onCreate()
). Pour réaliser cela, on appelle setContentView()
en lui passant la référence de la ressource de mise en page sous la forme : R.layout.layout_file_name
:
public class MainActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Remarque : La méthode de rappel (callback) onCreate()
d’une activité est appelée par le framework Android lorsque l’activité est lancée.
En fonction de l’état de l’activité, il est possible de contrôler celle-ci à l’aide des méthodes héritées de la classe Activity
:
Chaque composant possède un ID afin de l’identifier de manière unique dans l’arborescence.
Remaque : Lorsque l’application est compilée, cet ID est référencé sous la forme d’un entier, mais l’ID est généralement attribué dans le fichier XML sous la forme d’une chaîne dans l’attribut id
:
<TextView android:id="@+id/texteMessage" ... />
xml
Il est possible d’interagir avec un composant de la GUI en utilisant son ID :
public class MainActivity extends AppCompatActivity
{
private TextView texteMessage;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
texteMessage = (TextView)findViewById(R.id.texteMessage);
texteMessage.setText("Bonjour le monde !");
}
}
Solution n°1 :
La méthode setOnClickListener()
installe un gestionnaire d’écoute pour l’objet Button
. Cette méthode reçoit en argument une interface de callback OnClickListener()
de classe View
, implémentée par sa méthode onClick()
.
Button boutonShow = (Button)findViewById(R.id.buttonShow);
boutonShow.setOnClickListener(
new View.OnClickListener()
{
public void onClick(View v)
{
Toast toast = Toast.makeText(getApplicationContext(), "onClick()", Toast.LENGTH_SHORT);
toast.show();
}
}
);
Remarque : Ici, on utilise 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
.
Solution n°2 :
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.
public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
private Button boutonShow;
private Button boutonHide;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.exemple_linearlayout);
boutonShow = (Button)findViewById(R.id.buttonShow);
boutonHide = (Button)findViewById(R.id.buttonHide);
boutonShow.setOnClickListener(this);
boutonHide.setOnClickListener(this);
}
@Override
public void onClick(View element)
{
Toast toast;
if (element == boutonShow)
{
toast = Toast.makeText(getApplicationContext(), "boutonShow : onClick()", Toast.LENGTH_SHORT);
toast.show();
}
else if (element.getId() == R.id.buttonHide)
{
toast = Toast.makeText(getApplicationContext(), "boutonHide : onClick()", Toast.LENGTH_SHORT);
toast.show();
}
}
}
Un layout est un système de mise en page dont la structure est prédéfinie.
Android fournit différents layouts :
LinearLayout
, RelativeLayout
et FrameLayout
GridLayout
et TableLayout
ConstraintLayout
(pour remplacer avantageusement RelativeLayout
), CoordinatorLayout
, et CollapsingToolbarLayout
Tous les layouts incluent une largeur et une hauteur (layout_width
et layout_height
) et il est nécessaire de les définir. De nombreux layouts possèdent également des marges et des bordures facultatives.
Même si il est possible de définir la largeur et la hauteur avec des valeurs exactes (des valeurs absolues telles que des pixels), il est déconseillé de le faire. Généralement, on utilisera l’une de ces constantes pour définir la largeur ou la hauteur :
wrap_content
: les dimensions requises par son contenu ;match_parent
: aussi grande que le parent le permet (remplace fill_parent
).match_constraint
: utilisé à la place de match_parent
dans ConstraintLayout
(= 0dp
)Sinon, il est possible d’utiliser des mesures relatives telles que les unités de pixels indépendantes de la densité (dp
).
Cette approche permettra de s’assurer que l’application s’affichera correctement sur une variété de tailles d’écran d’appareils.
Le layout LinerLayout organise les composants sur une ligne suivant une orientation
qui peut être vertical
(en colonne) ou horizontal
(en ligne).
L’attribut android:gravity
permet de définir le placement du contenu dans le composant. Par contre, l’attribut android:layout_gravity
permettra de définir le placement du composant dans le layout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:layout_margin="50dp"
android:padding="25dp"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/buttonShow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left"
android:text="Show" />
<Button
android:id="@+id/buttonHide"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:text="Hide" />
<Button
android:id="@+id/buttonTest"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Test" />
</LinearLayout>
Ce qui donne :
Le ConstraintLayout
reprend le fonctionnement du RelativeLayout
de manière avantageuse :
ConstraintLayout
permet de placer les widgets de façon relative aux autres à l’aide de “contraintes” de positionnement, similaires aux règles du RelativeLayout
(layout_alignTop
, layout_toRightOf
, etc.).
Les attributs de contrainte de base ont la forme : layout_constraintX_toYof
avec X
le widget où l’on place la contrainte et Y
le widget de destination.
L’ensemble des contraintes disponibles :
Exemple :
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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/texteMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Ce qui donne (un placement automatiquement centré ici):
Avec deux boutons :
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="@+id/button3"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="@+id/button2"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</android.support.constraint.ConstraintLayout>
Ce qui donne :
ConstraintLayout
introduit aussi les concepts :
layout_constraintVertical_bias
et/ou layout_constraintHorizontal_bias
(ici 0.2
)match_constraint
(qui fixe le layout_width
et/ou layout_height
à 0dp
) et un rapport dans layout_constraintDimensionRatio
layout_constraintHorizontal_chainStyle
appliqué à l’élément de tête de chaine et qui peut prendre par exemple les valeurs spread
, spread_inside
ou packed
) où les éléments sont liés entre eux ave layout_constraintLeft_toRightOf
et layout_constraintRight_toLeftOf
Ce qui donne avec :
spread
spread_inside
packed
layout_constraintGuide_percent
) et des barrières (barier) que l’on ne peut pas dépasser<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="96dp" />
de définir des min/max sur la largeur et hauteur (par exemple layout_constraintHeight_max
et un % dans layout_constraintHeight_percent
)
de réappliquer des contraintes avec la classe ConstrainSet
Le GridLayout
permet de placer les widgets sur une grille. Les attributs android:rowCount
et android:columnCount
définissent le nombre de lignes et de colonnes.
Chaque composant se positionne dans la grille avec les attributs android:layout_row
pour le numéro de ligne et android:layout_column
pour le numéro de colonne. Il est possible de fusionner des lignes avec android:layout_rowSpan
et des colonnes avec android:layout_columnSpan
.
L’attribut android:layout_weight
permet de définir le « poids » (l’espace) que le composant peut prendre à l’intérieur du layout.
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:padding="10dp"
android:rowCount="3">
<Button
android:id="@+id/case1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
android:layout_margin="5dp"
android:layout_marginStart="5dp"
android:layout_row="0"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:textColor="#FFFFFF"
android:text="ZONE 1"/>
<Button
android:id="@+id/case2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="1"
android:layout_margin="5dp"
android:layout_row="0"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:textColor="#FFFFFF"
android:text="ZONE 2"/>
<Button
android:id="@+id/case3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:layout_margin="5dp"
android:layout_row="0"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:textColor="#FFFFFF"
android:text="ZONE 3"/>
<Button
android:id="@+id/case4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
android:layout_columnSpan="3"
android:layout_margin="5dp"
android:layout_row="1"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:textColor="#FFFFFF"
android:text="ZONE 4"/>
<Button
android:id="@+id/case5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
android:layout_margin="5dp"
android:layout_marginBottom="55dp"
android:layout_row="2"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:textColor="#FFFFFF"
android:text="ZONE 5"/>
<Button
android:id="@+id/case6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="1"
android:layout_margin="5dp"
android:layout_marginBottom="55dp"
android:layout_row="2"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:textColor="#FFFFFF"
android:text="ZONE 6"/>
<Button
android:id="@+id/case7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:layout_margin="5dp"
android:layout_marginBottom="55dp"
android:layout_row="2"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:textColor="#FFFFFF"
android:text="ZONE 7"/>
</GridLayout>
Ce qui donne :
Une boîte de dialogue est une fenêtre (qui ne remplit pas l’écran) qui invite l’utilisateur à prendre une décision ou à entrer des informations supplémentaires. La classe Dialog est la classe de base des dialogues mais on utilisera une AlertDialog qui est une de ses sous-classes.
Le principe de base est le suivant :
AlertDialog.Builder boiteSuppression = new AlertDialog.Builder(this);
boiteSuppression.setMessage("Vous êtes sur le point de supprimer définitivement un élément.");
boiteSuppression.setPositiveButton("Continuer", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
// ici l'action de suppression
...
}
});
boiteSuppression.setNegativeButton("Annuler", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
}
});
boiteSuppression.show();
On obtient quelque chose comme ceci :
Il est possible d’associer une vue à base de layout :
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/texteChoixNumeroTable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:text="Numéro de la table TTPA ?"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<NumberPicker
android:id="@+id/numeroTable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/texteChoixNumeroTable" />
</android.support.constraint.ConstraintLayout>
Il suffit maintenant de l’associer avec setView()
:
AlertDialog.Builder choixNumeroTable = new AlertDialog.Builder(this);
LayoutInflater inflater = this.getLayoutInflater();
View vue = inflater.inflate(R.layout.choix_numero_table, null);
choixNumeroTable.setView(vue);
npNumeroTable = (NumberPicker) vue.findViewById(R.id.numeroTable);
npNumeroTable.setMaxValue(9);
npNumeroTable.setMinValue(1);
// numeroTable n'a pas encore été choisi ?
if(numeroTable == 0)
npNumeroTable.setValue(1); // valeur par défaut
else
npNumeroTable.setValue(numeroTable); // valeur précedemment choisie
npNumeroTable.setWrapSelectorWheel(false);
choixNumeroTable.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
numeroTable = npNumeroTable.getValue();
}
});
choixNumeroTable.setNegativeButton("Annuler", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
AlertDialog choixNumeroTableDialog = choixNumeroTable.create();
choixNumeroTableDialog.show();
Il est possible d’ajuster la taille de la boîte de dialogue (ici à 80%) juste après l’appel à show()
:
Rect displayRectangle = new Rect();
Window window = this.getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(displayRectangle);
choixNumeroTableDialog.getWindow().setLayout((int)(displayRectangle.width() * 0.8f), choixNumeroTableDialog.getWindow().getAttributes().height);
//choixNumeroTableDialog.getWindow().setLayout((int)(displayRectangle.width() * 0.8f), (int)(displayRectangle.height() * 0.8f));
En pratique, une application est souvent constituée de plusieurs pages parmi lesquelles l’utilisateur peut naviguer selon son gré. Et sous Android, une nouvelle page est … une nouvelle activité ! On va donc ajouter au projet une nouvelle activité.
Pour cela :
1 . On crée un nouveau fichier ressource de type layout. (Facultatif*)
2 . On crée une nouvelle activité Empty (*le fichier ressource layout aurait pu être créé automatiquement) et on la nomme par exemple MyActivity.
3 . Dans la classe de la nouvelle activité, on appelle setContentView()
pour associer le layout à l’activité.
La nouvelle activité a été ajoutée au fichier AndroidManifest.xml
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tv.myapplicationlayout">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MyActivity"></activity>
</application>
</manifest>
Pour afficher la nouvelle activité, on a besoin d’un objet de type 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, … Le lancement effectif de l’intention est réalisé par l’appel à startActivity()
.
Intent intent = new Intent(MainActivity.this, MyActivity.class);
startActivity(intent);
Remarque : La touche retour de l’appareil permet de revenir sur l’activité principale.
Pour retourner d’une activité, il suffit d’appeler la méthode finish()
.
Il est aussi possible de récupérer un code de retour de l’activité en appelant startActivityForResult()
au lancement :
startActivityForResult(intent, 0); // un code peut être passé au lancement (ici 0)
L’activité doit fixer le code de retour avec setResult()
puis appeler finish()
:
setResult(1);
finish();
Le code retourné peut être récupéré avec la méthode callback onActivityResult()
:
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
Log.v("onActivityResult()", "resultCode : " + resultCode);
}
L’Intent
utilisé peut servir à “passer” des données à l’activité lancée :
Intent intent = new Intent(MainActivity.this, MyActivity.class);
String srtTempMin = "TempMin";
double tempMin = 10.5;
intent.putExtra(srtTempMin, tempMin);
startActivity(intent);
Les données sont récupérées :
public class MyActivity extends AppCompatActivity
{
private double tempMin;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.myactivity);
Intent intent = getIntent();
tempMin = intent.getDoubleExtra("TempMin", 23);
...
}
}
Il est possible de “passer” un objet :
Intent intent = new Intent(MainActivity.this, MyActivity.class);
MaClasse unObjet = new MaClasse();
intent.putExtra("unObjet", (Serializable)unObjet);
startActivity(intent);
Il faut que la classe utilisée implémente Serializable
(ou Parcelable
) :
public class Salle implements Serializable
{
// ...
}
L’objet est récupéré comme ceci :
public class MyActivity extends AppCompatActivity
{
MaClasse unObjet;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.myactivity);
Intent intent = getIntent();
unObjet = (MaClasse)intent.getSerializableExtra("unObjet");
...
}
}
Evidemment, cela fonctionne aussi dans l’autre sens. Dans ce cas, les données “retournées” seront récupérées via la méthode callback onActivityResult()
.
Il est aussi possible de concevoir une activité de façon modulaire en utilisant les fragments. Un fragment (un peu comme une “sous-activité”) représente un comportement ou une partie de l’interface utilisateur dans un FragmentActivity. On peut combiner plusieurs fragments dans une seule activité et réutiliser un fragment dans plusieurs activités. Un fragment a son propre cycle de vie, reçoit ses propres événements d’entrée …
Tutoriel : mathias-seguy.developpez.com/tutoriels/android/comprendre-fragments/
a . Intégrer un Spinner
dans le layout :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="50dp"
android:padding="25dp"
android:gravity="center"
android:orientation="vertical">
<Spinner
android:id="@+id/listeNoms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"/>
</LinearLayout>
ArrayAdapter
) qui se comporte comme un intermédiaire entre les données et la vue :public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
Spinner listeNoms; // la vue
List<String> noms; // les données
ArrayAdapter<String> adapter; // l'adaptateur
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.exemple_linearlayout);
listeNoms = (Spinner)findViewById(R.id.listeNoms);
// Des données à placer dans la liste déroulante
noms = new ArrayList<String>();
noms.add("Toto");
noms.add("Titi");
noms.add("Tata");
// On associe les données à l'adaptateur
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, noms);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// On associe l'adaptateur à la vue
listeNoms.setAdapter(adapter);
// On installe le gestionnaire d’écoute lors de la sélection d'une entrée de la liste
listeNoms.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
@Override
public void onItemSelected(AdapterView<?> arg0, View arg1, int position, long id)
{
Log.v("listeNoms", "position : " + position + " - " + " nom : " + noms.get(position));
}
@Override
public void onNothingSelected(AdapterView<?> arg0)
{
// TODO Auto-generated method stub
}
});
}
}
Remarque : Ici, on utilise un layout prédifini par Android : R.layout.simple_spinner_item
. Evidemment, il est possible de personnaliser son layout.
Liste des layouts prédéfinis : R.layout
Un ListView
est un ViewGroup
qui affiche des éléments selon une liste. C’est un vue très utilisée dans les applications Android (par exemple pour faire une liste de Contacts ou de News …). Un ListView
est composé de ListItem
.
a . Intégrer un ListView
dans le layout :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="50dp"
android:padding="25dp"
android:gravity="center"
android:orientation="vertical">
<ListView
android:id="@+id/listeNoms"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
ArrayAdapter
) qui se comporte comme un intermédiaire entre les données et la vue :public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
ListView listeNoms; // la vue
List<String> noms; // les données
ArrayAdapter<String> adapter; // l'adaptateur
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.exemple_linearlayout);
listeNoms = (ListView)findViewById(R.id.listeNoms);
// Des données à placer dans la liste déroulante
noms = new ArrayList<String>();
noms.add("Toto");
noms.add("Titi");
noms.add("Tata");
// On associe les données à l'adaptateur
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, noms);
// On associe l'adaptateur à la vue
listeNoms.setAdapter(adapter);
// On installe le gestionnaire d’écoute pour le clic
listeNoms.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> a, View v, int position, long id)
{
Log.v("listeNoms", "position : " + position + " - " + " nom : " + noms.get(position));
}
});
}
}
Remarque : Ici, on utilise un layout prédifini par Android : R.layout.simple_list_item_1
. Il existe d’autres layouts prédéfinis comme : R.layout.simple_list_item_checked
ou simple_list_item_multiple_choice
. Evidemment, il est possible de personnaliser son layout.
Liste des layouts prédéfinis : R.layout
A titre d’exemple, voici le layout prédéfini R.layout.simple_list_item_1
:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:minHeight="?android:attr/listPreferredItemHeightSmall" />
Il est souvent nécessaire de sauvegarder des informations propres à l’application (comme le nom du dernier utilisateur connecté). Pour cela, il existe une solution simple (autre que des fichiers ou d’une base de données SQLite) : SharedPreferences.
Les SharedPreferences
offre un système de persistance de données (clé/valeur) pour l’application.
public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
ListView listeNoms; // la vue
List<String> noms; // les données
ArrayAdapter<String> adapter; // l'adaptateur
public static final String PREFERENCES = "preferences";
public static final String PREFERENCES_NOM = "Nom";
SharedPreferences sharedPreferences;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.exemple_linearlayout);
// On récupère le SharedPreferences à partir du contexte
sharedPreferences = getBaseContext().getSharedPreferences(PREFERENCES, MODE_PRIVATE);
// On récupère un élément si il existe
if (sharedPreferences.contains(PREFERENCES_NOM))
{
// (voir aussi getBoolean(), getFloat(), getInt() ...)
String nom = sharedPreferences.getString(PREFERENCES_NOM, null); // null ou une valeur par défaut
Toast.makeText(getApplicationContext(), "Dernier nom sélectionné : " + nom, Toast.LENGTH_SHORT).show();
}
listeNoms = (ListView)findViewById(R.id.listeNoms);
// Des données à placer dans la liste déroulante
noms = new ArrayList<String>();
noms.add("Toto");
noms.add("Titi");
noms.add("Tata");
// On associe les données à l'adaptateur
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, noms);
// On associe l'adaptateur à la vue
listeNoms.setAdapter(adapter);
// On installe le gestionnaire d’écoute pour le clic
listeNoms.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> a, View v, int position, long id)
{
Log.v("listeNoms", "position : " + position + " - " + " nom : " + noms.get(position));
Toast.makeText(getApplicationContext(), "Nom sélectionné : " + noms.get(position), Toast.LENGTH_SHORT).show();
// On sauvegarde un élément (voir aussi putBoolean(), putFloat(), putInt() ...)
sharedPreferences.edit().putString(PREFERENCES_NOM, noms.get(position)).apply(); // ou .commit()
}
});
}
}
Cet exemple montre l’intégration d’un écran de démarrage réalisée à partir d’une animation.
Créer un nouveau répertoire nommé anim
dans le répertoire res
avec un clic droit. Refaire un clic droit sur anim
et créer un nouveau fichier de ressources d’animation nommé fade_in.xml
:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="1000"
android:fromAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="1.0" />
</set>
Créer une activité vide à l’aide d’Android Studio nommée Splash
:
public class Splash extends AppCompatActivity
{
private Animation animation;
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
imageView = (ImageView)findViewById(R.id.imageView);
animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.fade_in);
animation.setAnimationListener(new Animation.AnimationListener()
{
@Override
public void onAnimationStart(Animation animation)
{
}
@Override
public void onAnimationEnd(Animation animation)
{
// A la fin de l'animation, on lance l'activité principale
Intent intent = new Intent(Splash.this, MainActivity.class);
startActivity(intent);
}
@Override
public void onAnimationRepeat(Animation animation)
{
}
});
imageView.startAnimation(animation);
}
}
Ajouter une image (un logo par exemple) dans le layout activity_splash.xml
:
<?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=".Splash">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/imageView"
android:src="@drawable/logo"
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 le fichier AndroidManifest.xml
, on paramètre l’activité Splash
comme la seule activité à être lancée au démarrage (cf. balise <intent-filter>
comprenant <action>
(MAIN) et <category>
(LAUNCHER)) :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplicationjson">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity">
</activity>
</application>
</manifest>
Le widget RecyclerView est une version plus avancée de ListView
. Le RecyclerView
est notamment adapté pour des éléments basés sur de grands ensembles de données ou des données qui changent fréquemment.
Dans l’exemple, on affiche une liste de skieurs participant à la coupe du monde de ski alpin 2020 en utilisant : RecyclerView
, CardView
et la bibliothèque Glide
.
Lire : RecyclerView, CardView et Glide [PDF]
© Thierry Vaira <tvaira@free.fr>