I. Histoire▲
Pour mémoire , l'affichage de liste d'article 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 composant permettant de personnaliser celui-ci.
Nous allons présenter brièvement ses sous-composants.
- Adapter:
L'adapter permet de pouvoir contenir l'ensemble des données à afficher dans le RecyclerView en gérant également ses mises à jours. 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 pouvoir 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 pouvoir personnaliser les sous vues et les séparateurs. Par exemple nous pouvons dessiner avant et après l'affichage de 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 vous 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 vous faut ainsi rajouter dans votre 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. Rajoutons également un bouton pour pouvoir ajouter un nouvel article:
<
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éer 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éfinir 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 (onCreateViewHolder et onBindViewHolder), ainsi que votre nouvelle classe ViewHolder qui, comme toujours, établit un lien entre la vue et ses éléments que vous allez mettre à jour en fonction de l'article qu'elle affiche.
/**
*
*
@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:
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
d'un
rafraîchissement
}
III-E. Résultat▲
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 vous obliger à utiliser le pattern du ViewHolder, en effet, le ViewHolder et sa vue sont liés par une liaison 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 pouvoir créer la vue à afficher, et de retourner associé à ce composant, un objet ViewHolder.
onBindViewHolder(VIewHolder holder, int position) : Cette méthode , nous permet de pouvoir 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 vous ont été renvoyées pour que vous les mettiez à 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ères 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 cela :
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ères à afficher une grille comme préalablement ce que faisait 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 ligne ou colonne en fonction de l'orientation choisie que nous voulons. 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 vous avez 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 :
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 de 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 colonne ou ligne établie. Celui-ci n'a pas besoin de connaître le contexte.
RecyclerView recyclerView =
…;
StaggeredLayoutManager manager =
new
StaggeredLayoutManager (
2
, GridLayoutManager .VERTICAL);
recyclerView.setGapStrategy
(
StaggeredLayoutManager .GAP_HANDLING_NONE);
recyclerView.setLayoutManager
(
manager);
Custom: Vous pouvez également personnaliser vos LayoutManager. Il existe des bibliothèques sur github comme celle-ci qui vous propose différentes manières d'afficher vos sous vues.
https://github.com/lucasr/twoway-view
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 pouvoir créer ou interagir avec les animations que nous aurons ajoutées.
animateAdd(RecyclerView.ViewHolder holder) : Cette méthode est appelée lors d'un 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 d'une modification d'un article.
animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) : Cette méthode est appelée lors d'un déplacement d'un article.
animateRemove(RecyclerView.ViewHolder holder) : Cette méthode est appelée lors d'une suppression d'un article.
animateEnd(RecyclerView.ViewHolder holder): Cette méthode est appelé lorsqu'une animation se termine d'un article.
endAnimations() : Cette méthode est appelé 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é lorsque les animations sont en attente pour être exécutées.
Si vous avez crée une classe personnalisée, c'est à dire une classe qui surchargera les classes de bases (ItemAnimation ou DefaultItemAnimator), il vous suffira alors d'appeler cette fonction pour quelle 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é de pouvoir interagir avec le canvas de ces sous vues avant et après leurs affichages. Pour résumer les séparateurs , etc.. se feront à partir de ce sous composant. Nous retrouvons un ensemble de méthode.
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é avant l'affichage des sous vues.
onDrawOver(Canvas canvas, RecyclerView parent) : Cette méthode est appelé après l'affichage des sous vues.
Si vous avez crée une classe personnalisée, il vous suffira alors d'appeler cette fonction pour quelle soit prise en compte par le RecyclerView.
recyclerView.addItemAnimator
(
MaPropreClasse);
Vous pouvez rajouter x ItemDecoration à votre RecyclerView.
V. Personnalisation▲
Prenons un exemple un peu plus complexe. Supposons que nous voulons personnaliser l'ensemble de notre composant. Comme exemple partons sur l'affichage d'un Header et un Footer depuis une grille avec comme article un 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 clique sur l'article.
V-A. Le Layout▲
Le layout affichera seulement la liste ainsi que un bouton permettant d'ajouter un nouvel article :
<
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û à leurs personnalisations, nous aurons alors une image et un texte dans ce layout, appelons le item.xml :
<?
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 là ImageModel :
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 canvas.
Pour gérer les Headers et Footers depuis l'adapter nous utiliserons les types de vues, 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 :
/**
*
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 :
/**
*
*
@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ée 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 se 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 :
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 :
/**
*
*
@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 :
/**
*
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 :
/**
*
*
@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 vous suffit depuis le style de votre 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 post traitements 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.
/**
*
*
@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ées depuis notre RecyclerView, votre Activity ou votre Fragment . Ne pas oublier de créer notre liste d'articles.
//
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é sur une Activité, voici la classe complète :
/**
*
*
@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▲
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 n'est accessible. Pour ne pas perdre du temps à chaque utilisation de ce composant, créer 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 :
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 vous suffit de rajouter cette classe dans votre 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 toutes sortes 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é de base (divider, fade, etc..) . Ou tout simplement la multi sé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 devraient pas poser problème à ceux ayant déjà utiliser les anciens composants. Le passage à cette classe devrait se faire en douceur !
Le projet sous github : https://github.com/ffournier/RecyclerView.