为什么 RecyclerView 没有 onItemClickListener()?

新手上路,请多包涵

我正在探索 RecyclerView 我很惊讶地发现 RecyclerView 没有 onItemClickListener()

我有两个问题。

主要问题

我想知道为什么谷歌删除了 onItemClickListener()

是否存在性能问题或其他问题?

次要问题

我通过在我的 RecyclerView.Adapter --- 中写入 onClick 解决了我的问题:

 public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {

    public TextView txtViewTitle;
    public ImageView imgViewIcon;

    public ViewHolder(View itemLayoutView) {
        super(itemLayoutView);
        txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
        imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
    }

    @Override
    public void onClick(View v) {

    }
}

这可以吗/有没有更好的方法?

原文由 T_V 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 465
2 个回答

tl;dr 2016 使用 RxJava 和 PublishSubject 为点击公开一个 Observable。

 public class ReactiveAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    String[] mDataset = { "Data", "In", "Adapter" };

    private final PublishSubject<String> onClickSubject = PublishSubject.create();

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        final String element = mDataset[position];

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               onClickSubject.onNext(element);
            }
        });
    }

    public Observable<String> getPositionClicks(){
        return onClickSubject.asObservable();
    }
}

原帖:

自从引入 ListViewonItemClickListener 一直存在问题。当您拥有任何内部元素的点击侦听器时,不会触发回调,但没有通知或没有充分记录(如果有的话),因此存在很多混淆和 SO 问题。

鉴于 RecyclerView 更进一步并且没有行/列的概念,而是任意布置数量的孩子,他们将 onClick 委托给每个孩子,或程序员实现。

想想 Recyclerview 不是 ListView 1:1 替代品,而是作为复杂用例的更灵活的组件。正如你所说,你的解决方案是谷歌对你的期望。现在您有了一个适配器,可以将 onClick 委托给构造函数上传递的接口,这是 ListViewRecyclerview 的正确模式。

 public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {

    public TextView txtViewTitle;
    public ImageView imgViewIcon;
    public IMyViewHolderClicks mListener;

    public ViewHolder(View itemLayoutView, IMyViewHolderClicks listener) {
        super(itemLayoutView);
        mListener = listener;
        txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
        imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
        imgViewIcon.setOnClickListener(this);
        itemLayoutView.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v instanceof ImageView){
           mListener.onTomato((ImageView)v);
        } else {
           mListener.onPotato(v);
        }
    }

    public static interface IMyViewHolderClicks {
        public void onPotato(View caller);
        public void onTomato(ImageView callerImage);
    }

}

然后在你的适配器上

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

   String[] mDataset = { "Data" };

   @Override
   public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_layout, parent, false);

       MyAdapter.ViewHolder vh = new ViewHolder(v, new MyAdapter.ViewHolder.IMyViewHolderClicks() {
           public void onPotato(View caller) { Log.d("VEGETABLES", "Poh-tah-tos"); };
           public void onTomato(ImageView callerImage) { Log.d("VEGETABLES", "To-m8-tohs"); }
        });
        return vh;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // Get element from your dataset at this position
        // Replace the contents of the view with that element
        // Clear the ones that won't be used
        holder.txtViewTitle.setText(mDataset[position]);
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return mDataset.length;
    }
  ...

现在查看最后一段代码: onCreateViewHolder(ViewGroup parent, int viewType) 签名已经建议了不同的视图类型。对于它们中的每一个,您也需要一个不同的视图,随后它们中的每一个都可以有一组不同的点击。或者,您可以创建一个通用的视图持有者,它可以接受任何视图和一个 onClickListener 并相应地应用。或者将一个级别委托给协调器,以便多个片段/活动具有相同的列表和不同的点击行为。同样,所有灵活性都在您身边。

它是一个真正需要的组件,并且与我们对 ListView 的内部实现和改进非常接近。谷歌终于承认了这一点,这很好。

原文由 MLProgrammer-CiM 发布,翻译遵循 CC BY-SA 3.0 许可协议

为什么 RecyclerView 没有 onItemClickListener

RecyclerView 是一个工具箱,与旧版 ListView 相比,它具有更少的内置功能和更多的灵活性。 onItemClickListener 并不是从 ListView 中删除的唯一功能。但是它有很多听众和方法可以根据您的喜好扩展它,在合适的人手中它会更强大;)。

在我看来, RecyclerView 中删除的最复杂的功能是 _快速滚动_。大多数其他功能都可以轻松地重新实现。

如果您想知道添加了哪些其他很酷的功能 RecyclerView ,请阅读另一个问题 答案。

内存高效 - onItemClickListener 的嵌入式解决方案

该解决方案 Android GDE Hugo Visser 在发布 RecyclerView 之后提出。他为您提供了一个免许可课程,您只需输入您的代码并使用它。

它通过使用 RecyclerView.OnChildAttachStateChangeListener 展示了 RecyclerView 引入的一些多功能性。

编辑 2019 :我的 kotlin 版本,java one,来自 Hugo Visser,保留在下面

科特林 / 爪哇

创建一个文件 values/ids.xml 并将其放入其中:

 <?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="item_click_support" type="id" />
</resources>

然后将下面的代码添加到您的源代码中

科特林

用法:

 recyclerView.onItemClick { recyclerView, position, v ->
    // do it
}

(它还支持长项目点击,请参阅下面我添加的另一个功能)。

实现(我对 Hugo Visser Java 代码的改编):

 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 版本的额外功能

有时您不希望 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 时不断销毁和创建它们。

如果您需要更多,您还可以调整代码以将持有人本身还给您。

最后的评论

请记住,通过在列表的每个视图上设置一个点击侦听器,在您的适配器中处理它是完全可以的,就像其他建议的答案一样。

这不是最有效的做法(每次重用视图时都会创建一个新的侦听器)但它可以工作并且在大多数情况下这不是问题。

这也有点反对关注点分离,因为委托点击事件并不是适配器的真正职责。

原文由 Daniele Segato 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题