Mengapa RecyclerView tidak memiliki onItemClickListener
Itu RecyclerView
adalah kotak alat, berbeda dari yang lama ListView
ia kurang memiliki fitur dan fleksibilitas lebih. Ini onItemClickListener
bukan satu-satunya fitur yang dihapus dari ListView. Tetapi memiliki banyak pendengar dan metode untuk memperluas sesuai dengan keinginan Anda, itu jauh lebih kuat di tangan kanan;).
Menurut pendapat saya fitur paling kompleks yang dihapus RecyclerView
adalah Fast Scroll . Sebagian besar fitur lainnya dapat dengan mudah diimplementasikan kembali.
Jika Anda ingin tahu fitur keren apa yang RecyclerView
ditambahkan, baca jawaban ini untuk pertanyaan lain.
Memori efisien - solusi jatuhkan untuk onItemClickListener
Solusi ini telah diusulkan oleh Hugo Visser , sebuah GDE Android, tepat setelah RecyclerView
dirilis. Dia membuat kelas bebas lisensi tersedia bagi Anda untuk hanya memasukkan kode Anda dan menggunakannya.
Ini menampilkan beberapa fleksibilitas yang diperkenalkan dengan RecyclerView
memanfaatkan RecyclerView.OnChildAttachStateChangeListener
.
Sunting 2019 : versi kotlin oleh saya, java one, dari Hugo Visser, disimpan di bawah
Kotlin / Jawa
Buat file values/ids.xml
dan letakkan ini di dalamnya:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_click_support" type="id" />
</resources>
kemudian tambahkan kode di bawah ini ke sumber Anda
Kotlin
Pemakaian:
recyclerView.onItemClick { recyclerView, position, v ->
// do it
}
(itu juga mendukung klik item panjang dan lihat di bawah untuk fitur lain yang saya tambahkan).
implementasi (adaptasi saya ke kode 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) }
}
(lihat di bawah ini, Anda juga perlu menambahkan file XML)
Fitur bonus dari versi Kotlin
Terkadang Anda tidak ingin semua item RecyclerView dapat diklik.
Untuk menangani ini, saya telah memperkenalkan ItemClickSupportViewHolder
antarmuka yang dapat Anda gunakan ViewHolder
untuk mengontrol item mana yang dapat diklik.
Contoh:
class MyViewHolder(view): RecyclerView.ViewHolder(view), ItemClickSupportViewHolder {
override val isClickable: Boolean get() = false
override val isLongClickable: Boolean get() = false
}
Jawa
Pemakaian:
ItemClickSupport.addTo(mRecyclerView)
.setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
@Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
// do it
}
});
(itu juga mendukung klik item panjang)
Implementasi (komentar ditambahkan oleh saya):
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);
}
}
Cara kerjanya (mengapa ini efisien)
Kelas ini berfungsi dengan melampirkan a RecyclerView.OnChildAttachStateChangeListener
ke RecyclerView
. Pendengar ini diberitahu setiap kali seorang anak dilampirkan atau dilepaskan dari RecyclerView
. Kode menggunakan ini untuk menambahkan pendengar tap / klik panjang ke tampilan. Pendengar yang meminta RecyclerView
untuk RecyclerView.ViewHolder
yang berisi posisi.
Ini lebih efisien daripada solusi lain karena itu menghindari membuat beberapa pendengar untuk setiap tampilan dan terus menghancurkan dan menciptakan mereka saat RecyclerView
sedang digulir.
Anda juga dapat mengadaptasi kode untuk memberikan kembali dudukan itu sendiri jika Anda membutuhkan lebih banyak.
Komentar terakhir
Perlu diingat bahwa benar-benar baik-baik saja untuk menanganinya di adaptor Anda dengan menetapkan pada setiap tampilan daftar Anda pendengar klik, seperti jawaban lain yang diajukan.
Ini bukan hal yang paling efisien untuk dilakukan (Anda membuat pendengar baru setiap kali Anda menggunakan kembali tampilan) tetapi itu berhasil dan dalam kebanyakan kasus itu bukan masalah.
Ini juga agak menentang pemisahan kekhawatiran karena itu sebenarnya bukan Pekerjaan Adaptor untuk mendelegasikan kejadian klik.