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.
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 :
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 :
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).
RecyclerView recyclerView =
(
RecyclerView) findViewById
(
R.id.myListSimple);
Puis, créons les articles que nous afficherons dans la liste.
// 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.
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
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.
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 :
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 :
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 :
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.
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 :
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.
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 :
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.
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.
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.
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
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.
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
<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 :
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.
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.
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 :
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 :
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 :
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 :
// 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