RecyclerView causes recycling problems

I have a list of items that I created using RecyclerView . When the user clicks on one of them, I change the background color for the selected item. The problem is that when I look at my items and they are processed, some elements get the selected background color of the element (which is wrong). Here you can see my Adapter code:

 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() { } } 
+7
android android-recyclerview recyclerview-layout
source share
3 answers

The problem is the recyclerView recycle mode, which assigns your ViewHolder elements non-displayable elements that will be displayed on the screen. I would not suggest you bind your logic based on the ViewHolder object, as in all of the above answers. This will really cause you problems. You should build logic based on the state of the data object, not the ViewHolder Object, because you will never know when it will be processed.

Suppose you saved state boolean isSelected in a ViewHolder for verification, but if it is true, then the new item will have the same state when this ViewHolder is recycled.

The best way to do this is to hold any state in the DataModel. In your case, only a boolean value is selected .

Example example for example

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

As @Gabriel explained in a comment,

What if you want to select one item at a time?

In this case, again, you should not save the selected state of the element in the ViewHolder object, since it becomes recycled and causes problems. For this, it is better to have an int selectedItemPosition field in the Adapter class not ViewHolder . The following code snippet shows it.

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

If you need to maintain only the selected state of an element, I highly recommend using the notifyDataSetChanged () method of the Adapter class, since RecyclerView provides much more flexibility for these cases.

+7
source share

You must change your logic to assign a value inside an element (object), and not a view:

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

Then, in your onBindViewHolder method onBindViewHolder you need to determine the color according to this value in the element.

 if (orderItem.isSelected()){ viewHolder.orderItem.setBackgroundColor(xxxx); } else { viewHolder.orderItem.setBackgroundColor(xxxx); } 
+1
source share

This is a fairly common mistake that has a simple solution.

Quick answer: add this line to your onBindViewHolder method:

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

(with DEFAULT_COLOR color, which has a default user interface)

Explained answer: when the system processes the viewer, it simply calls the onBindViewHolder method, so if you change any of this viewer, you will have to reset it. This will happen if you change the background, position position, etc. Any changes that are not related to the content as such should be reset in this method.

+1
source share

All Articles