RecyclerView вызывает проблемы при переработке

У меня есть список элементов, которые я создал с помощью RecyclerView . Когда пользователь нажимает на один из них, я меняю цвет фона для выбранного элемента. Проблема в том, что когда я просматриваю свои объекты, и они перерабатываются, некоторые элементы получают цвет фона выбранного элемента (что неверно). Здесь вы можете увидеть код моего Adapter :

 public class OrderAdapter extends RecyclerView.Adapter { private static final String SELECTED_COLOR = "#ffedcc"; private List mOrders; public OrderAdapter() { this.mOrders = new ArrayList(); } public void setOrders(List orders) { mOrders = orders; } public void addOrders(List orders) { mOrders.addAll(0, orders); } public void addOrder(OrderModel order) { mOrders.add(0, order); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { Context context = parent.getContext(); LayoutInflater inflater = LayoutInflater.from(context); // Inflate the custom layout View contactView = inflater.inflate(R.layout.order_main_item, parent, false); // Return a new holder instance ViewHolder viewHolder = new ViewHolder(contactView); return viewHolder; } @Override public void onBindViewHolder(final ViewHolder viewHolder, final int position) { final OrderModel orderModel = mOrders.get(position); // Set item views based on the data model TextView customerName = viewHolder.customerNameText; SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/dd/yyyy' 'HH:mm:ss:S"); String time = simpleDateFormat.format(orderModel.getOrderTime()); customerName.setText(time); TextView orderNumber = viewHolder.orderNumberText; orderNumber.setText("Order No: " + orderModel.getOrderNumber()); Button button = viewHolder.acceptButton; button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { viewHolder.userActions.acceptButtonClicked(position); } }); final LinearLayout orderItem = viewHolder.orderItem; orderItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { viewHolder.userActions.itemClicked(orderModel); viewHolder.orderItem.setBackgroundColor(Color.parseColor(SELECTED_COLOR)); } }); } @Override public int getItemCount() { return mOrders.size(); } public static class ViewHolder extends RecyclerView.ViewHolder implements OrderContract.View { public TextView customerNameText; public Button acceptButton; public TextView orderNumberText; public OrderContract.UserActions userActions; public LinearLayout orderItem; public ViewHolder(View itemView) { super(itemView); userActions = new OrderPresenter(this); customerNameText = (TextView) itemView.findViewById(R.id.customer_name); acceptButton = (Button) itemView.findViewById(R.id.accept_button); orderNumberText = (TextView) itemView.findViewById(R.id.order_number); orderItem = (LinearLayout) itemView.findViewById(R.id.order_item_selection); } @Override public void removeItem() { } } 

Проблема заключается в recyclerView использовании recyclerView которое присваивает вашим элементам ViewHolder вне экрана новые элементы, которые будут отображаться на экране. Я бы не предложил вам привязать свою логику на ViewHolder объекта ViewHolder как и во всех вышеперечисленных ответах. Это действительно вызовет у вас проблемы. Вы должны строить логику, основанную на состоянии вашего объекта данных, а не объекта ViewHolder поскольку вы никогда не узнаете, когда он будет переработан.

Предположим, что вы сохраните состояние boolean isSelected в ViewHolder чтобы проверить, но если это правда, то такое же состояние будет там для нового элемента, когда этот viewHolder будет переработан.

Лучший способ сделать это – сохранить любое состояние объекта DataModel. В вашем случае выбирается только логическое значение.

Пример примера

 package chhimwal.mahendra.multipleviewrecyclerproject; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.support.v7.widget.CardView; import android.widget.TextView; import java.util.List; /** * Created by mahendra.chhimwal on 12/10/2015. */ public class MyRecyclerViewAdapter extends RecyclerView.Adapter { private Context mContext; private List mRViewDataList; public MyRecyclerViewAdapter(Context context, List rViewDataList) { this.mContext = context; this.mRViewDataList = rViewDataList; } @Override public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.item_recycler_view, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { holder.bindDataWithViewHolder(mRViewDataList.get(position)); } @Override public int getItemCount() { return mRViewDataList != null ? mRViewDataList.size() : 0; } public class ViewHolder extends RecyclerView.ViewHolder { private TextView textView; private LinearLayout llView; private DataModel mDataItem=null; public ViewHolder(View itemView) { super(itemView); llView=(LinearLayout)itemView.findViewById(R.id.ll_root_view); textView = (TextView) itemView.findViewById(R.id.tvItemName); cvItemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // One should handle onclick of event here based on the dataItem ie mDataItem in this case. // something like that.. /* Intent intent = new Intent(mContext,ResultActivity.class); intent.putExtra("MY_DATA",mDataItem); //If you want to pass data. intent.putExtra("CLICKED_ITEM_POSTION",getAdapterPosition()); // If one want to get selected item position startActivity(intent);*/ Toast.makeText(mContext,"You clicked item number "+ViewHolder.this.getAdapterPosition(),Toast.LENTH_SHORT).show(); } }); } //This is clean method to bind data with viewHolder. Do all dirty things on View based on dataItem. //Must be called from onBindViewHolder(),with dataItem. In our case dataItem is String object. public void bindDataWithViewHolder(DataModel dataItem){ this.mDataItem=dataItem; if(mDataItem.isSelected()){ llView.setBackgroundColor(Color.ParseColor(SELCTED_COLOR); }else{ llView.setBackgroundColor(Color.ParseColor(DEFAULT_COLOR); } //other View binding logics like setting text , loading image etc. textView.setText(mDataItem); } } } 

Как пояснил @Gabriel в комментарии,

что, если вы хотите выбрать один элемент вовремя?

В этом случае снова нельзя сохранять выбранное состояние элемента в объекте ViewHolder , так как оно становится переработанным и вызывает проблему. Для этого лучше иметь поле int selectedItemPosition в classе Adapter не ViewHolder . Следующий fragment кода показывает это.

 public class MyRecyclerViewAdapter extends RecyclerView.Adapter { private Context mContext; private List mRViewDataList; //variable to hold selected Item position private int mSelectedItemPosition = -1; public MyRecyclerViewAdapter(Context context, List rViewDataList) { this.mContext = context; this.mRViewDataList = rViewDataList; } @Override public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.item_recycler_view, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { holder.bindDataWithViewHolder(mRViewDataList.get(position),position); } @Override public int getItemCount() { return mRViewDataList != null ? mRViewDataList.size() : 0; } public class ViewHolder extends RecyclerView.ViewHolder { private TextView textView; private LinearLayout llView; private DataModel mDataItem=null; public ViewHolder(View itemView) { super(itemView); llView=(LinearLayout)itemView.findViewById(R.id.ll_root_view); textView = (TextView) itemView.findViewById(R.id.tvItemName); cvItemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Handling for background selection state changed int previousSelectState=mSelectedItemPosition; mSelectedItemPosition = getAdapterPosition(); //notify previous selected item notifyItemChanged(previousSelectState); //notify new selected Item notifyItemChanged(mSelectedItemPosition); //Your other handling in onclick } }); } //This is clean method to bind data with viewHolder. Do all dirty things on View based on dataItem. //Must be called from onBindViewHolder(),with dataItem. In our case dataItem is String object. public void bindDataWithViewHolder(DataModel dataItem, int currentPosition){ this.mDataItem=dataItem; //Handle selection state in object View. if(currentPosition == mSelectedItemPosition){ llView.setBackgroundColor(Color.ParseColor(SELCTED_COLOR); }else{ llView.setBackgroundColor(Color.ParseColor(DEFAULT_COLOR); } //other View binding logics like setting text , loading image etc. textView.setText(mDataItem); } } } 

Если вам нужно поддерживать только выбранное состояние элемента, я настоятельно рекомендую использовать метод notifyDataSetChanged () classа Adapter, поскольку RecyclerView обеспечивает гораздо большую гибкость для этих случаев.

Вы должны изменить свою логику, чтобы назначить значение внутри элемента (объекта), а не вид:

 orderItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { orderItem.setSelected(xxxx); } }); 

Затем в вашем методе onBindViewHolder вы должны определить цвет в соответствии с этим значением в элементе.

 if (orderItem.isSelected()){ viewHolder.orderItem.setBackgroundColor(xxxx); } else { viewHolder.orderItem.setBackgroundColor(xxxx); } 

Это довольно распространенная ошибка, которая имеет простое решение.

Быстрый ответ: добавьте эту строку в свой метод onBindViewHolder :

 if (orderItem.isSelected()){ viewHolder.orderItem.setBackgroundColor(Color.parseColor(SELECTED_COLOR)); } else { viewHolder.orderItem.setBackgroundColor(Color.parseColor(DEFAULT_COLOR)); } 

DEFAULT_COLOR цветом, который имеет пользовательский указатель)

Объясненный ответ: когда система перерабатывает зритель, он просто вызывает onBindViewHolder поэтому, если вы изменили что-либо из этого зрителя, вам придется его сбросить. Это произойдет, если вы измените фон, позицию позиции и т. Д. Любые изменения, не связанные с содержимым как таковым, должны быть сброшены в этом методе

Давайте будем гением компьютера.