Pourquoi le RecyclerView n'a pas onItemClickListener
C'est RecyclerView
une boîte à outils, contrairement à l'ancienne, ListView
elle a moins de fonctionnalités et plus de flexibilité. Ce onItemClickListener
n'est pas la seule fonctionnalité supprimée de ListView. Mais il a beaucoup d'auditeurs et de méthode pour l'étendre à votre guise, il est bien plus puissant entre de bonnes mains;).
À mon avis, la fonctionnalité la plus complexe supprimée RecyclerView
est le Fast Scroll . La plupart des autres fonctionnalités peuvent être facilement réimplémentées.
Si vous voulez savoir quelles autres fonctionnalités intéressantes RecyclerView
ajoutées, lisez cette réponse à une autre question.
Efficace en mémoire - solution intégrée pour onItemClickListener
Cette solution a été proposée par Hugo Visser , un Android GDE, juste après RecyclerView
sa sortie. Il a mis à votre disposition une classe sans licence pour simplement déposer votre code et l'utiliser.
Il met en valeur une partie de la polyvalence introduite avec RecyclerView
en faisant usage de RecyclerView.OnChildAttachStateChangeListener
.
Edit 2019 : version kotlin par moi, java one, de Hugo Visser, gardée ci-dessous
Kotlin / Java
Créez un fichier values/ids.xml
et mettez-le dedans:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_click_support" type="id" />
</resources>
puis ajoutez le code ci-dessous à votre source
Kotlin
Usage:
recyclerView.onItemClick { recyclerView, position, v ->
// do it
}
(il prend également en charge les longs clics sur les objets et voir ci-dessous pour une autre fonctionnalité que j'ai ajoutée).
implémentation (mon adaptation au code Java de Hugo Visser):
typealias OnRecyclerViewItemClickListener = (recyclerView: RecyclerView, position: Int, v: View) -> Unit
typealias OnRecyclerViewItemLongClickListener = (recyclerView: RecyclerView, position: Int, v: View) -> Boolean
class ItemClickSupport private constructor(private val recyclerView: RecyclerView) {
private var onItemClickListener: OnRecyclerViewItemClickListener? = null
private var onItemLongClickListener: OnRecyclerViewItemLongClickListener? = null
private val attachListener: RecyclerView.OnChildAttachStateChangeListener = object : RecyclerView.OnChildAttachStateChangeListener {
override fun onChildViewAttachedToWindow(view: View) {
// every time a new child view is attached add click listeners to it
val holder = this@ItemClickSupport.recyclerView.getChildViewHolder(view)
.takeIf { it is ItemClickSupportViewHolder } as? ItemClickSupportViewHolder
if (onItemClickListener != null && holder?.isClickable != false) {
view.setOnClickListener(onClickListener)
}
if (onItemLongClickListener != null && holder?.isLongClickable != false) {
view.setOnLongClickListener(onLongClickListener)
}
}
override fun onChildViewDetachedFromWindow(view: View) {
}
}
init {
// the ID must be declared in XML, used to avoid
// replacing the ItemClickSupport without removing
// the old one from the RecyclerView
this.recyclerView.setTag(R.id.item_click_support, this)
this.recyclerView.addOnChildAttachStateChangeListener(attachListener)
}
companion object {
fun addTo(view: RecyclerView): ItemClickSupport {
// if there's already an ItemClickSupport attached
// to this RecyclerView do not replace it, use it
var support: ItemClickSupport? = view.getTag(R.id.item_click_support) as? ItemClickSupport
if (support == null) {
support = ItemClickSupport(view)
}
return support
}
fun removeFrom(view: RecyclerView): ItemClickSupport? {
val support = view.getTag(R.id.item_click_support) as? ItemClickSupport
support?.detach(view)
return support
}
}
private val onClickListener = View.OnClickListener { v ->
val listener = onItemClickListener ?: return@OnClickListener
// ask the RecyclerView for the viewHolder of this view.
// then use it to get the position for the adapter
val holder = this.recyclerView.getChildViewHolder(v)
listener.invoke(this.recyclerView, holder.adapterPosition, v)
}
private val onLongClickListener = View.OnLongClickListener { v ->
val listener = onItemLongClickListener ?: return@OnLongClickListener false
val holder = this.recyclerView.getChildViewHolder(v)
return@OnLongClickListener listener.invoke(this.recyclerView, holder.adapterPosition, v)
}
private fun detach(view: RecyclerView) {
view.removeOnChildAttachStateChangeListener(attachListener)
view.setTag(R.id.item_click_support, null)
}
fun onItemClick(listener: OnRecyclerViewItemClickListener?): ItemClickSupport {
onItemClickListener = listener
return this
}
fun onItemLongClick(listener: OnRecyclerViewItemLongClickListener?): ItemClickSupport {
onItemLongClickListener = listener
return this
}
}
/** Give click-ability and long-click-ability control to the ViewHolder */
interface ItemClickSupportViewHolder {
val isClickable: Boolean get() = true
val isLongClickable: Boolean get() = true
}
// Extension function
fun RecyclerView.addItemClickSupport(configuration: ItemClickSupport.() -> Unit = {}) = ItemClickSupport.addTo(this).apply(configuration)
fun RecyclerView.removeItemClickSupport() = ItemClickSupport.removeFrom(this)
fun RecyclerView.onItemClick(onClick: OnRecyclerViewItemClickListener) {
addItemClickSupport { onItemClick(onClick) }
}
fun RecyclerView.onItemLongClick(onLongClick: OnRecyclerViewItemLongClickListener) {
addItemClickSupport { onItemLongClick(onLongClick) }
}
(voir ci-dessous, vous devez également ajouter un fichier XML)
Fonction bonus de la version Kotlin
Parfois, vous ne voulez pas que tous les éléments de RecyclerView soient cliquables.
Pour gérer cela, j'ai introduit l' ItemClickSupportViewHolder
interface que vous pouvez utiliser sur votre ViewHolder
pour contrôler quel élément est cliquable.
Exemple:
class MyViewHolder(view): RecyclerView.ViewHolder(view), ItemClickSupportViewHolder {
override val isClickable: Boolean get() = false
override val isLongClickable: Boolean get() = false
}
Java
Usage:
ItemClickSupport.addTo(mRecyclerView)
.setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
@Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
// do it
}
});
(il prend également en charge le clic long sur un élément)
Mise en œuvre (commentaires ajoutés par moi):
public class ItemClickSupport {
private final RecyclerView mRecyclerView;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
// ask the RecyclerView for the viewHolder of this view.
// then use it to get the position for the adapter
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
}
};
private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
return false;
}
};
private RecyclerView.OnChildAttachStateChangeListener mAttachListener
= new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(View view) {
// every time a new child view is attached add click listeners to it
if (mOnItemClickListener != null) {
view.setOnClickListener(mOnClickListener);
}
if (mOnItemLongClickListener != null) {
view.setOnLongClickListener(mOnLongClickListener);
}
}
@Override
public void onChildViewDetachedFromWindow(View view) {
}
};
private ItemClickSupport(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
// the ID must be declared in XML, used to avoid
// replacing the ItemClickSupport without removing
// the old one from the RecyclerView
mRecyclerView.setTag(R.id.item_click_support, this);
mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
}
public static ItemClickSupport addTo(RecyclerView view) {
// if there's already an ItemClickSupport attached
// to this RecyclerView do not replace it, use it
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support == null) {
support = new ItemClickSupport(view);
}
return support;
}
public static ItemClickSupport removeFrom(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support != null) {
support.detach(view);
}
return support;
}
public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
return this;
}
public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
return this;
}
private void detach(RecyclerView view) {
view.removeOnChildAttachStateChangeListener(mAttachListener);
view.setTag(R.id.item_click_support, null);
}
public interface OnItemClickListener {
void onItemClicked(RecyclerView recyclerView, int position, View v);
}
public interface OnItemLongClickListener {
boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
}
}
Comment ça marche (pourquoi c'est efficace)
Cette classe fonctionne en attachant un RecyclerView.OnChildAttachStateChangeListener
au RecyclerView
. Cet écouteur est averti chaque fois qu'un enfant est attaché ou détaché du RecyclerView
. Le code l'utilise pour ajouter un écouteur tap / long click à la vue. Cet auditeur demande le RecyclerView
pour RecyclerView.ViewHolder
qui contient la position.
C'est plus efficace que les autres solutions car cela évite de créer plusieurs écouteurs pour chaque vue et continue de les détruire et de les créer pendant le RecyclerView
défilement.
Vous pouvez également adapter le code pour vous rendre le support lui-même si vous en avez besoin de plus.
Remarque finale
Gardez à l'esprit qu'il est COMPLÈTEMENT bien de le gérer dans votre adaptateur en définissant sur chaque vue de votre liste un écouteur de clic, comme les autres réponses proposées.
Ce n'est tout simplement pas la chose la plus efficace à faire (vous créez un nouvel écouteur chaque fois que vous réutilisez une vue) mais cela fonctionne et dans la plupart des cas, ce n'est pas un problème.
C'est également un peu contre la séparation des préoccupations car ce n'est pas vraiment le travail de l'adaptateur de déléguer les événements de clic.