Чому додавання OnClickListener усередині onBindViewHolder RecyclerView.Adapter вважається поганою практикою?


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

        }
    }
}

Однак я вважаю, що застосовувати OnClickListener у onBindViewHolderметоді - погана практика . Чому така погана практика і яка краща альтернатива?

Відповіді:


63

Причиною того, що краще обробляти вашу логіку клацання всередині ViewHolder, є те, що вона дозволяє більш чітко прослуховувати кліки. Як зазначено в книзі Commonsware:

Віджети, які можна натиснути, як RatingBar, у рядку ListView вже давно суперечать подіям клацання на самих рядках. Отримання рядків, за якими можна клацнути, із вмістом рядків, за яким також можна клацнути, часом стає досить складно. За допомогою RecyclerView ви маєте більш чіткий контроль над тим, як обробляються подібні речі ... адже саме ви налаштовуєте всю логіку обробки за кліком.

Використовуючи модель ViewHolder, ви можете отримати багато переваг для обробки кліків у RecyclerView, ніж раніше в ListView. Я писав про це в дописі в блозі, порівнюючи відмінності - https://androidessence.com/recyclerview-vs-listview

Що стосується того, чому це краще у ViewHolder, а не в onBindViewHolder(), це тому onBindViewHolder(), що викликається для кожного елемента, а встановлення прослуховувача кліків є непотрібною опцією для повторення, коли ви можете викликати його один раз у своєму конструкторі ViewHolder. Потім, якщо ваш клік відповідає залежно від позиції елемента, на який клацнули, ви можете просто зателефонувати getAdapterPosition()з ViewHolder. Ось ще одна відповідь, яку я дав, яка демонструє, як ви можете використовувати the OnClickListenerз вашого класу ViewHolder.


2
Щоб уникнути непотрібних налаштувань прослуховувача клацань, отримайте! Але чи можемо ми реалізувати це всередині onCreateViewHolder (), як пропонує Brucelet (див. Відповідь нижче).
Sujit Yadav

1
@SujitYadav Я припускаю, що це мало б такий самий ефект, оскільки onCreateViewHolder()викликається лише один раз (на ViewHolder), тож чи впроваджувати це всередині конструктора ViewHolder чи в, onCreateViewHolder()залежить від вас як особистих переваг. У мене склалася звичка розміщувати це у VH, але ви повинні робити те, що, на вашу думку, є найбільш читабельним, і це допоможе вам зрозуміти в майбутньому. Просто уникайте onBindViewHolder()з міркувань продуктивності, як пропонується браслет.
AdamMc331

@Sujit @McAdam Мені, як правило, подобається робити це onCreateViewHolder()замість ViewHolderконструктора, щоб я міг створити свій ViewHolderклас staticі не потрібно передавати посилання на адаптер в ViewHolder. Але це в кінцевому рахунку , в основному , вибір стилю, як і повинно бути відповідність один до одного між onCreateViewHolder()і new ViewHolder().
RussHWolf

Вам не потрібно передавати посилання на адаптер у вікні перегляду? Ви можете зателефонувати getAdapterPosition()зсередини ViewHolder. Дивіться відповідь, на яку я посилався. Якщо я не зрозумів, що ти мав на увазі?
AdamMc331

@FirstOne Дякуємо за повідомлення! Я переписав блог деякий час назад. Я оновив посилання. :)
AdamMc331

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-операцій .?bcoz, коли я використовував getLayoutPosition (), це все одно працює!
аді,

15

onCreateViewHolder()Метод буде викликатися перші кілька разів ViewHolderнеобхідні кожен viewType. onBindViewHolder()Метод буде викликатися кожен раз , коли новий елемент прокручується в поле зору, або має зміна даних. Ви хочете уникнути будь-яких дорогих операцій, onBindViewHolder()оскільки це може уповільнити вашу прокрутку. Це менше турбує в onCreateViewHolder(). Таким чином, як правило, краще створювати такі речі, як OnClickListeners, onCreateViewHolder()щоб вони траплялися лише один раз на ViewHolderоб’єкт. Ви можете зателефонувати getLayoutPosition()всередину слухача, щоб отримати поточну позицію, а не брати positionаргумент, наданий onBindViewHolder().


7

Павло подав чудовий приклад коду, окрім одного рядка в кінці. Ви повинні повернути створений власник. Не новий переглядач (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/render , він onBindViewHolderповинен виконувати свою роботу "набагато менше однієї мілісекунди", щоб запобігти повільному візуалізації.

RecyclerView: Прив'язка триває занадто довго

Прив'язка (тобто onBindViewHolder (VH, int)) повинна бути дуже простою і займати набагато менше однієї мілісекунди для всіх, крім найскладніших елементів. Він просто повинен брати елементи POJO із внутрішніх даних елементів вашого адаптера та викликати налаштування викликів у поданнях у ViewHolder. Якщо RV OnBindView займає багато часу, переконайтесь, що ви виконуєте мінімальну роботу зі своїм кодом прив'язки.


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

0

я зіткнувся з невеликою проблемою, якою хочу поділитися у відповідях, якщо хтось інший також стикається з нею. я мав зображення та текст, щоб показати у 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;
    }

однак, коли я натисну на карту в режимі переробки, це не буде працювати, оскільки елемент перегляду знаходиться під зображенням. Таким чином, я трохи змінив код наступним чином.

 @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 тепер людині доведеться натискати на ескіз чи зображення.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.