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 notre exemple, nous allons afficher une liste de skieurs participant à la coupe du monde de ski alpin 2020.
Un skieur est caractérisé par un nom, un prénom, une nationalité, son rang au classement général et un nombre de points obtenu pendant la saison :
public class Skieur
{
private String nom;
private String prenom;
private String nationalite;
private int rang;
private int points;
public Skieur(String nom, String prenom, String nationalite, int rang, int points)
{
this.nom = nom;
this.prenom = prenom;
this.nationalite = nationalite;
this.rang = rang;
this.points = points;
}
public String getNom()
{
return nom;
}
...
}
Pour pouvoir utiliser RecyclerView
, il faut modifier le fichier app/build.gradle
:
dependencies {
...
implementation 'com.android.support:recyclerview-v7:28.0.0'
...
}
Le RecyclerView
est l’objet principal qu’il faudra ajouter au layout permettant d’afficher notre liste de skieurs :
<?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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listeSkieurs"
android:layout_width="match_parent"
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" />
</androidx.constraintlayout.widget.ConstraintLayout>
Plusieurs autres objets sont indispensables au fonctionnement :
un Adapter
(RecyclerView.Adapter) qui permet de faire le lien entre les données et les vues affichées dans le RecyclerView
.
un LayoutManager
(RecyclerView.LayoutManager) qui permet de positionner les vues d’élément dans le RecyclerView
. Il est possible d’utiliser un des gestionnaires standards (comme LinearLayoutManager
ou GridLayoutManager
) ou implémenter le sien.
un ViewHolder
(RecyclerView.ViewHolder) qui permet de représenter une vue d’élément dans le RecyclerView
.
L’activité aura donc besoin de trois objets :
public class MainActivity extends AppCompatActivity
{
private RecyclerView recyclerView; // la vue
private RecyclerView.Adapter adapter; // l'adaptateur
private RecyclerView.LayoutManager layoutManager; // le gesdtionnaire de mise en page
...
}
Le RecyclerView
se remplit des vues fournies par le LayoutManager
. Les vues sont représentées par des objets ViewHolder
. Ces objets sont des instances (chargées d’afficher un seul élément avec une vue) d’une classe qu’il faut définir en étendant RecyclerView.ViewHolder
. Ils sont gérés par un Adapter
qu’il faut aussi définir en étendant RecyclerView.Adapter
.
On instancie nos trois objets dans la méthode onCreate()
de l’activité :
public class MainActivity extends AppCompatActivity
{
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
private RecyclerView.LayoutManager layoutManager;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView)findViewById(R.id.listeSkieurs);
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(this);
// ou pour un affichage en grille sur 2 colonnes par exemple :
//layoutManager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(layoutManager);
List<Skieur> skieurs = recupererDonnees();
adapter = new SkieurAdapter(skieurs);
recyclerView.setAdapter(adapter);
}
private List<Skieur> recupererDonnees()
{
// Pour l'exemple :
List<Skieur> skieurs = Arrays.asList(
new Skieur("PINTURAULT", "Alexis", "FRA", 1, 1148),
new Skieur( "KILDE", "Aleksander Aamodt", "NOR", 2, 1122),
new Skieur( "KRISTOFFERSEN", "Henrik", "NOR", 3, 1041),
new Skieur( "MAYER", "Matthias", "AUT", 4, 816),
new Skieur( "KRIECHMAYR", "Vincent", "AUT", 5, 776),
new Skieur( "FEUZ", "Beat","SUI", 6, 742),
new Skieur( "CAVIEZEL", "Mauro", "SUI", 7, 633),
new Skieur( "JANSRUD", "Kjetil", "NOR", 8, 625),
new Skieur( "MEILLARD", "Loic", "SUI", 9, 579),
new Skieur( "DRESSEN", "Thomas", "GER", 10, 570),
new Skieur( "PARIS", "Dominik", "ITA", 11, 556),
new Skieur( "NOEL", "Clement", "FRA", 12, 550),
new Skieur( "YULE", "Daniel", "SUI", 13, 495),
new Skieur( "ZUBCIC", "Filip", "CRO", 14, 475),
new Skieur( "MUFFAT-JEANDET", "Victor", "FRA", 15, 465)
);
return skieurs;
}
}
Il faut maintenant créer le layout skieur.xml
pour afficher chaque élément dans le RecyclerView
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_margin="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="0dp">
<TextView
android:id="@+id/rang"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<TextView
android:id="@+id/prenom"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"/>
<TextView
android:id="@+id/nom"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"/>
</LinearLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="0dp">
<TextView
android:id="@+id/nationalite"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<TextView
android:id="@+id/points"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"/>
</LinearLayout>
</LinearLayout>
Il faut maintenant définir le ViewHolder
en héritant de RecyclerView.ViewHolder qui affichera les données de l’élément (un skieur) dans ce layout.
public class SkieurViewHolder extends RecyclerView.ViewHolder
{
private final TextView nom;
private final TextView prenom;
private final TextView nationalite;
private final TextView points;
private final TextView rang;
private Skieur skieur;
public SkieurViewHolder(final View itemView)
{
super(itemView);
nom = ((TextView)itemView.findViewById(R.id.nom));
prenom = ((TextView)itemView.findViewById(R.id.prenom));
nationalite = ((TextView)itemView.findViewById(R.id.nationalite));
points = ((TextView)itemView.findViewById(R.id.points));
rang = ((TextView)itemView.findViewById(R.id.rang));
}
public void afficher(Skieur skieur)
{
this.skieur = skieur;
nom.setText(skieur.getNom());
prenom.setText(skieur.getPrenom());
nationalite.setText(skieur.getNationalite());
points.setText(skieur.getPoints() + " points");
rang.setText(skieur.getRang());
}
}
Remarque : dans SkieurViewHolder()
, il est possible d’installer un gestionnaire pour gérer un clic sur un élément affiché (Lire aussi).
...
itemView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view) {
new AlertDialog.Builder(itemView.getContext())
.setTitle("Skieur")
.setMessage(skieur.toString())
.show();
}
});
Il ne reste plus qu’à définir l’Adapter
en héritant de RecyclerView.Adapter
. Pour cela, il faut redéfinir les méthodes :
onCreateViewHolder()
qui crée les ViewHolder
en fonction des besoinsonBindViewHolder()
qui met à jour l’affichage de la vue d’un élément (en fonction de sa position dans la liste)getItemCount()
qui retourne le nombre d’élémentspublic class SkieurAdapter extends RecyclerView.Adapter<SkieurViewHolder>
{
private List<Skieur> skieurs = null;
public SkieurAdapter(List<Skieur> skieurs)
{
if(skieurs != null)
{
this.skieurs = skieurs;
}
}
@NonNull
@Override
public SkieurViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
{
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.skieur, parent, false);
return new SkieurViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SkieurViewHolder holder, int position)
{
Skieur skieur = skieurs.get(position);
holder.afficher(skieur);
}
@Override
public int getItemCount()
{
if(skieurs != null)
return skieurs.size();
return 0;
}
}
Evidemment il est possible de personnaliser l’affichage des éléments à partir du layout skieur.xml
. Par exemple, avec un affichage basé sur CardView :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/card_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp">
...
</androidx.cardview.widget.CardView>
</LinearLayout>
Pour utiliser le widget CardView
, il faut modifier le fichier app/build.gradle
:
dependencies {
...
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
...
}
Pull To Refresh est une fonctionnalité très utilisée par les applications Android pour actualiser un contenu.
Pour réaliser cela, on va utiliser un SwipeRefreshLayout.
Il faut tout d’abord modifier le fichier app/build.gradle
:
dependencies {
...
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
...
}
Puis le layout qui contient le RecyclerView
:
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
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/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listeSkieurs"
android:layout_width="match_parent"
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" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
Ensuite pour gérer le Pull To Refresh, on doit ajouter un OnRefreshListener()
dans lequel on appelera notifyDataSetChanged()' de l'
Adapter` pour rafraîchir les données.
On va modifier notre exemple pour générer un nombre aléatoire de skieurs pour notre liste et intégrer le Pull To Refresh :
public class MainActivity extends AppCompatActivity
{
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
private RecyclerView.LayoutManager layoutManager;
private List<Skieur> skieurs = new ArrayList<>();
private SwipeRefreshLayout swipeRefreshLayout;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
{
@Override
public void onRefresh()
{
recupererDonnees();
}
});
recyclerView = (RecyclerView)findViewById(R.id.listeSkieurs);
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter = new SkieurAdapter(skieurs);
recyclerView.setAdapter(adapter);
recupererDonnees();
}
private void recupererDonnees()
{
final int nb = new Random().nextInt((15 - 10) + 1) + 10;
List<Skieur> tous = Arrays.asList(
new Skieur("PINTURAULT", "Alexis", "FRA", 1, 1148),
new Skieur( "KILDE", "Aleksander Aamodt", "NOR", 2, 1122),
new Skieur( "KRISTOFFERSEN", "Henrik", "NOR", 3, 1041),
new Skieur( "MAYER", "Matthias", "AUT", 4, 816),
new Skieur( "KRIECHMAYR", "Vincent", "AUT", 5, 776),
new Skieur( "FEUZ", "Beat","SUI", 6, 742),
new Skieur( "CAVIEZEL", "Mauro", "SUI", 7, 633),
new Skieur( "JANSRUD", "Kjetil", "NOR", 8, 625),
new Skieur( "MEILLARD", "Loic", "SUI", 9, 579),
new Skieur( "DRESSEN", "Thomas", "GER", 10, 570),
new Skieur( "PARIS", "Dominik", "ITA", 11, 556),
new Skieur( "NOEL", "Clement", "FRA", 12, 550),
new Skieur( "YULE", "Daniel", "SUI", 13, 495),
new Skieur( "ZUBCIC", "Filip", "CRO", 14, 475),
new Skieur( "MUFFAT-JEANDET", "Victor", "FRA", 15, 465)
);
skieurs.clear();
for(int i =0; i < nb; i++)
{
skieurs.add(tous.get(i));
}
rafraichir(skieurs);
}
private void rafraichir(List<Skieur> skieurs)
{
swipeRefreshLayout.setRefreshing(false);
adapter.notifyDataSetChanged();
}
}
On va terminer notre exemple en insérant dans la vue le drapeau de chaque coureur. Pour cela, on va utiliser la bibliothèque Glide. Glide prend en charge la récupération (via Internet), le décodage et l’affichage d’images.
Pour utiliser Glide
, il faut modifier le fichier app/build.gradle
:
dependencies {
...
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
...
}
On ajoute ensuite un ImageView
dans le layout skieur.xml
.
On récupère une instance de Glide
que l’on passe en paramètre de l’Adapter
:
adapter = new SkieurAdapter(skieurs, Glide.with(this));
recyclerView.setAdapter(adapter);
On modifie le ViewHolder
afin d’assurer l’affichage de l’image du drapeau :
public class SkieurViewHolder extends RecyclerView.ViewHolder
{
...
private final ImageView drapeau;
private Skieur skieur;
public SkieurViewHolder(final View itemView)
{
...
drapeau = ((ImageView)itemView.findViewById(R.id.drapeau));
...
}
public void afficher(Skieur skieur, RequestManager glide)
{
...
glide.load("http://tvaira.free.fr/flags/" + skieur.getNationalite().toLowerCase() + ".png").apply(RequestOptions.circleCropTransform()).into(drapeau); // en forme de cercle !
}
}
On termine en modifiant l’Adapter
afin de gérer l’instance Glide
:
public class SkieurAdapter extends RecyclerView.Adapter<SkieurViewHolder>
{
private List<Skieur> skieurs = null;
private RequestManager glide;
public SkieurAdapter(List<Skieur> skieurs, RequestManager glide)
{
if(skieurs != null)
{
this.skieurs = skieurs;
}
this.glide = glide;
}
...
@Override
public void onBindViewHolder(@NonNull SkieurViewHolder holder, int position)
{
Skieur skieur = skieurs.get(position);
holder.afficher(skieur, this.glide);
}
...
}
On peut personnaliser le CardView
en modifiant la couleur de fond et en arrondissant les coins comme ceci :
<androidx.cardview.widget.CardView
android:id="@+id/card_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
app:cardBackgroundColor="#FFD6DBDA"
app:cardCornerRadius="5dp">
...
</androidx.cardview.widget.CardView>
© Thierry Vaira <tvaira@free.fr>