RecyclerView mixed up data while scrolling

Problem scrolling RecyclerView after scrolling down and up. The idea is to change the color of the elements, but when I scroll down, everything is fine, and when the scroll goes up - the elements that should not be colored change color.

Here is my adapter:

public class NotificationsAdapter extends RecyclerView.Adapter<NotificationsAdapter.ViewHolder> { private NotificationData notificationData; private Context mContext; private ArrayList<NotificationData> infromationList = new ArrayList<>(); public NotificationsAdapter(Context context, ArrayList<NotificationData> infromationList) { this.infromationList = infromationList; this.mContext = context; } @Override public NotificationsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemLayoutView; ViewHolder viewHolder; itemLayoutView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.notification_single_item, parent, false); viewHolder = new ViewHolder(itemLayoutView, viewType); return viewHolder; } @Override public void onBindViewHolder(NotificationsAdapter.ViewHolder holder, int position) { notificationData = infromationList.get(position); holder.notificationDate.setText(convertDate(notificationData.getDate())); holder.notificationStatus.setText(notificationData.getNotificationStatus()); holder.orderDescription.setText(notificationData.getNotificationLabel()); if ("true".equals(notificationData.getReadStatus())) { holder.root.setBackgroundColor(mContext.getResources().getColor(R.color.white)); holder.notificationStatus.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); } } @Override public int getItemCount() { return (null != infromationList ? infromationList.size() : 0); } public static class ViewHolder extends RecyclerView.ViewHolder { public TextView notificationDate; public TextView notificationStatus; public TextView orderDescription; public LinearLayout root; public ViewHolder(View itemView, int position) { super(itemView); notificationDate = (TextView) itemView.findViewById(R.id.notificationDate); notificationStatus = (TextView) itemView.findViewById(R.id.notificationStatus); orderDescription = (TextView) itemView.findViewById(R.id.orderDescription); root = (LinearLayout) itemView.findViewById(R.id.root); } } private String convertDate(String date) { String convertedDate; String[] parts = new String[2]; parts = date.split("T"); date = parts[0]; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd"); Date testDate = null; try { testDate = sdf.parse(date); }catch(Exception ex){ ex.printStackTrace(); } SimpleDateFormat formatter = new SimpleDateFormat("dd.mm.yyyy"); convertedDate = formatter.format(testDate); return convertedDate; } } 
+10
source share
10 answers

I had the same problem and the only solution I found for this is:

 holder.setIsRecyclable(false); 

Your repeater will no longer recycle, so when scrolling, the elements will be the same, and if you want to delete an element, do not use notifyitemRemoved(position) , use notifyDataSetChanged() instead.

+38
source

Add setHasStableIds(true); into your adapter constructor and override these two methods in the adapter.

 @Override public long getItemId(int position) { return position; } @Override public int getItemViewType(int position) { return position; } 
+12
source

There is a problem in your onBindViewHolder(...) , it should be:

 if ("true".equals(notificationData.getReadStatus())) { holder.root.setBackgroundColor(mContext.getResources().getColor(R.color.white)); holder.notificationStatus.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); } else { holder.root.setBackgroundColor(yourDefaultColor); holder.notificationStatus.setTypeface(yourDefaultTypeface); } 
+3
source

onBindHolder is called several times since the Recycler View needs to be presented, if only new. Thus, every time you set visilibity in child views, other view states also change.

Whenever you scroll up and down, these views get re-drawn with the wrong visibility settings, so always specify both conditions that cause the recycler to be viewed, do not know the previous state / conditions / values ​​of our widgets.

Decision:

If in the If block you set the visibility of any android widget.setVisibility (View.Gone), then in the else block you must set its visibility to the opposite value, for example widget.setVisibility (View.Visible), to overcome the above problem.

  @Override public void onBindViewHolder(ViewHolder viewHolder, int i) { viewHolder.tvName.setText(ModelCategoryProducts.name.get(i)); viewHolder.tvPrice.setText("Rs."+String.format("%.2f", Float.parseFloat(ModelCategoryProducts.price.get(i)))); if(ModelCategoryProducts.special_price.get(i).equals("null")) { viewHolder.tvSpecialPrice.setVisibility(View.GONE); // here visibility is gone and in else it opposite visibility i set. viewHolder.tvPrice.setTextColor(Color.parseColor("#ff0000")); viewHolder.tvPrice.setPaintFlags(0);// here paint flag is 0 and in else it opposite flag that i want is set. }else if(!ModelCategoryProducts.special_price.get(i).equals("null")){ viewHolder.tvPrice.setTextColor(Color.parseColor("#E0E0E0")); viewHolder.tvSpecialPrice.setVisibility(View.VISIBLE); viewHolder.tvSpecialPrice.setText("Rs." + String.format("%.2f", Float.parseFloat(ModelCategoryProducts.special_price.get(i)))); viewHolder.tvPrice.setPaintFlags(viewHolder.tvPrice.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); } if (!ModelCategoryProducts.image_url.get(i).isEmpty()) { Picasso.with(context) .load(ModelCategoryProducts.image_url.get(i)) .into(viewHolder.ivProduct); } viewHolder.setClickListener(new ItemClickListener() { @Override public void onClick(View view, int position, boolean isLongClick) { if (isLongClick) { // Toast.makeText(context, "#" + position + " - " + ModelCategoryProducts.name.get(position) + " (Long click)", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, "#" + position + " - " + ModelCategoryProducts.name.get(position), Toast.LENGTH_SHORT).show(); Intent i = new Intent(context, ProductDetail.class); i.putExtra("position",position); i.putExtra("flagHlvCheck", 5); context.startActivity(i); } } }); } 
+1
source
  @Override public DataObjectHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.custom_layout, parent, false); DataObjectHolder dataObjectHolder = new DataObjectHolder(view); dataObjectHolder.setIsRecyclable(false); return dataObjectHolder; } 
0
source

The best way is to specify an ArrayList, for example, as a model, to have some parameters and define a setter and getter for it.

 package com.test.mohammaddvi.snappfood.Model; public class OfferList { private boolean visibilityOrder; private int number; public OfferList(int number, boolean visibilityOrder) { this.number=number; this.visibilityOrder=visibilityOrder; } public boolean isVisibilityOrder() { return visibilityOrder; } public void setVisibilityOrder(boolean visibilityOrder) { this.visibilityOrder = visibilityOrder; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } 

}

and set the variables as you need, and to get this you must do this in the onBindViewHolder of your RecyclelerView Adapter:

 if (offerList.isVisibilityOrder()) { holder.foodMinusButton.setVisibility(View.VISIBLE); holder.foodOrderNumber.setText(offerList.getNumber() + ""); holder.foodOrderNumber.setVisibility(View.VISIBLE); } else { holder.foodMinusButton.setVisibility(View.INVISIBLE); } 

and specify your adapter for reuse:

 public class RecyclerViewMenuFragmentAdapter extends RecyclerView.Adapter<RecyclerViewMenuFragmentAdapter.SingleItemInMenuFragment> { private ArrayList<Food> foodList; private Context mContext; private List<OfferList> offers; public RecyclerViewMenuFragmentAdapter(ArrayList<Food> foodList, Context mContext, List<OfferList> offers) { this.foodList = foodList; this.mContext = mContext; this.offers = offers; } 
0
source

Try adding this to the adapter.

 @Override public int getItemViewType(int position) { return position; } 
0
source
 @Override public void onBindViewHolder(final MyViewHolder holder, int position) { final UserData userdata = userdataList.get(position); holder.setIsRecyclable(false); holder.name.setText(userdata.getName()); holder.active.setChecked(userdata.getActive()); String userPic = userdata.getPic(); holder.active.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked){ userdata.setActive(isChecked); } }); } 
0
source
 class AnyRVAdapter: androidx.recyclerview.widget.RecyclerView.Adapter<AnyRVAdapter.MViewHolder>() { // put saver outside viewholder val saveLayId = mutableListOf<Int>() inner class MViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView) { fun bindModel(d: TesListModel.MList, position:Int) { // concept here val showedId= saveLayId.find { s -> s == layoutPosition} if (idClicked == null) { // save the layout id lyClicked.visibility = View.VISIBLE saveLayId.add(layoutPosition) } else { // remove the layout id lyClicked.visibility = View.INVISIBLE saveLayId.remove(layoutPosition) } } } 

but I think this code is heavy if you use for a large list of data.

0
source

If someone may encounter problems getting random values ​​in some fields in the viewport, try setting at least any default value for all fields.

0
source

All Articles