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

时间:2015-11-21 16:35:52

标签: java android performance android-recyclerview

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

8 个答案:

答案 0 :(得分:53)

在ViewHolder中处理点击逻辑更好的原因是因为它允许更明确的点击侦听器。正如Commonsware书中所表达的那样:

  

ListView行中的可点击小部件(如RatingBar)长期以来与行本身的点击事件冲突。获取可以单击的行,行内容也可以单击,有时会有点棘手。使用RecyclerView,您可以更明确地控制如何处理这类事情......因为您是设置所有点击处理逻辑的人。

通过使用ViewHolder模型,您可以在RecyclerView中获得比以前ListView中的点击处理更多的好处。我在博客文章中写了这篇文章,比较了差异 - https://androidessence.com/android/recyclerview-vs-listview/

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

答案 1 :(得分:13)

onCreateViewHolder()方法会在前几次被调用,每个ViewHolder需要viewType。每次新项目滚动到视图中或者其数据发生更改时,都会调用onBindViewHolder()方法。您希望避免onBindViewHolder()中的任何昂贵操作,因为它会降低您的滚动速度。这在onCreateViewHolder()中不太重要。因此,在OnClickListener中创建onCreateViewHolder()之类的内容通常会更好,这样它们每ViewHolder个对象只会发生一次。您可以在侦听器中调用getLayoutPosition()以获取当前位置,而不是将position参数提供给onBindViewHolder()

答案 2 :(得分:8)

每次将视图绑定到尚未看到的对象时,都会调用onBindViewHolder方法。每次你都会添加新的听众。你应该这样做

@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 :(得分:5)

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

答案 4 :(得分:0)

https://developer.android.com/topic/performance/vitals/renderonBindViewHolder应该在“少于一毫秒的时间内”完成工作,以防止缓慢渲染。

答案 5 :(得分:0)

这是我在ViewHolder中而不是onBindViewHolder中实现按钮单击的方式。此示例显示了如何将多个按钮与一个接口绑定,该接口在填充行时不会生成更多对象。

该示例使用西班牙语和科特林,但是我确信逻辑是可以理解的。

/**
 * 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)
}

答案 6 :(得分: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。

答案 7 :(得分:0)

您也可以通过这种方式进行。

MainActivity class

在这种多种类型的界面触发器中,您可以实现此目标...

Adapter class