À la découverte du RecyclerView
Salut à tous, c’est Nicolas, développeur Android au sein de Webwag depuis le mois d’avril 2016 ! Aujourd’hui, je vais vous présenter un widget très utilisé dans le monde des développeurs Android : le RecyclerView.
Le RecyclerView est un widget disponible depuis l’API 21, Android Lollipop, qui nous permet d’afficher des données sous forme de liste. Le principal intérêt du RecyclerView est de pouvoir gérer l’affichage d’une large base de données, tout en conservant uniquement en mémoire les éléments affichés à l’écran. C’est une ListView plus flexible et plus simple d’implémentation.
Cas pratique sur une application qui liste les clubs de Ligue 1 :
Nous allons maintenant nous pencher sur le fonctionnement et l’implémentation du RecyclerView sur une application qui a pour but d’afficher les clubs de Ligue 1 de la manière suivante :
Imaginons que nous avons à notre disposition une base de données qui contient les informations suivantes sur les vingt clubs :
- son nom
- un lien vers son logo
- son année de fondation
- son site web
public class Club { public String name; public String logoUrl; public String year; public String websiteUrl; }
Nous allons désormais voir comment fonctionne le RecyclerView pour ordonner et afficher ces informations.
Fonctionnement & implémentation
Le RecyclerView nécessite l’utilisation d’un LayoutManager, ainsi que d’un Adapter qui va gérer un ensemble de ViewHolder.
LayoutManager
Le LayoutManager permet au RecyclerView de savoir comment afficher les éléments de la liste. Il y a trois implémentations de base :
- le LinearLayoutManager : il permet un affichage unidirectionnel, soit vertical, soit horizontal.
- le GridLayoutManager : comme son nom l’indique, le GridLayoutManager permet d’afficher les éléments sous forme de grille.
- le StaggeredGridLayoutManager : permet d’afficher sous forme de grille des éléments qui ne font pas forcément la même taille.
Dans notre exemple, le RecyclerView est donc géré par un LinearLayoutManager vertical.
Adapter & ViewHolder
L’Adapter va gérer l’affichage des éléments de la liste. Il doit implémenter les méthodes suivantes :
onCreateViewHolder()
: crée un ViewHolder. Le ViewHolder contient les éléments qui composent un élément de la liste. Ici, il possèdera deux TextView (un pour le nom de l’équipe, l’autre pour l’année de fondation) et une ImageView (pour afficher le logo).@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_club, parent, false); return new ViewHolder(itemView); } public class ViewHolder extends RecyclerView.ViewHolder { ImageView logo; TextView name; TextView year; public ViewHolder(View view) { super(view); logo = (ImageView)view.findViewById(R.id.club_logo); name = (TextView )view.findViewById(R.id.club_name); year = (TextView )view.findViewById(R.id.club_year); } }
onBindViewHolder()
: permet de lier les éléments de la ViewHolder à leurs valeurs dans la base de données. Le club correspondant est récupéré grâce à la position de la ViewHolder dans l’Adapter. Nous allons ajouter un OnClickListener sur l’élément, qui permettra d’ouvrir le site web du club lors d’un click de l’utilisateur.public List<Club> data; @Override public void onBindViewHolder(final ViewHolder holder, int position) { final Club club = data.get(position); holder.itemView.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { // Ouvre le site web du club dans le navigateur par défaut de l'utilisateur Utils.openBrowser(v.getContext(), club.websiteUrl); } } ); holder.name.setText(club.name); holder.year.setText(club.year); holder.logo.setImageDrawable(null); // Charge le logo du club dans l'ImageView Glide .with(holder.logo.getContext()) .load(club.logoUrl) .into(holder.logo); }
Il faut également avoir créer le layout nécessaire à l’affichage d’un élément, utilisé dans le
onCreateViewHolder()
(R.layout.item_club
) :<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="60dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:paddingTop="10dp" android:paddingBottom="10dp"> <ImageView android:id="@+id/club_logo" android:layout_width="40dp" android:layout_height="40dp" android:layout_marginStart="20dp" /> <TextView android:id="@+id/club_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toEndOf="@id/club_logo" android:layout_marginStart="20dp" android:textColor="@android:color/black" android:textSize="16sp" android:textStyle="bold" tools:text="Marseille"/> <TextView android:id="@+id/club_year" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toEndOf="@id/club_logo" android:layout_marginStart="20dp" android:layout_alignParentBottom="true" android:textColor="@android:color/black" android:textSize="14sp" tools:text="1903"/> </RelativeLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:layout_alignParentBottom="true" android:background="#9F9F9F"/> </RelativeLayout>
getItemCount()
: renvoie le nombre d’éléments total à afficher@Override public int getItemCount() { return data.size(); }
L’avantage du RecyclerView est de créer autant de ViewHolder que d’éléments affichés à l’écran. Ainsi, la méthode onCreateViewHolder()
ne sera appelée qu’à l’initialisation, là où la méthode onBindViewHolder
sera appelée à chaque fois qu’un nouvel élément doit être affiché.
Il ne reste plus qu’à déclarer le RecyclerView dans le layout de l’activité ou du fragment souhaité, puis à le configurer :
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recycler); RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(new Adapter()); }
L’utilisation d’un RecyclerView se résume donc en cinq étapes :
- Déclarer le RecyclerView dans l’activité ou le fragment
- Créer le layout utilisé pour afficher un élément de la liste
- Créer le ViewHolder qui va initialiser l’élément en fonction des données
- Créer l’Adapter qui va gérer les ViewHolder
- Attacher l’Adapter et le LayoutManager au RecyclerView
C’est la fin de cette introduction au RecyclerView, on se retrouve bientôt pour un prochain article sur le blog de Webwag !
À noter :
- EasyRecyclerView (https://github.com/Jude95/EasyRecyclerView) est une bibliothèque qui encapsule un RecyclerView avec un PullToRefresh (lors d’un swipe tout en haut de la liste, permet de rafraîchir la liste. Utile dans une liste d’articles par exemple) et un LoadMore (quand on arrive en bas de la liste, permet de charger des articles plus anciens). Vous pouvez retrouver de nombreuses bibliothèques en rapport avec les RecyclerView sur Android Arsenal (https://android-arsenal.com/tag/199)
- L’API 25 d’Android a introduit le DividerItemDecoration. Il permet de définir un
divider
(vue qui se situe entre les éléments, ici la fine barre grise) pour un RecyclerView. https://developer.android.com/reference/android/support/v7/widget/DividerItemDecoration.html - Dans la méthode de l’Adapter
onCreateViewHolder()
, un des arguments est leviewType
. Il n’est pas utilisé ici, mais peut être intéressant si jamais plusieurs layouts différents doivent être utilisés au sein du même Adapter. Pour modifier leviewType
, il faut redéclarer la méthodegetItemViewType()
:private static final int TYPE_CLUB_L1 = 0; private static final int TYPE_CLUB_L2 = 1; @Override public int getItemViewType(int position) { if(data.get(position).isL1()) { return TYPE_CLUB_L1; } return TYPE_CLUB_L2; }
Il faut également modifier la méthode
onCreateViewHolder()
en conséquence :@Override public ClubHolder onCreateViewHolder(ViewGroup parent, int viewType) { int layoutId; if(viewType == TYPE_CLUB_L1) { layoutId = R.layout.item_club_l1; } else { layoutId = R.layout.item_club_l2; } View itemView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); return new ClubHolder(itemView); }