Why when you click on the RecyclerView element, several OnClick methods work, i.e. does the background of several elements change?

Markup:

<android.support.v7.widget.RecyclerView android:id="@+id/calculator_activity_what_to_exchange_horizontal_recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/white" /> 

Class:

 mFromRecyclerView = (RecyclerView) findViewById(R.id.calculator_activity__horizontal_recycler_view); mFromAdapter = new CalculatorHorizontalAdapter(); mFromRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); mFromRecyclerView.setAdapter(mFromAdapter); mFromAdapter.setCourseArray(courseArray); mFromAdapter.notifyDataSetChanged(); 

Adapter:

 public class CalculatorHorizontalAdapter extends RecyclerView.Adapter<CalculatorHorizontalAdapter.MyViewHolder> { private Course[] courseArray; public class MyViewHolder extends RecyclerView.ViewHolder { public TextView ShortTv; public TextView LongTv; public TextView Long2Tv; public LinearLayout holderLayout; public MyViewHolder(View view) { super(view); ShortTv = (TextView) view.findViewById(R.id.item_calculator_currency_short_tv); holderLayout = (LinearLayout) view.findViewById(R.id.item_calculator_currency_ll); } } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_calculator_currency, parent, false); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(final MyViewHolder holder, final int position) { if (courseArray[position].currencyBuy != null) holder.ShortTv.setText(setOfCourses.get(position)); holder.holderLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { holder.holderLayout.setBackgroundColor(Color.parseColor("#0f67c0")); } }); } @Override public int getItemCount() { return courseArray.length; } public CalculatorHorizontalAdapter(Course[] courseArray) { this.courseArray = courseArray; } public CalculatorHorizontalAdapter() { } public void setCourseArray(Course[] courseArray) { this.courseArray = courseArray; } } 

    3 answers 3

    Yesterday I ran into a similar problem, sat for a long time and found a supposed reason. OnClick has nothing to do with it, it's all about the specifics of RecyclerView itself: when initialized, the adapter creates a certain number of ViewHolder instances once, depending on the screen size of the device. They serve as containers for the layouts of elements. The layout is attached to the holder only once in onCreateViewHolder () and belongs to it throughout its existence. When scrolling, RecyclerView reuses the holder that has left the screen and its layout to display the data of the next item using onBindViewHolder (). Thus, by changing the layout of one of the holders, when scrolling you can see the same holder with its modified layout, only with a different position and other data. I hope this will be useful for the next pioneers.

    • This is not a putative reason, but a well-known (and indicated in off-documentation, it was not necessary to sit and think for so long) the fact is that the list re-uses created items (what even its name means) and an item can easily get into the current visible position which "left" behind the screen - this is the principle of operation and optimization of the output to the list and it was so intended. The very thing on the list you get right. - pavlofff
    • The fact may be known, but, obviously, not all. Based on the question itself and the absence of intelligible answers for six months, it seems that the documentation does not allow everyone to immediately, completely and unequivocally realize all these nuances (especially for deletants like me). It's a long time to sit and think for yourself. Thanks for the comment. - Keanu_Reeves
    • The fact is that the question of reusing ListView and RecyclerView items, as well as related issues, is asked here with enviable regularity several times each month (I only answered similar ones several times, for example or here ) and with a slight desire, a sufficiently detailed answer find quite possible. The lack of a description of what is happening in the lists in this particular issue is not an indication that the topic is not disclosed. - pavlofff
    • Thinking is very good, I agree. The fact that you yourself understood the mechanism of action is also just great (although in fact it is much more complicated, but the essence is correct), but for example, I do not have so much free time to understand for a few days what can happen there - you have to force and to search for faster ways to knowledge, off-docks, the first such place, it is better to start studying the problem from there. - pavlofff
    • You're right. To do work that can be avoided is a crime. Unfortunately, as was said, the information described in the documentation was not completely clear to me. As a result, I came to this kind of problem. The lack of answers made me approach the problem in the traditional way. Not a few days, but I definitely killed 5 hours. It is a pity of course, what to do. - Keanu_Reeves

    You do not need to change the background when you click. It will not be saved. @ Dmitry Titov, in his answer, flawlessly named the reason for this. What do you need to do? Create an array and when pressed, change the values ​​in it. And in onBindViewHolder , depending on these values, set the color. By the way, for every if should be else . Look at the code. Do it always and remember - just change something in the list item will not work

     public class CalculatorHorizontalAdapter extends RecyclerView.Adapter<CalculatorHorizontalAdapter.MyViewHolder> { private Course[] courseArray; private boolean[] checks; //это массив. в нем будем хранить true если фон изменен и false если нет //по умолчанию все false это то что нам надо public class MyViewHolder extends RecyclerView.ViewHolder implements OnClickListener { @Override public void onClick(View v) { notifyItemChanged(getAdapterPosition()); //обновляем. да, так надо, не знаю почему checks[getAdapterPosition()] = true; //меняем значение в массиве по позиции нажатого айтема notifyItemChanged(getAdapterPosition()); //еще раз после замены } public TextView ShortTv; public TextView LongTv; public TextView Long2Tv; public LinearLayout holderLayout; public MyViewHolder(View view) { super(view); ShortTv = (TextView) view.findViewById(R.id.item_calculator_currency_short_tv); holderLayout = (LinearLayout) view.findViewById(R.id.item_calculator_currency_ll); holderLayout.setOnClickListener(this); } } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_calculator_currency, parent, false); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(final MyViewHolder holder, final int position) { if (courseArray[position].currencyBuy != null) holder.ShortTv.setText(setOfCourses.get(position)); //если значение в массиве true то ставим другой цвет if(checks[position]) holder.holderLayout.setBackgroundColor(Color.parseColor("#0f67c0")); //иначе ставим цвет по умолчанию. я думаю у вас белый else holder.holderLayout.setBackgroundColor(Color.WHITE); } @Override public int getItemCount() { return courseArray.length; } public CalculatorHorizontalAdapter(Course[] courseArray) { this.courseArray = courseArray; } public CalculatorHorizontalAdapter() { } public void setCourseArray(Course[] courseArray) { this.courseArray = courseArray; } } 
    • Nothing has changed (Also, when you click, the background of several elements changes, and not the one that you clicked on ( - Igor
    • @Igor, updated the answer - Flippy

    The element on which you hang listener:

     LinearLayout holderLayout; 

    The element to which you cast the color:

     LinearLayout holderLayout; 
    • Yes, i.e. on the same item. It is not correct? - Igor