为啥在 RecyclerView.Adapter 的 onBindViewHolder 中添加 OnClickListener 被认为是不好的做法?
Posted
技术标签:
【中文标题】为啥在 RecyclerView.Adapter 的 onBindViewHolder 中添加 OnClickListener 被认为是不好的做法?【英文标题】:Why is adding an OnClickListener inside onBindViewHolder of a RecyclerView.Adapter considered bad practice?为什么在 RecyclerView.Adapter 的 onBindViewHolder 中添加 OnClickListener 被认为是不好的做法? 【发布时间】:2016-02-24 01:43:42 【问题描述】:我有以下代码用于 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);
但是,我认为在 onBindViewHolder
方法中实现 OnClickListener 是一种不好的做法。为什么这是不好的做法,有什么更好的选择?
【问题讨论】:
【参考方案1】:在 ViewHolder 中处理点击逻辑更好的原因是它允许更明确的点击侦听器。正如 Commonsware 书中所述:
ListView 行中的可点击小部件(如 RatingBar)长期以来一直与行本身的点击事件发生冲突。获取可以单击的行以及也可以单击的行内容有时会有点棘手。使用 RecyclerView,您可以更明确地控制此类事情的处理方式……因为您是设置所有点击处理逻辑的人。
通过使用 ViewHolder 模型,与以前在 ListView 中相比,在 RecyclerView 中进行点击处理可以获得很多好处。我在一篇比较差异的博文中写到了这一点 - https://androidessence.com/recyclerview-vs-listview
至于为什么在 ViewHolder 中而不是在 onBindViewHolder()
中更好,那是因为 onBindViewHolder()
为每个项目调用,并且设置点击侦听器是一个不必要的选项,当你可以调用它一次时重复你的 ViewHolder 构造函数。然后,如果您的点击响应取决于所点击项目的位置,您可以简单地从 ViewHolder 中调用 getAdapterPosition()
。 Here 是我给出的另一个答案,它演示了如何在 ViewHolder 类中使用 OnClickListener
。
【讨论】:
为了避免不必要的点击监听设置了!但是我们可以像 Brucelet 建议的那样在 onCreateViewHolder() 中实现它吗(见下面的答案)。 @SujitYadav 我想它会产生相同的效果,因为onCreateViewHolder()
只被调用一次(每个 ViewHolder)所以无论你在 ViewHolder 构造函数中还是在 onCreateViewHolder()
中实现它都取决于你个人喜好。我已经养成了把它放在 VH 中的习惯,但是你应该做你认为最易读的,并且会帮助你将来理解的东西。出于性能原因(如 brucelet 建议),请避免使用 onBindViewHolder()
。
@Sujit @McAdam 我更喜欢在onCreateViewHolder()
而不是ViewHolder
构造函数中这样做,这样我就可以使我的ViewHolder
类static
并且不需要传递对适配器插入ViewHolder
。但这最终主要是一种风格选择,因为onCreateViewHolder()
和new ViewHolder()
之间应该是一一对应的。
您不需要在viewholder中传递对适配器的引用吗?您可以从 ViewHolder 内部调用 getAdapterPosition()
。请参阅我链接到的答案。除非我误解了你的意思?
@FirstOne 感谢您的通知!不久前我重写了博客。我已经更新了链接。 :)【参考方案2】:
每次当你将你的视图绑定到刚刚还没有看到的对象时,都会调用 onBindViewHolder
方法。并且每次你都会添加一个新的监听器。
您应该做的是在onCreateViewHolder
上附加点击侦听器
示例:
@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;
【讨论】:
是 getAdapterPosition() 的最佳使用方式,如果我将特定行的位置和对象发送到 Activity 以执行 CRUD 操作。当我使用 getLayoutPosition() 时,它仍然有效! 很好的答案。顺便提一下,getAdapterPosition() 只能放在 onClick() 方法里面,不能放在listner外面,否则位置会是-1。那是因为当 onCreateViewHolder() 被调用时,viewholder 还没有被附加到视图上;当单击视图时,视图必须已附加到视图,因此它可以工作。此外,getAdapterPosition() 将是 deprecated【参考方案3】:onCreateViewHolder()
方法将被调用前几次,每个viewType
都需要ViewHolder
。 onBindViewHolder()
方法将在每次新项目滚动到视图中或更改其数据时调用。您希望避免在onBindViewHolder()
中进行任何昂贵的操作,因为它会减慢您的滚动速度。在onCreateViewHolder()
中,这不是一个问题。因此,通常最好在onCreateViewHolder()
中创建像OnClickListener
s 这样的东西,这样它们每个ViewHolder
对象只发生一次。您可以在侦听器内部调用getLayoutPosition()
以获取当前位置,而不是将position
提供给onBindViewHolder()
的参数。
【讨论】:
【参考方案4】:Pavel provided 很棒的代码示例,除了最后一行。您应该返回已创建的持有人。不是新的 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;
【讨论】:
【参考方案5】:根据https://developer.android.com/topic/performance/vitals/render,onBindViewHolder
应该在“不到一毫秒”的时间内完成其工作,以防止渲染缓慢。
RecyclerView:绑定耗时过长
Bind(即onBindViewHolder(VH, int))应该很简单,而且 除了最复杂的以外,所有时间都不到一毫秒 项目。它只是应该从适配器的内部获取 POJO 项目 项目数据,并在 ViewHolder 中的视图上调用设置器。如果房车 OnBindView 需要很长时间,请确认您正在做的最少 在您的绑定代码中工作。
【讨论】:
【参考方案6】:这就是我在 ViewHolder 而不是 onBindViewHolder 中实现按钮点击的方式。这个例子展示了如何将多个按钮与一个界面绑定,这样在填充行时不会生成更多的对象。
示例是西班牙语和 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)
以及要在每个适配器中实现的 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)
【讨论】:
【参考方案7】:我遇到了一个小问题,如果其他人也面临这个问题,我想在答案中分享。 我有图像和文本在 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;
但是,当我在回收视图中单击卡片时,它将不起作用,因为 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;
也就是说,现在人们必须点击缩略图或图像,而不是 itemview。
【讨论】:
【参考方案8】:你也可以这样做..
MainActivity class
在这多种类型的界面触发器中你可以实现这个...
Adapter class
【讨论】:
以上是关于为啥在 RecyclerView.Adapter 的 onBindViewHolder 中添加 OnClickListener 被认为是不好的做法?的主要内容,如果未能解决你的问题,请参考以下文章
java 在RecyclerView Adapter onCreateViewHolder()获取查看位置
RecyclerView.Adapter.notifyItemMoved(0,1) 滚动屏幕
Android RecyclerView Adapter notifyItemChanged() 获取 IllegalStateException 已经在池中
我无法从另一个带有 recyclerview.adaper 的片段中打开带有 recyclerview.adapter 的片段