Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView onBindViewHolder only called when getItemViewType changes

I have a viewholder with multiple viewtypes.

When scrolling onBindViewHolder is only called when getItemViewType changes value. This causes my list items to not be updated properly.

Is this a bug? Or I'm i doing something wrong here. This seems very strange behaviour from the new recyclerView class.

Here is my adapter:

package se.davison.smartrecycleradapter;  import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.util.SparseIntArray; import android.view.LayoutInflater; import android.view.ViewGroup;  import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List;  /**  * Created by richard on 10/11/14.  */ public class SectionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {       private static final String TAG = SectionAdapter.class.getSimpleName();     private final LayoutInflater inflater;     private final int sectionLayoutId;     private SparseIntArray positionSection;     private LinkedHashMap<AdapterItem, List<AdapterItem>> items = new LinkedHashMap<AdapterItem, List<AdapterItem>>();     private List<Class<? extends AdapterItem>> itemTypes = new ArrayList<Class<? extends AdapterItem>>(20);      public SectionAdapter(Context context, int sectionLayoutId, LinkedHashMap<AdapterItem, List<AdapterItem>> items) {         this.inflater = LayoutInflater.from(context);         this.sectionLayoutId = sectionLayoutId;         this.items = items;         initList(items);     }      public SectionAdapter(Context context, int sectionLayoutId) {         this.inflater = LayoutInflater.from(context);         this.sectionLayoutId = sectionLayoutId;         this.items = new LinkedHashMap<AdapterItem, List<AdapterItem>>();         initList(items);     }       @Override     public int getItemViewType(int position) {         AdapterItem item = getItem(position);         Log.d(TAG, position + ": class " + item.getClass());         return itemTypes.indexOf(item.getClass());     }      @Override     public int getItemCount() {         int count = 0;         if (items == null) {             return 0;         }          for (AdapterItem key : items.keySet()) {             count++;             List<AdapterItem> childItems = items.get(key);             if (childItems != null) {                 count += childItems.size();             }         }         return count;     }      private void initList(HashMap<AdapterItem, List<AdapterItem>> items) {         positionSection = new SparseIntArray(items.size());         int count = 0;         int sectionIndex = -1;         for (AdapterItem key : items.keySet()) {             Class headerClass = key.getClass();             if (!itemTypes.contains(headerClass)) {                 itemTypes.add(headerClass);             }             List<AdapterItem> childItems = items.get(key);             sectionIndex = count;             if (childItems != null) {                 for (AdapterItem item : childItems) {                     Class clazz = item.getClass();                     if (!itemTypes.contains(clazz)) {                         itemTypes.add(clazz);                     }                     positionSection.put(count, sectionIndex);                     count++;                 }              }             count++;         }         setHasStableIds(true);     }      private AdapterItem getItem(int position) {          int totalChildCount = 0;         int separatorCount = 0;         for (AdapterItem key : items.keySet()) {             if (position == 0 || position == totalChildCount + separatorCount) {                 return key;             }             separatorCount++;             List<AdapterItem> list = items.get(key);             int couldCount = countList(list);             if (position < totalChildCount + separatorCount + couldCount) {                 return list.get(position - (totalChildCount + separatorCount));             }              totalChildCount += couldCount;         }          return null;     }      public void setItems(LinkedHashMap<AdapterItem, List<AdapterItem>> items) {         this.items = items;         notifyDataSetChanged();     }      public void setItemsAtHeader(int id, List<AdapterItem> items) {         AdapterItem header = null;         for (AdapterItem key : this.items.keySet()) {             if (key.headerId() == id) {                 header = key;                 break;             }         }         if (header == null) {             throw new IllegalArgumentException(String.format("No header with id %s is found", id));         }         setItemsAtHeader(header, items);     }      private void setItemsAtHeader(AdapterItem header, List<AdapterItem> items) {         this.items.put(header, items);     }      private int countList(List<?> list) {         return list == null ? 0 : list.size();     }      @Override     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {         AdapterItem firstItem = getFirstItemWithClass(itemTypes.get(viewType));         return firstItem.onCreateViewHolder(inflater, viewGroup);     }      private AdapterItem getFirstItemWithClass(Class<? extends AdapterItem> clazz) {          for (AdapterItem key : items.keySet()) {             if (key.getClass() == clazz) {                 return key;             }             List<AdapterItem> childItems = items.get(key);             if (childItems != null) {                 for (AdapterItem item : childItems) {                     if (item.getClass() == clazz) {                         return item;                     }                 }             }         }         throw new IllegalStateException("Something is wrong, you dont have any items with that class in your list");     }       @Override     public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {         AdapterItem item = getItem(position);         Log.d(TAG, "ITEM = " + item.getClass().getSimpleName());         Log.d(TAG, "POS = " + position);         if (item instanceof OneLineAdapterItem) {             Log.d(TAG, "TEXT = " + ((OneLineAdapterItem) item).getText());         }          item.onBindViewHolder(viewHolder, position);     } } 

I've also abstracted out the items like so:

public abstract class AdapterItem<VH extends RecyclerView.ViewHolder> {       public boolean isHeader(){         return false;     }      public int headerId(){         return -1;     }      public abstract VH onCreateViewHolder(LayoutInflater inflater, ViewGroup parent);      public abstract void onBindViewHolder(VH viewHolder, int position); } 

And for sections

public class SectionAdapterItem extends AdapterItem<SectionAdapterItem.ViewHolder> {      private String text;     private boolean dividerVisible = false;      public static class ViewHolder extends RecyclerView.ViewHolder{          TextView titel;         ImageView divider;          public ViewHolder(View itemView) {             super(itemView);             titel = (TextView) itemView.findViewById(android.R.id.title);             divider = (ImageView) itemView.findViewById(android.R.id.icon);         }     }      public void setDividerVisible(boolean dividerVisible) {         this.dividerVisible = dividerVisible;     }      public boolean isDividerVisible() {         return dividerVisible;     }      @Override     public boolean isHeader() {         return true;     }      @Override     public int headerId() {         return super.headerId();     }      public SectionAdapterItem(String text) {         this.text = text;     }      @Override     public ViewHolder onCreateViewHolder(LayoutInflater inflater, ViewGroup parent) {         return new ViewHolder(inflater.inflate(R.layout.top_header, parent, false));     }      @Override     public void onBindViewHolder(ViewHolder viewHolder, int position) {         viewHolder.titel.setText(text);         viewHolder.divider.setVisibility(dividerVisible?View.VISIBLE:View.GONE);     } } 

It works fine for the frist visible rows, but then it fails.

like image 522
Richard Avatar asked Nov 13 '14 13:11

Richard


People also ask

How often is onBindViewHolder called?

However, in RecyclerView the onBindViewHolder gets called every time the ViewHolder is bound and the setOnClickListener will be triggered too. Therefore, setting a click listener in onCreateViewHolder which invokes only when a ViewHolder gets created is preferable. Here is a diagram of the implementation.

How many times is called onCreateViewHolder?

By default it have 5.

What is the use of onBindViewHolder?

bindViewHolder. This method internally calls onBindViewHolder(ViewHolder, int) to update the RecyclerView. ViewHolder contents with the item at the given position and also sets up some private fields to be used by RecyclerView.

What does notifyDataSetChanged do in RecyclerView?

notifyDataSetChanged. Notify any registered observers that the data set has changed. There are two different classes of data change events, item changes and structural changes. Item changes are when a single item has its data updated but no positional changes have occurred.


1 Answers

I've forgot to implement getItemId when using setHasStableIds(true);

Implementing that solved the issue!

like image 189
Richard Avatar answered Sep 29 '22 07:09

Richard