Por que o RecyclerView não possui onItemClickListener
O RecyclerView
é uma caixa de ferramentas, em contraste com o antigo ListView
, possui menos recursos de criação e mais flexibilidade. Esse onItemClickListener
não é o único recurso que está sendo removido do ListView. Mas tem muitos ouvintes e método para estendê-lo ao seu gosto, é muito mais poderoso nas mãos certas;).
Na minha opinião, o recurso mais complexo removido RecyclerView
é o Fast Scroll . A maioria dos outros recursos pode ser facilmente reimplementada.
Se você quiser saber quais outros recursos interessantes foram RecyclerView
adicionados, leia esta resposta para outra pergunta.
Memória eficiente - solução drop-in para onItemClickListener
Esta solução foi proposta por Hugo Visser , um Android GDE, logo após o RecyclerView
lançamento. Ele disponibilizou uma aula sem licença para você simplesmente inserir seu código e usá-lo.
Ele mostra um pouco da versatilidade introduzida com RecyclerView
o uso de RecyclerView.OnChildAttachStateChangeListener
.
Edit 2019 : versão kotlin por mim, java one, de Hugo Visser, mantida abaixo
Kotlin / Java
Crie um arquivo values/ids.xml
e coloque-o nele:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_click_support" type="id" />
</resources>
adicione o código abaixo à sua fonte
Kotlin
Uso:
recyclerView.onItemClick { recyclerView, position, v ->
// do it
}
(também oferece suporte a um longo clique no item e veja abaixo outro recurso que eu adicionei).
implementação (minha adaptação ao código Java 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) }
}
(veja abaixo você também precisa adicionar um arquivo XML)
Recurso de bônus da versão Kotlin
Às vezes, você não deseja que todos os itens do RecyclerView sejam clicáveis.
Para lidar com isso, apresentei a ItemClickSupportViewHolder
interface que você pode usar ViewHolder
para controlar qual item é clicável.
Exemplo:
class MyViewHolder(view): RecyclerView.ViewHolder(view), ItemClickSupportViewHolder {
override val isClickable: Boolean get() = false
override val isLongClickable: Boolean get() = false
}
Java
Uso:
ItemClickSupport.addTo(mRecyclerView)
.setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
@Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
// do it
}
});
(também suporta clique longo no item)
Implementação (comentários adicionados por mim):
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);
}
}
Como funciona (por que é eficiente)
Essa classe funciona anexando RecyclerView.OnChildAttachStateChangeListener
a ao RecyclerView
. Esse ouvinte é notificado toda vez que um filho é anexado ou desanexado do RecyclerView
. O código usa isso para anexar um ouvinte de toque / clique longo à exibição. Esse ouvinte pede RecyclerView
o RecyclerView.ViewHolder
que contém a posição.
Isso é mais eficiente do que outras soluções, porque evita a criação de vários ouvintes para cada exibição e continua destruindo e criando-os enquanto a RecyclerView
rolagem está rolando.
Você também pode adaptar o código para devolver o suporte, se precisar de mais.
Observação final
Lembre-se de que é completamente bom manipulá-lo no seu adaptador, definindo em cada exibição da sua lista um ouvinte de clique, como outra resposta proposta.
Não é apenas a coisa mais eficiente a ser feita (você cria um novo ouvinte toda vez que reutiliza uma exibição), mas funciona e, na maioria dos casos, não é um problema.
Também é um pouco contra a separação de preocupações, porque não é realmente o trabalho do adaptador delegar eventos de clique.