为什么在RecyclerView.Adapter的onBindViewHolder中添加OnClickListener被认为是不好的做法?


76

我有一个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是不好的做法。为什么会有这种不好的做法,还有什么更好的选择?

Answers:


63

之所以最好在ViewHolder中处理点击逻辑,是因为它允许更明确的点击侦听器。如Commonsware书中所述:

很久以来,ListView行中的可单击小部件(如RatingBar)一直与行本身的单击事件发生冲突。获取可以单击的行以及也可以单击的行内容有时会有些棘手。使用RecyclerView,您可以更加明确地控制如何处理此类事件……因为您是负责设置所有单击处理逻辑的人。

通过使用ViewHolder模型,与以前在ListView中相比,RecyclerView中的单击处理可以获得很多好处。我在比较差异的博客文章中写道-https: //androidessence.com/recyclerview-vs-listview

至于为什么在ViewHolder而不是in更好onBindViewHolder(),这是因为onBindViewHolder()每个项目都被调用并设置click监听器是不必要的选项,当您可以在ViewHolder构造函数中一次调用它时就可以重复。然后,如果您的单击响应取决于单击的项目的位置,则只需getAdapterPosition()在ViewHolder中调用即可。是我给出的另一个答案,展示了如何OnClickListener在ViewHolder类中使用from。


2
为避免不必要的点击侦听器设置,它得到了!但是我们可以按照Brucelet的建议在onCreateViewHolder()中实现它吗(请参见下面的答案)。
Sujit Yadav 2015年

1
@SujitYadav我想它会产生相同的效果,因为onCreateViewHolder()它只被调用一次(每个ViewHolder),所以您onCreateViewHolder()是根据个人喜好决定是否在ViewHolder构造函数中实现它。我已经养成了将其放入VH中的习惯,但是您应该做您认为最易读的内容,并在将来帮助您理解。只是避免onBindViewHolder()出于性能原因,例如brucelet建议。
AdamMc331

@Sujit @McAdam我倾向于onCreateViewHolder()ViewHolder构造器中而不是在构造器中这样做,这样我就可以创建我的ViewHolderstatic,而无需将对适配器的引用传递给ViewHolder。但它最终多半是风格的选择,应该有之间的一个一一对应onCreateViewHolder()new ViewHolder()
RussHWolf,2015年

您不需要在视座中传递对适配器的引用吗?您可以getAdapterPosition()从ViewHolder内部调用。查看我链接到的答案。除非我误解了你的意思?
AdamMc331

@FirstOne感谢您的通知!我前一段时间改写了博客。我已经更新了链接。:)
AdamMc331 '18

16

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()时,bcoz仍然有效!
ADI公司

15

onCreateViewHolder()方法将被称为第一数次ViewHolder,需要每一个viewTypeonBindViewHolder()每当新项目滚动到视图中或更改其数据时,都会调用该方法。您希望避免执行任何昂贵的操作,onBindViewHolder()因为这会降低滚动速度。这与无关紧要onCreateViewHolder()。因此,通常最好创建OnClickListeners in之类的东西,以onCreateViewHolder()使每个ViewHolder对象只发生一次。您可以getLayoutPosition()在侦听器内部进行调用以获取当前位置,而不是使用position提供给的参数onBindViewHolder()


7

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;
}

3

https://developer.android.com/topic/performance/vitals/renderonBindViewHolder应该做的工作在“远小于1毫秒”,以防止慢渲染。

RecyclerView:绑定时间过长

绑定(即onBindViewHolder(VH,int))应该非常简单,并且除最复杂的项目外,其他所有项目的花费都少于一毫秒。它仅应从适配器的内部项目数据中获取POJO项目,并在ViewHolder中的视图上调用设置程序。如果RV OnBindView花费很长时间,请验证您在绑定代码中所做的工作最少。


0

这就是我在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)
}

0

我遇到了一个小问题,如果其他人也遇到这个问题,我想在答案中分享。我有图像和文本以Cardview的形式显示在Recycleview中。因此,根据建议,我的代码应如下所示。

@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。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.