Java 为什么在 RecyclerView.Adapter 的 onBindViewHolder 中添加 OnClickListener 被认为是不好的做法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33845846/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why is adding an OnClickListener inside onBindViewHolder of a RecyclerView.Adapter considered bad practice?
提问by Sujit Yadav
I have the following code for a RecyclerView.Adapter
class and it works fine:
我有一个RecyclerView.Adapter
类的以下代码,它工作正常:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.Viewholder> {
private List<Information> items;
private int itemLayout;
public MyAdapter(List<Information> items, int itemLayout){
this.items = items;
this.itemLayout = itemLayout;
}
@Override
public Viewholder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false);
return new Viewholder(v);
}
@Override
public void onBindViewHolder(Viewholder holder, final int position) {
Information item = items.get(position);
holder.textView1.setText(item.Title);
holder.textView2.setText(item.Date);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(view.getContext(), "Recycle Click" + position, Toast.LENGTH_SHORT).show();
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast.makeText(v.getContext(), "Recycle Click" + position, Toast.LENGTH_SHORT).show();
return true;
}
});
}
@Override
public int getItemCount() {
return items.size();
}
public class Viewholder extends RecyclerView.ViewHolder {
public TextView textView1;
public TextView textView2;
public Viewholder(View itemView) {
super(itemView);
textView1=(TextView) itemView.findViewById(R.id.text1);
textView2 = (TextView) itemView.findViewById(R.id.date_row);
}
}
}
However, I believe it is bad practice to implement the OnClickListener in the onBindViewHolder
method. Why is this bad practice, and what is a better alternative?
但是,我认为在onBindViewHolder
方法中实现 OnClickListener 是不好的做法。为什么这是不好的做法,什么是更好的选择?
采纳答案by AdamMc331
The reason it is better to handle your click logic inside the ViewHolder is because it allows for more explicit click listeners. As expressed in the Commonsware book:
最好在 ViewHolder 中处理您的点击逻辑的原因是因为它允许更明确的点击侦听器。正如 Commonsware 书中所述:
Clickable widgets, like a RatingBar, in a ListView row had long been in conflict with click events on rows themselves. Getting rows that can be clicked, with row contents that can also be clicked, gets a bit tricky at times. With RecyclerView, you are in more explicit control over how this sort of thing gets handled… because you are the one setting up all of the on-click handling logic.
ListView 行中的可点击小部件(如 RatingBar)长期以来一直与行本身的点击事件发生冲突。获取可以点击的行,以及也可以点击的行内容,有时会变得有点棘手。使用 RecyclerView,您可以更明确地控制此类事情的处理方式……因为您是设置所有点击处理逻辑的人。
By using the ViewHolder model you can gain a lot of benefits for click handling in a RecyclerView than previously in the ListView. I wrote about this in a blog post comparing the differences - https://androidessence.com/recyclerview-vs-listview
通过使用 ViewHolder 模型,您可以在 RecyclerView 中获得比以前在 ListView 中处理点击更多的好处。我在一篇比较差异的博客文章中写到了这一点 - https://androidessence.com/recyclerview-vs-listview
As for why it is better in the ViewHolder instead of in onBindViewHolder()
, that is because onBindViewHolder()
is called for each and every item and setting the click listener is an unnecessary option to repeat when you can call it once in your ViewHolder constructor. Then, if your click responds depends on the position of the item clicked, you can simply call getAdapterPosition()
from within the ViewHolder. Hereis another answer I've given that demonstrates how you can use the OnClickListener
from within your ViewHolder class.
至于为什么它在 ViewHolder 而不是 in 更好onBindViewHolder()
,那是因为onBindViewHolder()
为每个项目调用并且设置单击侦听器是不必要的重复选项,当您可以在 ViewHolder 构造函数中调用它一次时。然后,如果您的点击响应取决于所点击项目的位置,您可以简单地getAdapterPosition()
从 ViewHolder 中调用。这是我给出的另一个答案,它演示了如何OnClickListener
在 ViewHolder 类中使用from 。
回答by Brucelet
The onCreateViewHolder()
method will be called the first several times a ViewHolder
is needed of each viewType
. The onBindViewHolder()
method will be called every time a new item scrolls into view, or has its data change. You want to avoid any expensive operations in onBindViewHolder()
because it can slow down your scrolling. This is less of a concern in onCreateViewHolder()
. Thus it's generally better to create things like OnClickListener
s in onCreateViewHolder()
so that they only happen once per ViewHolder
object. You can call getLayoutPosition()
inside the listener in order to get the current position, rather than taking the position
argument provided to onBindViewHolder()
.
该onCreateViewHolder()
方法将在ViewHolder
每个 需要的前几次调用viewType
。onBindViewHolder()
每次新项目滚动到视图中或更改其数据时,都会调用该方法。您希望避免任何昂贵的操作,onBindViewHolder()
因为它会减慢您的滚动速度。这在onCreateViewHolder()
. 因此,通常最好创建像OnClickListener
s inonCreateViewHolder()
这样的东西,以便它们每个ViewHolder
对象只发生一次。您可以getLayoutPosition()
在侦听器内部调用以获取当前位置,而不是使用position
提供给的参数onBindViewHolder()
。
回答by Pavel Kozemirov
The method onBindViewHolder
is called every time when you bind your view with object which just has not been seen. And every time you will add a new listener.
onBindViewHolder
每次将视图与尚未看到的对象绑定时,都会调用该方法。并且每次你都会添加一个新的监听器。
Instead what you should do is, attaching click listener on onCreateViewHolder
相反,您应该做的是,附加点击侦听器 onCreateViewHolder
example :
例子 :
@Override
public Viewholder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false);
final ViewHolder holder = new ViewHolder(v);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "position = " + holder.getAdapterPosition());
}
});
return holder;
}
回答by Daria Kirsanova
Pavel providedgreat code example except one line in the end. You should return created holder. Not the new Viewholder(v).
除了最后一行之外,Pavel提供了很棒的代码示例。您应该返回创建的持有人。不是新的 Viewholder(v)。
@Override
public Viewholder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false);
final ViewHolder holder = new ViewHolder(v);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "position = " + holder.getAdapterPosition());
}
});
return holder;
}
回答by Bink
Per https://developer.android.com/topic/performance/vitals/render, onBindViewHolder
should do its work in "much less than one millisecond" to prevent slow rendering.
根据https://developer.android.com/topic/performance/vitals/render,onBindViewHolder
应该在“远小于一毫秒”内完成其工作,以防止渲染缓慢。
RecyclerView: Bind taking too long
Bind (that is, onBindViewHolder(VH, int)) should be very simple, and take much less than one millisecond for all but the most complex items. It simply should take POJO items from your adapter's internal item data, and call setters on views in the ViewHolder. If RV OnBindView is taking a long time, verify that you're doing minimal work in your bind code.
RecyclerView:绑定时间太长
绑定(即 onBindViewHolder(VH, int))应该非常简单,除了最复杂的项目外,所有项目的时间都远少于一毫秒。它只是应该从适配器的内部项目数据中获取 POJO 项目,并在 ViewHolder 中的视图上调用 setter。如果 RV OnBindView 需要很长时间,请确认您在绑定代码中所做的工作最少。
回答by Gastón Saillén
This is how I implement the clicks of my buttons in my ViewHolder instead of my onBindViewHolder. This example shows how to bind more than one button with an interface, which will not generate more objects while populating rows.
这就是我如何在我的 ViewHolder 而不是我的 onBindViewHolder 中实现我的按钮的点击。这个例子展示了如何将多个按钮与一个界面绑定,这样在填充行时不会生成更多的对象。
The example is in Spanish and in Kotlin, but I'm sure the logic is understandable.
这个例子是西班牙语和Kotlin,但我相信逻辑是可以理解的。
/**
* Created by Gastón Saillén on 26 December 2019
*/
class DondeComprarRecyclerAdapter(val context:Context,itemListener:RecyclerViewClickListener):RecyclerView.Adapter<BaseViewHolder<*>>() {
interface RecyclerViewClickListener {
fun comoLlegarOnClick(v: View?, position: Int)
fun whatsappOnClick(v:View?,position: Int)
}
companion object{
var itemClickListener: RecyclerViewClickListener? = null
}
init {
itemClickListener = itemListener
}
private var adapterDataList = mutableListOf<Institucion>()
fun setData(institucionesList:MutableList<Institucion>){
this.adapterDataList = institucionesList
}
fun getItemAt(position:Int):Institucion = adapterDataList[position]
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
val view = LayoutInflater.from(context)
.inflate(R.layout.dondecomprar_row, parent, false)
return PuntosDeVentaViewHolder(view)
}
override fun getItemCount(): Int {
return if(adapterDataList.size > 0) adapterDataList.size else 0
}
override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
val element = adapterDataList[position]
when(holder){
is PuntosDeVentaViewHolder -> holder.bind(element)
else -> throw IllegalArgumentException()
}
}
inner class PuntosDeVentaViewHolder(itemView: View):BaseViewHolder<Institucion>(itemView),View.OnClickListener{
override fun bind(item: Institucion) {
itemView.txtTitleDondeComprar.text = item.titulo
itemView.txtDireccionDondeComprar.text = item.direccion
itemView.txtHorarioAtencDondeComprar.text = item.horario
itemView.btnComoLlegar.setOnClickListener(this)
itemView.btnWhatsapp.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v!!.id){
R.id.btnComoLlegar -> {
itemClickListener?.comoLlegarOnClick(v, adapterPosition)
}
R.id.btnWhatsapp -> {
itemClickListener?.whatsappOnClick(v,adapterPosition)
}
}
}
}
}
And the BaseViewHolder to implement in each adapter
以及在每个适配器中实现的 BaseViewHolder
/**
* Created by Gastón Saillén on 27 December 2019
*/
abstract class BaseViewHolder<T>(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(item: T)
}
回答by Abdul Wahid
i faced a small problem which i want to share in the answers if someone else also face it. i had image and text to show in Recycleview as Cardview. Thus my code according to recommendations should be as follow.
我遇到了一个小问题,如果其他人也遇到了,我想在答案中分享。我有图像和文本在 Recycleview 中显示为 Cardview。因此,根据建议,我的代码应如下所示。
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.books_item_row, parent, false);
final MyViewHolder holder = new MyViewHolder(itemView);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(), "Recycle Click", Toast.LENGTH_LONG).show();
}
});
return holder;
}
however when i will click the card in recycle view it will not work as the itemview is below the image. Thus i slightly changed the code as follow.
但是,当我在回收视图中单击卡片时,它不起作用,因为 itemview 位于图像下方。因此,我稍微更改了代码如下。
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.books_item_row, parent, false);
final MyViewHolder holder = new MyViewHolder(itemView);
holder.thumbnail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Log.d(TAG, "position = " + holder.getAdapterPosition());
Toast.makeText(getActivity(), "Recycle Click", Toast.LENGTH_LONG).show();
}
});
return holder;
}
i.e instead of itemview now person will have to click on thumbnail or image.
即现在人们必须单击缩略图或图像而不是 itemview。