Tại sao RecyclerView không có onItemClickListener
Đây RecyclerView
là một hộp công cụ, trái ngược với cái cũ, ListView
nó có ít tính năng xây dựng hơn và linh hoạt hơn. Đây onItemClickListener
không phải là tính năng duy nhất bị xóa khỏi ListView. Nhưng nó có rất nhiều người nghe và phương pháp để mở rộng nó theo ý thích của bạn, nó mạnh hơn rất nhiều trong tay phải;).
Theo tôi, tính năng phức tạp nhất bị loại bỏ RecyclerView
là Fast Scroll . Hầu hết các tính năng khác có thể dễ dàng thực hiện lại.
Nếu bạn muốn biết những tính năng thú vị khác RecyclerView
được thêm vào, hãy đọc câu trả lời này cho câu hỏi khác.
Bộ nhớ hiệu quả - giải pháp thả vào cho onItemClickListener
Giải pháp này đã được đề xuất bởi Hugo Visser , một GDE Android, ngay sau khi RecyclerView
được phát hành. Anh ấy đã tạo một lớp miễn phí có sẵn để bạn chỉ cần nhập mã của bạn và sử dụng nó.
Nó giới thiệu một số tính linh hoạt được giới thiệu bằng RecyclerView
cách sử dụng RecyclerView.OnChildAttachStateChangeListener
.
Chỉnh sửa 2019 : phiên bản kotlin của tôi, java một, từ Hugo Visser, được giữ bên dưới
Kotlin / Java
Tạo một tập tin values/ids.xml
và đặt nó trong đó:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_click_support" type="id" />
</resources>
sau đó thêm mã dưới đây vào nguồn của bạn
Kotlin
Sử dụng:
recyclerView.onItemClick { recyclerView, position, v ->
// do it
}
(nó cũng hỗ trợ nhấp vào mục dài và xem bên dưới để biết một tính năng khác mà tôi đã thêm).
triển khai (sự thích ứng của tôi với mã Java của 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) }
}
(xem bên dưới bạn cũng cần thêm một tệp XML)
Tính năng thưởng của phiên bản Kotlin
Đôi khi bạn không muốn tất cả các mục của RecyclerView có thể nhấp được.
Để xử lý việc này, tôi đã giới thiệu ItemClickSupportViewHolder
giao diện mà bạn có thể sử dụng ViewHolder
để kiểm soát mục nào có thể nhấp được.
Thí dụ:
class MyViewHolder(view): RecyclerView.ViewHolder(view), ItemClickSupportViewHolder {
override val isClickable: Boolean get() = false
override val isLongClickable: Boolean get() = false
}
Java
Sử dụng:
ItemClickSupport.addTo(mRecyclerView)
.setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
@Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
// do it
}
});
(nó cũng hỗ trợ nhấp vào mục dài)
Thực hiện (ý kiến được thêm bởi tôi):
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);
}
}
Cách thức hoạt động (tại sao nó hiệu quả)
Lớp này hoạt động bằng cách gắn một RecyclerView.OnChildAttachStateChangeListener
đến RecyclerView
. Người nghe này được thông báo mỗi khi một đứa trẻ được gắn hoặc tách ra khỏi RecyclerView
. Mã sử dụng điều này để nối một người nghe nhấn / nhấp dài vào chế độ xem. Người nghe đó hỏi RecyclerView
cái RecyclerView.ViewHolder
nào chứa vị trí.
Đây là giải pháp hiệu quả hơn các giải pháp khác vì nó tránh tạo nhiều người nghe cho mỗi chế độ xem và tiếp tục phá hủy và tạo chúng trong khi RecyclerView
đang được cuộn.
Bạn cũng có thể điều chỉnh mã để trả lại cho chủ sở hữu nếu bạn cần thêm.
Nhận xét cuối cùng
Hãy nhớ rằng HOÀN TOÀN tốt để xử lý nó trong bộ điều hợp của bạn bằng cách đặt trên mỗi chế độ xem danh sách của bạn một trình nghe nhấp chuột, giống như câu trả lời khác được đề xuất.
Đó không phải là điều hiệu quả nhất để làm (bạn tạo một trình nghe mới mỗi khi bạn sử dụng lại một khung nhìn) nhưng nó hoạt động và trong hầu hết các trường hợp, đó không phải là vấn đề.
Nó cũng là một chút chống lại sự phân tách mối quan tâm vì nó không thực sự là Công việc của Bộ điều hợp để ủy thác các sự kiện nhấp chuột.