繁体   English   中英

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

[英]Why is adding an OnClickListener inside onBindViewHolder of a RecyclerView.Adapter considered bad practice?

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

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

ListView 行中的可点击小部件(如 RatingBar)长期以来一直与行本身的点击事件发生冲突。 获取可以点击的行,以及也可以点击的行内容,有时会变得有点棘手。 使用 RecyclerView,您可以更明确地控制此类事情的处理方式……因为您是设置所有点击处理逻辑的人。

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

至于为什么在 ViewHolder 中而不是在onBindViewHolder()onBindViewHolder() ,那是因为onBindViewHolder()为每个项目调用,并且当您可以在 ViewHolder 构造函数中调用一次时,设置单击侦听器是不必要的重复选项. 然后,如果您的点击响应取决于所点击项目的位置,您可以简单地从 ViewHolder 中调用getAdapterPosition() 这里是另外一个答案我已经给演示如何您可以使用OnClickListener从ViewHolder类中。

每次当您将视图与尚未看到的对象绑定时,都会调用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;
}

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

除了最后一行之外,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;
}

根据https://developer.android.com/topic/performance/vitals/renderonBindViewHolder应该在“远小于一毫秒”内完成其工作,以防止渲染缓慢。

RecyclerView:绑定时间太长

绑定(即 onBindViewHolder(VH, int))应该非常简单,除了最复杂的项目外,所有项目的时间都远少于一毫秒。 它只是应该从适配器的内部项目数据中获取 POJO 项目,并在 ViewHolder 中的视图上调用 setter。 如果 RV OnBindView 需要很长时间,请确认您在绑定代码中所做的工作最少。

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

我遇到了一个小问题,如果其他人也遇到了,我想在答案中分享。 我有图像和文本在 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。

你也可以这样做..

主活动类

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

适配器类

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM