IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Le composant graphique « RecyclerView » sous Android

Image non disponible
Android2ee

Ce tutoriel va s'intéresser à un nouveau composant qui est le RecyclerView permettant d'afficher une liste d'articles dans un ensemble de sous-vues.

N'hésitez pas à commenter cet article ! 7 commentaires Donner une note à l´article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Histoire

Pour mémoire , l'affichage de liste d'articles sous Android se faisait via le composant ListView, GridView, etc. Ces composants avaient quelques ralentissements lorsque nous affichions leurs articles (items).  Un nouveau composant est apparu il y a peu, sur la bibliothèque de support v7. Ce composant qui s'appelle RecyclerView, nous permet de gérer ces listes d'articles et d'apporter quelques modifications par rapport à ces derniers composants.

II. RecyclerView

Cette classe garde en gros la même philosophie que les autres composants. C'est-à-dire, un adapter pour gérer la liste d'articles (d'items) ainsi que l'affichage de ses données, puis une liste d'événements pour interagir avec cette liste. L'adapter étant toujours personnalisable.

Quelques nouveautés sont apparues, comme l'écouteur nous permettant d'accéder aux vues nettoyées (RecyclerListener), ainsi que de pouvoir récupérer une vue depuis une position donnée (findChildViewUnder),  l'utilisation d'un LayoutManager(pour gérer les colonnes et lignes), l'utilisation de Décoration, etc.

II-A. Vue d'ensemble

Le RecyclerView est composé de sous-composants permettant de personnaliser celui-ci.

Image non disponible

Nous allons présenter brièvement ses sous-composants.

  • Adapter L'adapter permet de contenir l'ensemble des données à afficher dans le RecyclerView en gérant également ses mises à jour. Nous retrouvons le même principe que les adapters des ListView, GridView , etc.
  • LayoutManager

Les LayoutManager permettent de structurer l'ensemble des sous-vues contenues dans le RecyclerView.

  • ItemAnimator  

Les ItemAnimator nous permettent de personnaliser les animations en fonction de l'état de l'item. Par exemple nous pouvons exécuter une animation lors de la suppression d'un article, lors de son ajout, etc.

  • ItemDecoration

Les ItemDecoration nous permettent de personnaliser les sous-vues et les séparateurs. Par exemple nous pouvons dessiner avant et après l'affichage d'une vue tout en ayant accès à la donnée de celle-ci.  

III. Exemple simple, une liste

Voici un exemple concret sur l'utilisation d'un RecyclerView, dans un premier temps, il nous faudra insérer la bibliothèque v7 du RecyclerView dans votre nouveau projet. (com.android.support:recyclerview-v7). Depuis le 8 décembre 2014, il est préconisé d'utiliser AndroidStudio, il nous faut ainsi rajouter dans notre fichier build.gradle la ligne suivante :

 
Sélectionnez
dependencies {
   compile 'com.android.support:recyclerview-v7'
}

Et si vous êtes toujours sous Eclipse, sachez qu'il vous suffit de faire comme avant : insérer le fichier jar du projet RecyclerView que vous trouverez dans la sdk (extras/android/support/v7/recyclerView) dans le dossier libs de votre projet.

III-A. Le layout

Tout d'abord, créons un layout pour notre vue principale qui contiendra notre RecyclerView. Créons également un bouton pour pouvoir ajouter un nouvel article :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:paddingBottom="@dimen/activity_vertical_margin"
   android:paddingLeft="@dimen/activity_horizontal_margin"
   android:paddingRight="@dimen/activity_horizontal_margin"
   android:paddingTop="@dimen/activity_vertical_margin"
   android:orientation="vertical"
   tools:context="com.android2ee.recyclerview.SimpleActivity" >
   
   <Button android:id="@+id/myButtonSimpleAdd"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Add" />
      
<android.support.v7.widget.RecyclerView
   android:id="@+id/myListSimple"
   android:scrollbars="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>
  
</LinearLayout>

III-B. Le fichier MainActivity

Instancions le RecyclerView dans la méthode onCreate de l'Activity, ou la méthode onActivityCreated des Fragments, (exactement comme avant avec les ListView).

 
Sélectionnez
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.myListSimple);

Puis, créons les articles que nous afficherons dans la liste.

 
Sélectionnez
// fill the list items
List<String> items = new ArrayList<String>();
for (int i = 0; i < 6; i++) {
        // new item
           items.add("test " + i);
}

Nous assignons après l'adapter à ce RecyclerView, prenons comme layout celui fourni par le SDK android.R.layout.simple_list_item_1.

 
Sélectionnez
adapter = new RecyclerViewAdapter(items, android.R.layout.simple_list_item_1);
recyclerView.setAdapter(adapter);

Définissons ensuite notre LayoutManager, ici nous resterons sur un cas classique d'affichage de ligne

 
Sélectionnez
recyclerView.setLayoutManager(new LinearLayoutManager(this));

III-C. Le fichier RecyclerViewAdapter

Nous créons ensuite notre adapter avec les deux nouvelles méthodes (onCreateViewHolderetonBindViewHolder), ainsi que notre nouvelle classe ViewHolder qui, comme toujours, établit un lien entre la vue et ses éléments que nous allons mettre à jour en fonction de l'article qu'elle affiche.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
/**
* 
* @author florian
* RecyclerSimpleViewAdapter provide a simple RecyclerViewAdapter
*
*/
public class RecyclerSimpleViewAdapter extends RecyclerView.Adapter<RecyclerSimpleViewAdapter.ViewHolder> {

  /**
   * List items
  */
  private List<String> items;
  /**
   * the resource id of item Layout
  */
  private int itemLayout;

  /**
   * Constructor RecyclerSimpleViewAdapter
   * @param items : the list items
   * @param itemLayout : the resource id of itemView
   */
  public RecyclerSimpleViewAdapter(List<String> items, int itemLayout) {
    this.items = items;
    this.itemLayout = itemLayout;
  }

  /**
   * Create View Holder by Type
   * @param parent, the view parent
   * @param viewType : the type of View
  */
  @Override 
  public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // get inflater and get view by resource id itemLayout
    View v = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false);
    // return ViewHolder with View
    return new ViewHolder(v);
  }

  /**
   * Get the size of items in adapter
   * @return the size of items in adapter
  */
  @Override 
  public int getItemCount() {
    return items.size();
  }
  /**
   * Bind View Holder with Items 
   * @param holder: the view holder
   * @param position : the current position
  */
  @Override
  public void onBindViewHolder(RecyclerSimpleViewAdapter.ViewHolder holder, int position) {
    // find item by position
    String item = items.get(position);
    // save information in holder, we have one type in this adapter
    holder.primaryText.setText(item);
    holder.itemView.setTag(item);
    if ((position % 2) == 0) {
      holder.itemView.setBackgroundResource(R.color.color1);
    } else {
      holder.itemView.setBackgroundResource(R.color.color2);
    }
  }
  /**
   * 
   * @author florian
   * Class viewHolder
   * Hold an textView
  */
  public static class ViewHolder extends RecyclerView.ViewHolder {
    // TextViex
    public TextView primaryText;
    /**
     * Constructor ViewHolder
     * @param itemView: the itemView
    */
    public ViewHolder(View itemView) {
      super(itemView);
      // link primaryText
      primaryText = (TextView) itemView.findViewById(android.R.id.text1);
    }
  }
}

III-D. Mise à jour

Pour mettre à jour l'affichage, nous gardons le même principe qu'auparavant par l'appel de la méthode asynchrone notifyDataSetChanged. De nouvelles méthodes sont apparues pour optimiser ce rafraîchissement en fonction du type de mise à jour (notifyItemChanged, notifyItemInserted, notifyItemRemoved, etc.).

Par exemple, depuis le code (le fichier Main), nous aurons :

 
Sélectionnez
1.
2.
3.
4.
public void add(ViewModel item, int position) {
   items.add(position, item); // on insère le nouvel objet dans notre       liste d'article lié à l'adapter
   adapter.notifyItemInserted(position); // on notifie à l'adapter ce changement
}

III-E. Résultat

Voici ce que nous obtenons. Dans cet exemple, nous avons rajouté un bouton en haut de la liste :

Image non disponible

IV. Les sous-composants

Prenons un peu plus de temps pour comprendre ces nouveaux sous-composants.

IV-A. L' Adapter

Cet adapter n'est plus basé sur la classe BaseAdapter, mais sur le nouvel adapter RecyclerViewAdapter. Ce nouvel adapter amène une nouvelle gestion des ViewHolder en le forçant à utiliser ceux-ci. Celui -ci possède une liste d'articles (comme avec l' ArrayAdapter) et deux principales méthodes qui remplacent la méthode getView (du BaseAdapter). La principale différence est que les BaseAdapter géraient des vues, là où les RecyclerViewAdapter gèrent les ViewHolder associés à ces vues. Cette différence est purement syntaxique pour nous obliger à utiliser le pattern du ViewHolder, en effet, le ViewHolder et sa vue sont liés par une relation 1-1, ce qui signifie que l'on peut les considérer comme étant un seul et même objet. Ainsi, le RecyclerView possède deux méthodes à surcharger :

onCreateViewHolder(ViewGroup parent, int viewType) : cette méthode nous permet de créer la vue à afficher, et de retourner, associé à ce composant, un objet ViewHolder ;

onBindViewHolder(VIewHolder holder, int position) : cette méthode , nous permet d'afficher les données de l'article (l'item) dans la sous-vue courante.

Le principe du RecyclerViewAdapter est identique au BaseAdapter dans sa gestion des convertView. Ce sont les vues qui ont été recyclées et nous ont été renvoyées pour que nous les mettions à jour. Cela évite de créer une vue par item et de dévaster les performances à cause du passage intempestif du GarabageCollector.

Voici un schéma présentant l'enchaînement de ces fonctions :

Image non disponible

Image non disponible

IV-B. Le LayoutManager

Voici ce que nous pouvons avoir de base comme LayoutManager.

LinearLayoutManager : ce manager permet de créer les sous-vues de manière linéaires( horizontalement ou verticalement) . Cela revient à la classe ListVIew.

Image non disponible

Ce LayoutManager reste très simple et nous propose les deux orientations possibles (VERTICAL et HORIZONTAL), ainsi que la possibilité d'inverser l'ordre de l'affichage.

L'utilisation de ce layout se fera comme ceci :

 
Sélectionnez
1.
2.
3.
RecyclerView recyclerView = …;
LinearLayoutManager manager = new LinearLayoutManager(monContext, LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(manager);

Par défaut, nous avons un constructeur LinearLayoutManager(monContext) qui sera vertical et non inversé.


GridLayoutManager : ce manager permet de créer les sous-vues de manière à afficher une grille comme ce que faisait préalablement la classe GridView.

Image non disponible

Le GridLayoutManager est un layout basé sur le LinearLayoutManager, ce manager qui est plus complexe nous permet de définir en plus le nombre de lignes ou colonnes en fonction de l'orientation choisie. Il est possible de modifier ce nombre par la suite via l'appel à la méthode setSpanCount(spanCount).

Par défaut un article ne prend qu'un espace, mais nous avons la possibilité de changer ce nombre en fonction de la position de l'article via la classe SpanSizeLookup.


Voici un exemple pour utiliser un GridLayout :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
RecyclerView recyclerView = …;
GridLayoutManager manager = new GridLayoutManager (monContext, 2, GridLayoutManager .VERTICAL, false);
manager .setSpanSizeLookup(new SpanSizeLookup() {
   
  @Override
  public int getSpanSize(int arg0) {
    return (arg0 % 3) == 0 ? 2 : 1;
  }
});     
recyclerView.setLayoutManager(manager);

StaggeredGridLayoutManager : ce manager permet de créer des sous-vues dans une grille avec un décalage entre les articles.

Image non disponible

Le StaggeredLayoutManager est un layout simple qui affiche les éléments en fonction d'un nombre de colonnes ou lignes établi. Celui-ci n'a pas besoin de connaître le contexte.

 
Sélectionnez
1.
2.
3.
4.
RecyclerView recyclerView = …;
StaggeredLayoutManager manager = new StaggeredLayoutManager (2, GridLayoutManager .VERTICAL);
recyclerView.setGapStrategy(StaggeredLayoutManager .GAP_HANDLING_NONE);
recyclerView.setLayoutManager(manager);

Custom: nous pouvons également personnaliser nos LayoutManager.  Il existe des bibliothèques sur github comme celle-ci  qui nous propose différentes manières d'afficher nos sous-vues.

https://github.com/lucasr/twoway-view

Image non disponible

IV-C. L' ItemAnimator

Pour gérer les animations des sous-vues,  nous devons utiliser la classe ItemAnimator ou une classe dérivée. Par défaut la classe DefaultItemAnimator est utilisée par le RecyclerView. Cette classe nous fournit un ensemble de méthodes nous permettant d'exécuter les animations. Ces méthodes nous permettent de créer ou interagir avec les animations que nous aurons ajoutées.

animateAdd(RecyclerView.ViewHolder holder) : cette méthode est appelée lors de l'ajout d'un article.

animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) : cette méthode est appelée lors de la modification d'un article.

animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) : cette méthode est appelée lors du déplacement d'un article.

animateRemove(RecyclerView.ViewHolder holder) : cette méthode est appelée lors de la suppression d'un article.

animateEnd(RecyclerView.ViewHolder holder) : cette méthode est appelée lorsqu'une animation d'un article se termine.

endAnimations() :cette méthode est appelée lorsque toutes les animations du RecyclerView sont terminées.

isRunning() : cette méthode nous permet de retourner si une animation est en cours.

runPendingAnimations() : cette méthode est appelée lorsque les animations sont en attente pour être exécutées.

Si nous avons créé une classe personnalisée, c'est-à-dire une classe qui surchargera les classes de base (ItemAnimation ou DefaultItemAnimator), il nous suffira alors d'appeler cette fonction pour qu'elle soit prise en compte par le RecyclerView

 
Sélectionnez
recyclerView.setItemAnimator(MaPropreClasse);

IV-D. L' ItemDecoration

L' ItemDecoration nous permet de pouvoir « spécialiser » les sous-vues, en ayant la possibilité d'interagir avec le canevas de ces sous-vues avant et après leur affichage. Pour résumer, les séparateurs , etc. se feront à partir de ce  sous-composant. Nous retrouvons un ensemble de méthodes.

getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) : cette méthode nous permet de créer un offset (un nouvel espace) pour les articles, ce qui peut être intéressant par exemple pour les séparateurs.

onDraw(Canvas canvas, RecyclerView parent) : cette méthode est appelée avant l'affichage des sous-vues.

onDrawOver(Canvas canvas, RecyclerView parent) : cette méthode est appelée après l'affichage des sous-vues.

Si nous avons créé une classe personnalisée, il nous suffira alors d'appeler cette fonction pour qu'elle soit prise en compte par le RecyclerView.

 
Sélectionnez
recyclerView.addItemAnimator(MaPropreClasse);

Nous pouvez rajouter x ItemDecoration à notre RecyclerView.

V. Personnalisation

Prenons un exemple un peu plus complexe. Supposons que nous voulions personnaliser l'ensemble de notre composant. Comme exemple, partons sur l'affichage d'un Header et un Footer depuis une grille avec comme article l'affichage d'une image et son texte correspondant. Nous rajouterons également une animation sur l'ajout et suppression d'un article, ainsi qu'un séparateur entre les articles.

L'ajout des articles se fera via un bouton, la suppression via un clic sur l'article.

V-A. Le Layout

Le layout affichera seulement la liste, ainsi qu'un bouton permettant d'ajouter un nouvel article :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:paddingBottom="@dimen/activity_vertical_margin"
   android:paddingLeft="@dimen/activity_horizontal_margin"
   android:paddingRight="@dimen/activity_horizontal_margin"
   android:paddingTop="@dimen/activity_vertical_margin"
   android:orientation="vertical"
   tools:context="com.android2ee.recyclerview.ComplexActivity" >
   
   <Button android:id="@+id/myButtonComplexAdd"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Add" />
      
<android.support.v7.widget.RecyclerView
   android:id="@+id/myListComplex"
   android:scrollbars="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>
  
</LinearLayout>

Nous aurons également besoin de créer un layout pour nos articles dû à leur personnalisation, nous aurons alors une image et un texte dans ce layout, appelons-le item.xml :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
<?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="wrap_content"
   android:layout_margin="16dp"
   android:background="#FFFFFF"
   android:orientation="vertical" >

   <ImageView
       android:id="@+id/image"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_centerInParent="true"
       android:scaleType="fitXY" />

   <TextView 
       android:id="@+id/title"
       android:layout_width="match_parent"
       android:layout_height="40dp"
       android:layout_alignParentBottom="true"
       android:background="#99918a8a"
       android:textColor="#FFFFFF"
       android:textSize="12dp"
       android:gravity="center"
       
       />

</RelativeLayout>

V-B. Le modèle des articles

Créons maintenant une classe nous permettant de contenir l'ensemble des informations nécessaires pour un article, appelons-la ImageModel :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
package com.android2ee.recyclerview.adapter.model;

/**
* 
* @author florian
* Class ImageModel
* POJO for ComplexRecyclerView
*/
public class ImageModel {

   /**
   * Title Text
   */
   String mTitle;
   /**
   * Resource Id of image
   */
   int mResId;

   /**
   * Get Res Id
   * @return mResId
   */
   public int getResId() {
      return mResId;
   }

   /**
   * Set Res Id
   * @param resId : the new resource Id
   */
   public void setResId(int resId) {
      mResId = resId;
   }

   /**
   * Get Title
   * @return mTitle
   */
   public String getTitle() {
      return mTitle;
   }

   /**
   * Set Title
   * @param title ; the new title
   */
   public void setTitle(String title) {
      mTitle = title;
   }
}

V-C. Le RecyclerViewAdapter

Maintenant, attaquons-nous à notre adapter, cet adapter devra donc prendre en compte notre classe ImageModel pour traiter les articles ainsi que le layout des articles. Insérons également le Header et Footer depuis l'adapter. Cela aurait pu se faire également depuis les ItemDecoration par le canevas.

Pour gérer les Headers et Footers depuis l'adapter, nous utiliserons les types de vue, de ce fait nous saurons depuis la méthode onCreateViewHolder et onBindViewHolder de quel type de vue nous devons traiter. Pour gérer les types de vue, utilisons la méthode  getItemViewType, qui nous permet de retourner le type de vue en fonction de la position de l'article, soit :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
/**
* Get type of view by position (HEADER, FOOTER, STANDARD)
* @param position : the position of item
*/
@Override
public int getItemViewType(int position) {
  // test if header
  if (position == 0 && useHeader()) {
    return TYPE_HEADER;
  }
  // get the size of item in adapter
  int max = useHeader() ? mItems.size() + 1 : mItems.size();
  // test footer
  if (position == max && useFooter()) {
    return TYPE_FOOTER;
  }
  return TYPE_ADAPTERVIEW;
}

La classe complète :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
/**
* 
* @author florian
* RecyclerComplexViewAdapter provides and adpater custom of RecyclerView.Adapter
* which can display a footer and header on start and end of list
* to add and remove item you must be use function addItem and removeItem provide by this class
* 
*/
public class RecyclerComplexViewAdapter extends RecyclerView.Adapter<RecyclerComplexViewAdapter.ViewHolder> {

  // type Header View
  private static final int TYPE_HEADER = 0;
  // type Footer View
  private static final int TYPE_FOOTER = 1;
  // Type standard
  private static final int TYPE_ADAPTERVIEW = 2;

  // list items
  public List<ImageModel> mItems;
  // Context
  Context mContext;

  // Resource Id of HeaderView
  Integer mViewHeader;
  // Resource Id of FooterView
  Integer mViewFooter;

  /**
   * Constructor RecyclerComplexViewAdapter
   * @param context : the context
   * @param objects : the list of items
  */
  public RecyclerComplexViewAdapter(Context context,List<ImageModel> objects) {
    mContext = context;
    mItems = objects;

    // set null header and footer by default
    mViewHeader = null;
    mViewFooter = null;

  }

  /**
   * Constructor RecyclerComplexViewAdapter
   * @param context : the context
   * @param objects : the list items
   * @param viewHeaderId : the header resource id
   * @param viewFooterId : the footer resource id
  */
  public RecyclerComplexViewAdapter(Context context,List<ImageModel> objects, Integer viewHeaderId, Integer viewFooterId) {
    mContext = context;
    mItems = objects;

    mViewHeader = viewHeaderId;
    mViewFooter = viewFooterId;

  }

  /**
   * 
   * @author florian
   * Class viewHolder
   * Hold an imageView and textView
  */
  static class ViewHolder extends RecyclerView.ViewHolder{
    // The imageView
    public  ImageView mImageView;
    // the TextView
    public  TextView mTextView;
    // the rootView
    public View rootView;

    /**
     * Constructor
     * @param itemView
    */
    public ViewHolder(View itemView) {
      super(itemView);
      // set RootView
      rootView = itemView;
      // link imageView
      mImageView =(ImageView)itemView.findViewById(R.id.image);
      // link TextView
      mTextView =(TextView)itemView.findViewById(R.id.title);
    }
  }

  /**
   * Test if a Header resource is present
   * @return if a resource is present
   */
  private boolean useHeader() {
    return mViewHeader != null;
  }

  /**
   * Test if a Footer resource is present
   * @return if a resource is present
  */
  private boolean useFooter() {
    return mViewFooter != null;
  }

  /**
   * Get the size of items in adapter
   * @return the size of items in adapter
  */
  @Override
  public int getItemCount() {
    // get the size of items
    int count = mItems.size();
    // if header uses add 1
    if (useHeader()) {
      count ++;
    }
    // if footer uses add 1
    if (useFooter()) {
      count++;
    }
    return count;
  }

  /**
   * Bind View Holder with Items 
   * @param holder: the view holder
   * @param position : the current position
  */
  @Override
  public void onBindViewHolder(ViewHolder holder, int position) {
    // test type of holder
    if (holder.getItemViewType() == TYPE_HEADER) {
      // nothing
      Log.i("TAG", "POSITION TYPE_HEADER " + position);
    } else if (holder.getItemViewType() == TYPE_FOOTER) {
      // nothing
      Log.i("TAG", "POSITION TYPE_FOOTER " + position);
    } else {
      // save information in holder of item
      Log.i("TAG", "POSITION" + position);
      // get item  if header user shift the position by 1
      ImageModel item = mItems.get(useHeader() ? position - 1 : position); 
      holder.mImageView.setBackgroundResource(item.getResId());
      holder.mTextView.setText("Position " + position);
    }
  }


  /**
   * Create View Holder by Type
   * @param parent, the view parent
   * @param viewType : the type of View
  */
  @Override
  public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // get inflater
    LayoutInflater inflater =    
    (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
    View convertView;
    // in function of type return the right viewHolder
    if (viewType == TYPE_HEADER  && useHeader()) {
      // Header
      convertView = inflater.inflate(mViewHeader, parent, false);
    } else if (viewType == TYPE_FOOTER && useFooter()) {
      // Footer
      convertView = inflater.inflate(mViewFooter, parent, false);
    } else {
      // Standard
      convertView = inflater.inflate(R.layout.item, parent, false);
    }
    return new ViewHolder(convertView);
  }

  /**
   * Get type of view by position (HEADER, FOOTER, STANDARD)
   * @param position : the position of item
  */
  @Override
  public int getItemViewType(int position) {
    // test if header
    if (position == 0 && useHeader()) {
      return TYPE_HEADER;
    }
    // get the size of item in adapter
    int max = useHeader() ? mItems.size() + 1 : mItems.size();
    // test footer
    if (position == max && useFooter()) {
      return TYPE_FOOTER;
    }
    return TYPE_ADAPTERVIEW;
  }

  /**
   * Remove item in list and notify adapter
   * @param position : the position of item to remove
  */
  public void removeItem(int position) {
    // if header or footer don't move it
    int max = useHeader() ? mItems.size() + 1 : mItems.size();
    if (useHeader() && position == 0) {
      return;
    }
    // if header or footer don't move it
    if (useFooter() && position == max) {
      return;
    }
    // notify the adapter
    notifyItemRemoved(position);
    // remove item, if header used shift position by one
    mItems.remove(useHeader() ? position -1 : position);
  }

  /**
   * Add item in adapter
   * @param item : the new item to add
   * @param position : the position to add item
  */
  public void addItem(ImageModel item, int position) {
    // notify the adapter, shift position by one if header used
    notifyItemInserted(useHeader() ? position + 1: position);
    // add the item
    mItems.add(position, item);
  }
}

V-D. Le LayoutManager

Maintenant que nous avons créé l'adapter, intéressons-nous au LayoutManager. Il nous faut traiter le problème sur l'espace que le Header et le Footer doivent prendre, puisque nous sommes dans une grille, ces deux espaces doivent prendre l'ensemble de la ligne pour s'afficher. Pour ce faire, créons une nouvelle classe qui héritera de GridLayoutManager, et depuis la méthode getSpanSize de la classe SpanSizeLookUp, retournons l'espace nécessaire, soit la ligne pour le Header et le Footer, et l'espace de un pour les articles standards :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
public int getSpanSize(int position) {
  int max = mUseHeader ? mObjects.size() + 1 : mObjects.size();
  // if header
  if (position == 0 && mUseHeader) {
    // return all spanCount
    return mSpanCount;
    // if footer
  } else if (position == max && mUseFooter) {
    // return all spanCount
    return mSpanCount;
  }
  // standard return 1
  return 1;
}

Le classe complète :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
/**
* 
* @author florian
* Class MyGridLayoutManager
* Provides a way to display Header and Footer in a GridLayoutManager, and take all row or column
*
*/
public class MyGridLayoutManager extends GridLayoutManager {

  // useHeader
  private boolean mUseHeader;
  // useFooter
  private boolean mUseFooter;
  // spanCount of the Grid
  private int mSpanCount;
  // list items
  private List<ImageModel> mObjects;

  /**
   * New SpanSizeLookup, to treat Header and Footer size
  */
  public SpanSizeLookup mySpanSizeLookup = new SpanSizeLookup() {

    /**
     * Get Span Size of the position
     * @return the size of item
    */
    @Override
    public int getSpanSize(int position) {
      int max = mUseHeader ? mObjects.size() + 1 : mObjects.size();
      // if header
      if (position == 0 && mUseHeader) {
        // return all spanCount
        return mSpanCount;
        // if footer
      } else if (position == max && mUseFooter) {
        // return all spanCount
        return mSpanCount;
      }
      // standard return 1
      return 1;
    }
  };

  /**
   * Constructor MyGridLayoutManager
   * @param context : the context
   * @param spanCount : the spanCount of Grid
   * @param objects : the list Items
  */
  public MyGridLayoutManager(Context context, int spanCount, List<ImageModel> objects) {
    super(context, spanCount);
    mUseHeader = false;
    mUseFooter = false;
    mSpanCount = spanCount;
    mObjects = objects;
    // set mySpanSizeLookup to treat header and footer
    setSpanSizeLookup(mySpanSizeLookup);
  }

  /**
   * Display Header, if an header has displayed in adapter
   * @param value, true of false
  */
  public void displayHeader(boolean value) {
    mUseHeader = value;
  }

  /**
   * Display Footer, if an footer has displayed in adapter
   * @param value, true of false
  */
  public void displayFooter(boolean value) {
    mUseFooter = value;
  }

}

V-E. L' ItemDecoration

Pour les ItemDecorations, nous allons insérer un séparateur entre les articles. Pour ce faire créons notre propre classe MyDividerItemDecoration, nous récupérerons le divider fourni dans les styles de l'application. Pour créer l'espace de ce divider, cela se fera depuis la méthode getItemOffsets en retournant l'objet outRect , soit :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
/**
* Return the dimension outRect for itemPosition and parent (Offset)
* @param outRect : the new Rect
* @param itemPosition: the position of item
* @param parent : the view parent
* 
*/
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
  // depends orientation
  if (mOrientation == VERTICAL_LIST) {
    // return rect with height of divider
    outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
  } else {
    // return rect with width of divider
    outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
  }
}

La classe complète :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
/**
* 
* @author florian
* Class MyDividerItemDecoration
* Provides an separator between row or column in RecyclerView
*
*/
public class MyDividerItemDecoration extends RecyclerView.ItemDecoration {

  /**
   * Attributes of separator  android.R.attr.listDivider
  */
    private static final int[] ATTRS = new int[]{
      android.R.attr.listDivider
    };

    // static HORIZONTAL_LIST
    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    // static VERTICAL_LIST
    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

    /**
     * The drawable of separator to display
    */
    private Drawable mDivider;

    /**
     * The orientation of recyclerView HORIZONTAL_LIST, VERTICAL_LIST
    */
    private int mOrientation;

    /**
     * Constructor MyDividerItemDecoration
     * @param context : the context
     * @param orientation : the orientation HORIZONTAL_LIST, VERTICAL_LIST
    */
    public MyDividerItemDecoration(Context context, int orientation) {
      // get the attributes style
      final TypedArray a = context.obtainStyledAttributes(ATTRS);
      // get drawable in style
      mDivider = a.getDrawable(0);
      a.recycle();
      // set orientation
      setOrientation(orientation);
    }

    /**
     * Set Orientation
     * @param orientation : the new orientation HORIZONTAL_LIST or VERTICAL_LIST
    */
    public void setOrientation(int orientation) {
      // if not HORIZONTAL_LIST or VERTICAL_LIST , throw an exception
      if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
        throw new IllegalArgumentException("invalid orientation");
      }
      mOrientation = orientation;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent) {
      // call the right draw depends the orientation
      if (mOrientation == VERTICAL_LIST) {
        // vertical
        drawVertical(c, parent);
      } else {
        // horizontal
        drawHorizontal(c, parent);
      }
    }

    /**
     * Draw Vertical, so horizontal separator
     * @param c : the canvas
     * @param parent : the view parent
    */
    public void drawVertical(Canvas c, RecyclerView parent) {
      // get padding of parents (Left and right
      final int left = parent.getPaddingLeft();
      final int right = parent.getWidth() - parent.getPaddingRight();
      // get the count of child
      final int childCount = parent.getChildCount();
      for (int i = 0; i < childCount; i++) {
        final View child = parent.getChildAt(i);
        // create layoutParams
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
             .getLayoutParams();
        // calculate new padding child
        final int top = child.getBottom() + params.bottomMargin;
        final int bottom = top + mDivider.getIntrinsicHeight();
        // set bounds
        mDivider.setBounds(left, top, right, bottom);
        // draw in canvas
        mDivider.draw(c);
      }
    }

    /**
     * Draw Horizontal, so vertical separator
     * @param c : the canvas
     * @param parent : the view parent
    */
    public void drawHorizontal(Canvas c, RecyclerView parent) {
      // get padding of parents (Top and Bottom
      final int top = parent.getPaddingTop();
      final int bottom = parent.getHeight() - parent.getPaddingBottom();

      // get the count of child
      final int childCount = parent.getChildCount();
      for (int i = 0; i < childCount; i++) {
        final View child = parent.getChildAt(i);
        // create layoutParams
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
		.getLayoutParams();
        // calculate new padding child
        final int left = child.getRight() + params.rightMargin;
        final int right = left + mDivider.getIntrinsicHeight();
        // set bounds
        mDivider.setBounds(left, top, right, bottom);
        // draw in canvas
        mDivider.draw(c);
      }
    }

    /**
     * Return the dimension outRect for itemPosition and parent (Offset)
     * @param outRect : the new Rect
     * @param itemPosition: the position of item
     * @param parent : the view parent
     * 
    */
    @Override
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
      // depends orientation
      if (mOrientation == VERTICAL_LIST) {
        // return rect with height of divider
        outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
      } else {
        // return rect with width of divider
        outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
      }
    }
}

Pour personnaliser le divider, il nous suffit depuis le style de notre application de rajouter cette ligne :

 
Sélectionnez
<item name="android:listDivider">@drawable/divider</item>

V-F. L' ItemAnimator

Pour les ItemAnimator nous allons rajouter une animation de Flip sur l'ajout ou suppression des articles. Nous nous baserons sur la classe BaseItemAnimator de ce projet qui nous permettra de simplifier les pré et posttraitements des animations :

https://github.com/wasabeef/recyclerview-animators/blob/master/animators/src/main/java/jp/wasabeef/recyclerview/animators/BaseItemAnimator.java.

Maintenant créons la classe FlipInAnimator qui elle contiendra les animations de flip que nous voulons, sur l'ajout et la suppression des articles. Soit depuis les méthodes animateRemoveImpl et animateAddImpl. La méthode preAnimateAdd nous permettant de prépositionner l'animation avant de l'exécuter.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
/**
* 
* @author florian
* Class FlipInAnimator 
* Provides an animation with flip in top 
* for a RecyclerView
*
*/
public class FlipInAnimator extends BaseItemAnimator {

  @Override
  protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
    // start animate rotationX 0 --> 90
    ViewCompat.animate(holder.itemView)
  	      .rotationX(90)
	      .setDuration(getRemoveDuration())
	      .setListener(new DefaultRemoveVpaListener(holder))
	      .start();
    // Add holder in mRemoveAnimations
    mRemoveAnimations.add(holder);
  }

  @Override
  protected void preAnimateAdd(RecyclerView.ViewHolder holder) {
    // prepare animate 
    ViewCompat.setRotationX(holder.itemView, 90);
  }

  @Override
  protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
    // start animate rotationX 90 -> 0
    ViewCompat.animate(holder.itemView)
	      .rotationX(0)
	      .setDuration(getAddDuration())
	      .setListener(new DefaultAddVpaListener(holder)).start();
    // Add holder in mAddAnimations
    mAddAnimations.add(holder);
  }
}

V-G. Le fichier principal

Il ne nous reste plus qu'à rassembler tous ces sous-composants créés depuis notre RecyclerView, notre Activity ou notre Fragment . Ne pas oublier de créer notre liste d'articles.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
// create the recyclerView
RecyclerView recyclerView =  (RecyclerView) findViewById(R.id.myListComplex);
recyclerView.setHasFixedSize(false);
// add new Decoration, provide the separator between View (row in this case)
recyclerView.addItemDecoration(new MyDividerItemDecoration(this, MyDividerItemDecoration.VERTICAL_LIST));

// fill the list
items = new ArrayList<ImageModel>();
for (int i = 1; i < 29 ; i++) {
  // create a new ImageModel
  ImageModel img = new ImageModel();
  img.setTitle("Image No " + i);
  int drawableResourceId = this.getResources().getIdentifier("image"+String.valueOf(i), "drawable", this.getPackageName());
  img.setResId(drawableResourceId);
  // add in list
  items.add(img);

}

// create the adapter with header and footer
// header represent by R.layout.header and footer by  R.layout.footer
adapter = new RecyclerComplexViewAdapter(ComplexActivity.this, items, R.layout.header,  R.layout.footer);    
// set the adapter
recyclerView.setAdapter(adapter);
// create my LayoutManager Custom
MyGridLayoutManager layoutManager = new MyGridLayoutManager(this, 2, items);
// set parameter of this layoutManager
layoutManager.displayHeader(true);
layoutManager.displayFooter(true);
// set this LayoutManager in RecyclerView
recyclerView.setLayoutManager(layoutManager);
// create and set FlipInAnimator in RecyclerView
recyclerView.setItemAnimator(new FlipInAnimator());

Nous nous sommes basés sur une Activité, voici la classe complète :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
/**
* 
* @author florian
* Class ComplexActivity
* Present a complex RecyclerView 
* Grid with a header and footer
* with an animation
* with a custom adapter
* 
*/
public class ComplexActivity extends ActionBarActivity implements OnItemClickListener {

  /**
   * List of items
  */
  List<ImageModel> items ;
  /**
   * The RecyclerView Adapter
  */
  RecyclerComplexViewAdapter adapter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_complex);

    // create the recyclerView
    RecyclerView recyclerView =  (RecyclerView) findViewById(R.id.myListComplex);
    recyclerView.setHasFixedSize(false);
    // add new Decoration, provide the separator between View (row in this case)
    recyclerView.addItemDecoration(new MyDividerItemDecoration(this, MyDividerItemDecoration.VERTICAL_LIST));
    //  Add the RecyclerItemClickListener, to intercept click on his child
    recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(this, this));

    // fill the list
    items = new ArrayList<ImageModel>();
    for (int i = 1; i < 29 ; i++) {
      // create a new ImageModel
      ImageModel img = new ImageModel();
      img.setTitle("Image No " + i);
      int drawableResourceId = this.getResources().getIdentifier("image"+String.valueOf(i), "drawable", this.getPackageName());
      img.setResId(drawableResourceId);
      // add in list
      items.add(img);

    }

    // create the adapter with header and footer
    // header represent by R.layout.header and footer by  R.layout.footer
    adapter = new RecyclerComplexViewAdapter(ComplexActivity.this, items, R.layout.header,  R.layout.footer);    
    // set the adapter
    recyclerView.setAdapter(adapter);
    // create my LayoutManager Custom
    MyGridLayoutManager layoutManager = new MyGridLayoutManager(this, 2, items);
    // set parameter of this layoutManager
    layoutManager.displayHeader(true);
    layoutManager.displayFooter(true);
    // set this LayoutManager in RecyclerView
    recyclerView.setLayoutManager(layoutManager);
    // create and set FlipInAnimator in RecyclerView
    recyclerView.setItemAnimator(new FlipInAnimator());

    // Link Button Add 
    Button buttonAdd = (Button) findViewById(R.id.myButtonComplexAdd);
    buttonAdd.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View v) {

        // max and min random possible for item (1-28 images)
        int min = 1;
        int max = 28;

        // create a new item random
        Random r = new Random();
        int i = r.nextInt(max - min + 1) + min;
        // create new item ImageModel
        ImageModel img = new ImageModel();
        img.setTitle("Image No " + i);
        // get drawable by name image
        int drawableResourceId = getResources().getIdentifier("image"+String.valueOf(i), "drawable", getPackageName());
        img.setResId(drawableResourceId);
        Log.i(getClass().getCanonicalName(), "Add Image Position" + "image"+String.valueOf(i) + adapter.getItemCount());
        // add in list
        add(img, adapter.mItems.size());
      }
    });

  }

  /**
   * Add new item in list
   * @param item : the new item
   * @param position : the position of new item (insert)
  */
  public void add(ImageModel item, int position) {
    adapter.addItem(item, position);
  }

  /**
   * Remove item in list
   * @param item : the item to remove
  */
  public void remove(ImageModel item) {
    // find position of item in list
    int position = adapter.mItems.indexOf(item);
    // remove item
    adapter.removeItem(position);
  }

  /**
   * Remove item by position in list
   * @param position : the position of item to remove
  */
  public void remove(int position) {
    // remove item
    adapter.removeItem(position);
  }

  /**
   * Intercept Click on Item
   * @view : the view has clicked
   * @position : the position of view in list
  */
  @Override
  public void onItemClick(View view, int position) {
    Log.i(getClass().getCanonicalName(), "Id found : " + adapter.getItemId(position));
    // remove item
    remove(position);
  }
}

V-H. Résultat

Voici ce que nous obtenons :

Image non disponible

VI. Astuces pour les événements

Il manque la gestion majeure des événements sur les sous-vues, pour l'instant, seule l'interface onItemTouchListener est accessible. Pour ne pas perdre du temps à chaque utilisation de ce composant, créons une nouvelle interface qui nous permettra d'accéder directement aux autres événements (click, longclick, etc.). Cette interface sera déduite du traitement de l'événement récupéré depuis les méthodes onInterceptTouhEvent et onTouchEvent de onItemTouchListener.

Un exemple pour le ClickListener :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
  private OnItemClickListener mListener;
  public interface OnItemClickListener {
    public void onItemClick(View view, int position);
  }

  GestureDetector mGestureDetector;
  public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
    mListener = listener;
    mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
      @Override public boolean onSingleTapUp(MotionEvent e) {
        return true;
      }
    });
  }

  @Override public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
    View childView = view.findChildViewUnder(e.getX(), e.getY());
    if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
      mListener.onItemClick(childView, view.getChildPosition(childView));
    }
    return false;
  }

  @Override public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }
}

On pourrait remonter d'autres événements à partir de cette classe ou en créer d'autres !

Pour utiliser cette classe, il nous suffit de rajouter cette classe dans notre RecyclerView comme ceci :

 
Sélectionnez
//  Add the RecyclerItemClickListener, to intercept click on his child
recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(MyContext, MyOnItemClickListener));

VII. Conclusion

Cette nouvelle classe nous apporte des méthodes communes pour gérer toute sorte de listes ou grilles, avec une architecture propre acquise tout le long de l'utilisation des ListView et autres composants durant ces dernières années. (Utilisation des ViewHolder, etc.)

Par contre, il faut bien voir cette classe comme étant commune, donc épurée par rapport aux anciens composants qui étaient utilisés spécifiquement. Par exemple toutes les gestions des propriétés que nous avons pour la ListView ne sont pas implémentées de base (divider, fade, etc.) . Ou tout simplement la multisélection qui n'est pas implémentée de base :).

Cela demande alors de personnaliser cette classe en diverses sous-classes nous permettant de récupérer notre ancien confort sur l'affichage des articles. Dans tous les cas ; nous retrouvons la philosophie de l'utilisation des adapters, ce qui ne devrait pas poser problème à ceux ayant déjà utilisé les anciens composants. Le passage à cette classe devrait se faire en douceur !


Le projet sous github : https://github.com/ffournier/RecyclerView.

VIII. Remerciements Developpez

Mes remerciements à Claude Leloup pour sa relecture orthographique et également à Mickael Baron pour sa relecture technique

N'hésitez pas à commenter cet article ! 7 commentaires Donner une note à l´article (5)

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Feanorin. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.