RecyclerView causa problemas al reciclar

Tengo una lista del artículo que he creado usando RecyclerView. Cuando el usuario hace clic en uno de ellos i cambiar el color de fondo de ese elemento seleccionado. El problema es que cuando hago un desplazamiento a través de mis elementos y recibo reciclaje, algunos de los elementos obtienen el color de fondo del elemento seleccionado (que es incorrecto). Aquí puedes ver el código de mi adaptador:

public class OrderAdapter extends RecyclerView.Adapter<OrderAdapter.ViewHolder> { private static final String SELECTED_COLOR = "#ffedcc"; private List<OrderModel> mOrders; public OrderAdapter() { this.mOrders = new ArrayList<>(); } public void setOrders(List<OrderModel> orders) { mOrders = orders; } public void addOrders(List<OrderModel> 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() { } } 

El problema es el reciclajeVer el comportamiento de reciclaje que asigna los elementos de ViewHolder fuera de la pantalla a los nuevos elementos que vienen a aparecer en la pantalla. No le sugeriría que vinculara su lógica basada en el objeto ViewHolder como en todas las respuestas anteriores. Realmente le causará problemas. Usted debe construir la lógica basada en el estado de su objeto de datos no ViewHolder objeto como nunca sabrá cuando se recicla.

Suponga que se guarda un estado booleano isSelected en ViewHolder para comprobar, pero y si es cierto, entonces el mismo estado estará allí para el nuevo elemento cuando este viewHolder será reciclado.

La mejor manera de hacerlo es mantener el estado en el objeto DataModel. En su caso sólo se selecciona un booleano.

Ejemplo de ejemplo como

 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<MyRecyclerViewAdapter.ViewHolder> { private Context mContext; private List<DataModel> mRViewDataList; public MyRecyclerViewAdapter(Context context, List<DataModel> 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); } } } 

Como @Gabriel preguntó en un comentario,

¿Qué pasa si uno desea seleccionar un solo elemento a la vez?

En ese caso, de nuevo uno no debe guardar el estado del elemento seleccionado en el objeto ViewHolder , ya que el mismo se vuelve a reciclar y causarle problemas. Para que la mejor manera es tener un campo int selectedItemPosition en la clase Adapter no ViewHolder . El siguiente fragmento de código lo muestra.

 public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> { private Context mContext; private List<DataModel> mRViewDataList; //variable to hold selected Item position private int mSelectedItemPosition = -1; public MyRecyclerViewAdapter(Context context, List<DataModel> 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); } } } 

Si sólo tiene que mantener el estado del elemento seleccionado, desaconsejo fuertemente el uso del método notifyDataSetChanged () de la clase Adapter, ya que RecyclerView ofrece mucha más flexibilidad para estos casos.

Debe modificar su lógica asignar el valor dentro del elemento (objeto) no la vista:

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

Entonces en su método onBindViewHolder usted tiene que asentar el color según este valor en el artículo.

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

Este es un error común que tiene una solución fácil.

Respuesta rápida: agregue esta línea en su método onBindViewHolder :

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

(Con DEFAULT_COLOR el color que el usuario tiene por defecto)

Respuesta explicada: cuando el sistema recicla a un espectador simplemente llama onBindViewHolder método onBindViewHolder , así que si ha cambiado algo de ese observador tendrá que restablecerlo. Esto sucederá si cambia el fondo, la posición del elemento, etc. Cualquier cambio que no esté relacionado con el contenido por sí mismo debe restablecerse en ese método

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.