ทำไม RecyclerView จึงไม่มี onItemClickListener
RecyclerView
เป็นกล่องเครื่องมือในทางตรงกันข้ามของเดิมListView
ก็มีการสร้างน้อยกว่าในคุณสมบัติและความยืดหยุ่นมากขึ้น onItemClickListener
ไม่ได้เป็นคุณลักษณะเฉพาะถูกลบออกจาก ListView แต่มีผู้ฟังจำนวนมากและวิธีการที่จะขยายไปยังความชอบของคุณมันมีพลังมากขึ้นในมือขวา;)
ในความคิดของคุณลักษณะที่ซับซ้อนมากที่สุดในการลบออกRecyclerView
เป็นจานด่วนเลื่อน คุณสมบัติอื่น ๆ ส่วนใหญ่สามารถนำมาใช้ใหม่ได้อย่างง่ายดาย
ถ้าคุณต้องการที่จะรู้ว่าสิ่งดีๆอื่น ๆ คุณสมบัติRecyclerView
เพิ่มอ่านนี้คำตอบของคำถามอื่น
หน่วยความจำมีประสิทธิภาพ - โซลูชันดร็อปอินสำหรับ onItemClickListener
โซลูชันนี้ได้รับการเสนอโดยHugo Visserซึ่งเป็น Android GDE หลังจากRecyclerView
ได้รับการปล่อยตัว เขาสร้างคลาสที่ไม่มีลิขสิทธิ์ให้คุณเพียงวางรหัสและใช้งาน
มันแสดงบางส่วนของความเก่งกาจที่นำมาด้วยโดยการใช้RecyclerView
RecyclerView.OnChildAttachStateChangeListener
แก้ไข 2019 : รุ่น kotlin โดยฉันหนึ่ง java จาก Hugo Visser ด้านล่าง
Kotlin / Java
สร้างไฟล์values/ids.xml
และวางไว้ในมัน:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_click_support" type="id" />
</resources>
จากนั้นเพิ่มรหัสด้านล่างลงในแหล่งที่มาของคุณ
Kotlin
การใช้งาน:
recyclerView.onItemClick { recyclerView, position, v ->
// do it
}
(นอกจากนี้ยังรองรับการคลิกไอเท็มแบบยาวและดูด้านล่างสำหรับคุณลักษณะอื่นที่ฉันได้เพิ่มไว้)
การใช้งาน (การปรับตัวของฉันกับรหัส 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) }
}
(ดูด้านล่างคุณต้องเพิ่มไฟล์ XML)
คุณสมบัติโบนัสของ Kotlin version
บางครั้งคุณไม่ต้องการให้รายการทั้งหมดของ RecyclerView สามารถคลิกได้
เพื่อจัดการเรื่องนี้ฉันได้แนะนำItemClickSupportViewHolder
อินเทอร์เฟซที่คุณสามารถใช้ในViewHolder
การควบคุมรายการที่สามารถคลิกได้
ตัวอย่าง:
class MyViewHolder(view): RecyclerView.ViewHolder(view), ItemClickSupportViewHolder {
override val isClickable: Boolean get() = false
override val isLongClickable: Boolean get() = false
}
ชวา
การใช้งาน:
ItemClickSupport.addTo(mRecyclerView)
.setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
@Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
// do it
}
});
(นอกจากนี้ยังสนับสนุนการคลิกรายการยาว)
การใช้งาน (ความคิดเห็นที่เพิ่มโดยฉัน):
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);
}
}
มันทำงานอย่างไร (ทำไมมันถึงมีประสิทธิภาพ)
ชั้นนี้ทำงานโดยการแนบไปRecyclerView.OnChildAttachStateChangeListener
ฟังนี้จะได้รับแจ้งทุกครั้งที่เด็กถูกแนบมาหรือออกจากRecyclerView
RecyclerView
รหัสนี้ใช้เพื่อต่อท้ายฟังแตะ / ยาวคลิกเพื่อดู ผู้ฟังนั้นถามถึงRecyclerView
สิ่งRecyclerView.ViewHolder
ที่บรรจุตำแหน่ง
นี่เป็นวิธีที่มีประสิทธิภาพมากกว่าแล้วเพราะมันหลีกเลี่ยงการสร้างผู้ฟังหลายคนสำหรับแต่ละมุมมองและทำลายและสร้างมันต่อไปในขณะที่RecyclerView
เลื่อนอยู่
คุณสามารถปรับรหัสเพื่อให้ผู้ถือกลับคืนได้เองหากคุณต้องการมากกว่านี้
คำพูดสุดท้าย
โปรดทราบว่ามันเป็นเรื่องปกติที่จะจัดการกับมันในอะแดปเตอร์ของคุณโดยการตั้งค่าในแต่ละมุมมองของรายการของคุณให้เป็น listener การคลิกเช่นเดียวกับคำตอบอื่น ๆ ที่เสนอ
ไม่ใช่สิ่งที่มีประสิทธิภาพที่สุดในการทำ (คุณสร้างผู้ฟังใหม่ทุกครั้งที่คุณใช้มุมมองใหม่) แต่ก็ใช้งานได้และในกรณีส่วนใหญ่มันไม่ใช่ปัญหา
นอกจากนี้ยังเป็นเรื่องเล็กน้อยที่จะแยกแยะความกังวลเพราะงานของอะแดปเตอร์ไม่ใช่ตัวแทนการคลิกเหตุการณ์