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.
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.
By default it have 5.
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.
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.
I've forgot to implement getItemId
when using setHasStableIds(true);
Implementing that solved the issue!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With